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

Abierto
#1,755 2 comentarios 0 reacciones 1 asignado Ver en GitHub

Los mantenedores suelen responder en 1 día

@Kh4ster ya está trabajando en esto.

Desde el 20/8/2026.

Evaluación

Este issue todavía no se ha evaluado.

Descripción

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
Lenguaje dominante
Cuda
Estrellas
1.1k
Forks
255
Merge medio
1 d 11 h
PR fusionados (30 d)
123

Preparar el entorno

Primeros pasos

  1. Lee el issue completo y luego la guía de contribución del proyecto.
  2. Comenta en el issue que vas a ocuparte — evita que dos personas hagan lo mismo.
  3. Haz un fork del repositorio y trabaja en una rama.
  4. Abre un pull request que haga referencia al número del issue.

Más de NVIDIA/cuopt

Todos los issues de NVIDIA/cuopt

Recibe los nuevos issues en tu correo

Un resumen breve de issues de GitHub para principiantes.