Skip to content

Commit 9930616

Browse files
committed
Separate network adapters from the SDK transport API review
1 parent 9dd4f52 commit 9930616

62 files changed

Lines changed: 190 additions & 3683 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

TRANSPORT_API_PLAN.md

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,10 @@
22

33
Status: implementation in progress. New APIs still need final compatibility and native-binding review before release.
44

5+
## Review layout
6+
7+
The review stack separates the SDK transport APIs, native gRPC adapter, MQTT adapter, and AMQP adapter into four pull requests, in that order. This document tracks the whole initiative; the SDK pull request contains no network adapter implementation or optional adapter dependencies. Each adapter pull request targets the preceding branch so its diff contains only that layer.
8+
59
## Progress
610

711
| Work | Current evidence | Remaining |
@@ -252,7 +256,27 @@ The native adapter now exposes gRPC's verified `peer_identity_key` and immutable
252256

253257
TLS validation exposed a gRPC completion-queue limitation, reproduced without MCP in `examples/transports/reproduce_grpc_loop_shutdown.py`. With `grpcio==1.84.0` on macOS/Python 3.14.6, cancelled connectivity watches can complete after `channel.close()` and target a previously closed event loop. The adapter suite keeps one AnyIO runner for its session, while still closing per-test resources. The README records this support restriction, not an upstream fix; repeated loop lifetimes and final native-queue drainage are not certified.
254258

255-
Current evidence is in `/tmp/mcp-core-final.log`, `/tmp/mcp-adapter-final310.log`, `/tmp/mcp-adapter-final314.log`, `/tmp/mcp-docs-final.log`, and `/tmp/mcp-conformance-final-{client,server}-*.log`. Coverage data uses `/tmp/mcp-adapter-final310` and `/tmp/mcp-adapter-final314`. Core coverage is 100%; total adapter coverage is 90%, with only MQTT/AMQP implementation gaps remaining. Generated protobuf implementation is excluded as compiler output, not handwritten adapter code.
259+
### Reproduce the validation
260+
261+
```bash
262+
./scripts/test
263+
UV_PROJECT_ENVIRONMENT=examples/transports/.venv uv sync --frozen --package mcp-transport-examples --group dev
264+
UV_PROJECT_ENVIRONMENT=examples/transports/.venv uv run --frozen --package mcp-transport-examples --group dev pytest -c examples/transports/pyproject.toml examples/transports/tests --record-mode=none
265+
uv run --frozen pyright --project examples/transports
266+
DOCS_LANGUAGES=en-only bash scripts/docs/build.sh
267+
```
268+
269+
Run the broker commands in `examples/transports/README.md` against the pinned Compose fixtures. The shared-check workflow now runs the complete adapter suite and live broker programs on Python 3.10 and 3.14, and retains `transport-results-<version>` JUnit artifacts. Results are attached to [the pull request's checks](https://github.com/modelcontextprotocol/python-sdk/pull/3517/checks), not machine-local log paths. The conformance workflow records all six baseline legs separately.
270+
271+
Core coverage remains 100%. Whole adapter coverage is still incomplete because MQTT/AMQP failure paths are not cassette-backed; generated protobuf implementation is excluded as compiler output, not handwritten adapter code. Do not interpret a passing adapter pytest job as completion of that separate coverage gate.
272+
273+
### Review corrections
274+
275+
Native regressions now cover swallowed direct-handler cancellation, notification callback isolation, post-close notification drops, late request-scoped notifications, sanitized raw-dispatcher errors, strict progress fields, deep JSON and exponent overflow. HTTP framing supplies handler-visible context and headers without adding credentials to message representations; driver stream cleanup is shielded and bounded. The published principal-binding example is exercised directly.
276+
277+
RabbitMQ no longer grants client writes to the default exchange. Each receiving queue has a dedicated direct exchange, and live checks reject both default-exchange injection and writes to another principal's exchange. MQTT examples configure Last Wills before CONNECT; a live broker-forced disconnect settles a pending MCP call without relying on its request timeout. The existing aiomqtt negative-publish-reason limitation remains a merge blocker: its public API does not expose those acknowledgement codes.
278+
279+
Optional runtime design feedback remains separate from these corrections: configurable cleanup grace, exception-group behavior, and admission cancellation during runtime shutdown need a contract decision rather than an unreviewed change in semantics.
256280

257281
## Next implementation work
258282

docs/advanced/low-level-server.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ In a test you skip uvicorn and the port: `Client(server)` takes a low-level `Ser
6969

7070
`Server.serve()` shares one application lifespan across multiple custom transport connections, just like `MCPServer.serve()`. Use the complete adapter example under [Running your server](../run/index.md#custom-transports).
7171

72-
For a single connection, `Server.run(read_stream, write_stream, initialization_options, *, transport_builder=...)` remains available. The optional builder converts inbound message metadata into the `TransportContext` exposed as `ctx.transport`. Without it, stream dispatch supplies generic JSON-RPC metadata. Both paths retain the existing protocol-version handling; custom transport capabilities cannot enable features that the negotiated version forbids.
72+
For a single connection, `Server.run(read_stream, write_stream, initialization_options, *, transport_builder=...)` remains available. The optional builder converts inbound message metadata into the `TransportContext` exposed as `ctx.transport`. Without it, stream dispatch uses the context supplied by the framing transport, falling back to generic JSON-RPC metadata. Built-in HTTP transports supply their kind and the current request's headers. Both paths retain the existing protocol-version handling; custom transport capabilities cannot enable features that the negotiated version forbids.
7373

7474
## Nothing is checked for you
7575

docs/client/transports.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -157,7 +157,7 @@ The server side of this example uses `server.serve()`. Its lifecycle and connect
157157

158158
`DispatcherTransport` explicitly wraps an async context manager yielding a `Dispatcher`. `Client` enters that context, starts the dispatcher, and uses its ordinary MCP negotiation, callbacks, caching, and validation. It stops the dispatcher before exiting the connection context. You configure the client through the same constructor; there is no separate native client-session API.
159159

160-
The example uses the SDK's `DirectDispatcher`. The repository's `examples/transports/README.md` also contains a real gRPC implementation with protobuf envelopes and JSON payloads. Native network bindings implement this dispatcher boundary instead of creating `SessionMessage` streams. The connection context acquires the transport resources; it must yield an unstarted dispatcher because the SDK owns `run()`.
160+
The example uses the SDK's `DirectDispatcher`. The native gRPC reference adapter is developed in a separate follow-up to this SDK API change. Native network bindings implement this dispatcher boundary instead of creating `SessionMessage` streams. The connection context acquires the transport resources; it must yield an unstarted dispatcher because the SDK owns `run()`.
161161

162162
On the server, `runtime.connect(DispatcherTransport(...))` serves the modern per-request-envelope protocol. It rejects the legacy initialize handshake. Use `mode="auto"` or a supported modern version on the client. Message transports still support both eras. Native dispatchers supply their own contexts, so this server path rejects `session_id=` and `transport_builder=`.
163163

0 commit comments

Comments
 (0)