[FEA] Decouple cuopt_mcp from the cuopt package so it ships as a pure-Python wheel

Aperta
#1,755 2 commenti 0 reazioni 1 assegnatario Vedi su GitHub

I maintainer di solito rispondono entro 1 giorno

@Kh4ster ci sta già lavorando.

Dal 20/8/2026.

Valutazione

Questa issue non è ancora stata valutata.

Descrizione

Scope: decouple cuopt_mcp from the cuopt package

Goal

Make cuopt_mcp a pure-Python, CUDA-free wheel so an MCP client can install it
on a laptop (uvx cuopt-mcp) and talk to a remote cuopt_grpc_server. This is
what server.py already claims — "the host needs no GPU" — and what today's
packaging contradicts, since installing it pulls cuopt-cu13 and the CUDA
runtime onto a machine whose only job is to open a socket.

Secondary payoff: the CUDA-suffixed wheel matrix, the conda recipe and the
per-platform CI jobs all collapse to one py3-none-any artifact. That matters
now because none of that wiring exists yet — this decides what gets built.

What already exists (do not rebuild)

Asset Where Why it matters
Full wire contract cuopt_remote_service.proto, codegen/generated/cuopt_remote_data.proto "The protos and this document together fully specify the wire contract; any gRPC-capable language can implement a client. The cuOpt project does not consider this protocol private." — GRPC_INTERFACE.md
Codegen that already emits a Python-facing artifact generate_conversions.py:409 generate_mcp_schema() → cuopt_mcp_schema.json The "generate it, don't hand-maintain it" pattern is already implemented and shipped
Loader for that artifact cuopt_mcp/schema.py Already falls back between packaged _generated/ and the codegen output dir
Field ids for every array field_registry.yaml (array_id: 0,1,2…) These are exactly the ArrayChunk.field_id values on the wire

schema.py's docstring already states the principle: "Nothing here
hand-maintains a second copy of the settings surface."
This work extends that
from settings to problem and solution arrays.

The five couplings to remove

cuopt_mcp imports exactly five symbols from cuopt, all lazily:

Symbol Site Replacement
grpc…Client client.py:64 generated cuopt_remote_service_pb2_grpc stub over grpcio
grpc…TlsConfig client.py:45 grpc.ssl_channel_credentials
DataModel tools.py:169 populate ChunkedProblemHeader + array payloads directly
SolverSettings tools.py:267 populate PDLPSolverSettings / MIPSolverSettings
Read (MPS/LP/QPS) tools.py:61 open decision — see below

DataModel is the one worth calling out: today the MCP converts JSON arrays →
DataModel → the C++ client takes it apart again into arrays on the wire. The
MCP's JSON input is already the wire shape. Removing the middle step removes a
CUDA-linked C++ type that exists only to be disassembled.

Design

Three layers, smallest first.

1. Transport. grpcio + stubs generated by grpcio-tools from the two
existing protos. Generated at build time and committed alongside the other
codegen output, so ci/verify_grpc_codegen.sh covers drift.

2. Mapping. JSON arrays ↔ proto messages. Emitted from field_registry.yaml
by a new generate_python_mapping() in generate_conversions.py, sitting
beside the existing generate_mcp_schema(). A field added to the registry then
appears on the C++ side, the proto, the MCP schema and the Python mapper — or
CI fails. There is no second interface to drift because there is no
hand-written interface.

3. Tools. tools.py / client.py swap their imports. The tool surface,
JSON contract and tests are unchanged — the existing 63 tests are the
regression suite for this refactor.

Phasing

Phase 1 — unary only. GRPC_INTERFACE.md explicitly offers this: "Stick to
unary SubmitJob/GetResult and raise max_message_bytes on both sides… a custom
client can send any problem up to ~2 GiB or so as a single round-trip and skip
the chunked machinery entirely."
This removes the largest risk from the
critical path. Ships a working CUDA-free MCP.

Phase 2 — chunked path, only if needed. StartChunkedUpload /
SendArrayChunk / FinishChunkedUpload and the download mirror. Needed only
for problems past ~2 GiB, or for throughput (the doc measures ~580 MB/s chunked
vs ~280 MB/s unary on a 706 MB problem). Defer until a real workload asks.

Open decisions

  1. MPS/LP/QPS parsing (Read). Options: (a) drop problem_path, JSON-only;
    (b) add an RPC that ships file bytes and parses server-side; (c) pure-Python
    parser. Recommend (b) — it keeps the feature, puts parsing where the C++
    parser already lives, and lets a remote user submit an MPS file with no local
    cuOpt. Costs a proto addition, so it wants Trevor's view.
  2. Package name. Dropping the dependency makes cuopt-mcp (unsuffixed)
    correct, via disable-cuda = true. Verified: that flag alone un-suffixes the
    name and the dependency, which is only safe once the dependency is gone.
  3. Does cuopt_mcp stay in this repo? In-repo keeps it next to
    field_registry.yaml, which is what makes the generated mapper trustworthy.
    Recommend staying.

Non-goals

  • Replacing the Cython client. It stays the client for in-process/GPU users.
  • Routing/VRP over MCP.
  • Chunking in phase 1.

Risks

  • Two implementations of the wire protocol. Mitigated by generating the
    mapper from the registry and covering it with verify_grpc_codegen.sh; not
    eliminated. The chunking layer in phase 2 would be genuinely hand-written and
    is the real drift risk — another argument for deferring it.
  • Endianness. ArrayChunk.data is "raw native-endian element bytes". A
    Python client must match; numpy.tobytes() is native-endian, so this is fine
    in practice but needs an explicit test.
  • Scope creep into a general Python client. The deliverable is an MCP
    server, not a supported public Python gRPC client. Keep the generated mapper
    private to the package until someone asks otherwise.

Rough effort

Stub generation + build wiring small
generate_python_mapping() emitter medium — the registry is already parsed; this is a new emitter beside generate_mcp_schema()
tools.py / client.py swap small — 5 call sites, tests already exist
Server-side MPS RPC (if chosen) medium, and needs proto review
Phase 2 chunking medium-large, deferred
Lingua principale
Cuda
Stelle
1.1k
Fork
249
Merge medio
1g 11h
PR unite (30g)
123

Preparare l'ambiente

Come iniziare

  1. Leggi tutta la issue e poi la guida ai contributi del progetto.
  2. Commenta sulla issue per dire che te ne occupi tu — evita che due persone facciano lo stesso lavoro.
  3. Fai un fork del repository e lavora su un branch.
  4. Apri una pull request che faccia riferimento al numero della issue.

Altre issue di NVIDIA/cuopt

Tutte le issue di NVIDIA/cuopt

Ricevi le nuove issue nella tua casella

Un breve riepilogo di issue GitHub adatte ai principianti.