Commit Graph

120 Commits

Author SHA1 Message Date
Max Isbey 918e20aba0 test: converge span capture on capfire to fix xdist order-dependence
The previous tests/server/conftest.py called trace.set_tracer_provider()
directly, which is set-once per process and raced against logfire's capfire
fixture (tests/shared/test_otel.py) under xdist — whichever ran first in a
worker won, the other's tests broke.

Converge on capfire as the single span-capture owner since logfire.configure()
already handles repeat calls by swapping span processors instead of re-setting
the provider:

- tests/conftest.py: set LOGFIRE_DISTRIBUTED_TRACING=true so propagation tests
  don't trip logfire's 'found propagated trace context' RuntimeWarning.
- tests/server/conftest.py: SpanCapture adapter over capfire.exporter — filters
  to the mcp-python-sdk instrumentation scope and excludes logfire's
  pending_span markers, so tests assert on raw ReadableSpan without importing
  logfire types.
- tests/shared/test_otel.py: drop the now-unneeded filterwarnings decorator.
2026-04-25 22:14:12 +00:00
Max Isbey 6f80f8d150 test: move asserts inside async-with for 3.11 coverage instrumentation
coverage.py on Python 3.11 doesn't record statements after an
'async with running_pair(...)' exit when there's a nested
'with anyio.fail_after()' inside. Same workaround as 0a8f0f4 in PR2 —
move the asserts inside the async-with block.
2026-04-20 16:58:42 +00:00
Max Isbey a3b896f214 test: close PR3 coverage gaps to 100%
- Connection.check_capability per-field branches (parametrized)
- Context.log with logger and meta supplied
- Peer.notify forwards to wrapped Outbound
2026-04-16 22:44:41 +00:00
Max Isbey 6897c9ad99 feat: Connection, server Context, typed send_request, meta kwarg
TypedServerRequestMixin (server/_typed_request.py) provides shape-2 typed
send_request: per-spec overloads (CreateMessage/Elicit/ListRoots/Ping) infer
the result type; custom requests pass result_type explicitly. Mixed into both
Connection and the server Context.

Connection (server/connection.py) wraps an Outbound for the standalone stream.
notify is best-effort (never raises); send_raw_request gated on
has_standalone_channel; check_capability mirrors v1 for now (FOLLOWUP). Holds
peer info populated at initialize time and the per-connection lifespan state.

Context (server/context.py, alongside v1's ServerRequestContext) composes
BaseContext + PeerMixin + TypedServerRequestMixin and adds lifespan/connection.
Request-scoped log() rides the request's back-channel; ctx.connection.log()
uses the standalone stream.

dump_params(model, meta) merges user-supplied meta into _meta; threaded
through every PeerMixin and Connection convenience method.

31 tests, 0.06s.
2026-04-16 22:21:02 +00:00
Max Isbey 81d58bdd5b refactor: follow Outbound.send_raw_request rename in PeerMixin/BaseContext
PeerMixin methods and Peer/BaseContext now call/expose send_raw_request.
The typed send_request lands on Connection/Context in the next commit.
2026-04-16 22:02:36 +00:00
Max Isbey de81288d34 feat: BaseContext
Composition over a DispatchContext: forwards transport/cancel_requested/
send_request/notify/progress and adds meta. Satisfies Outbound so PeerMixin
works on it (proven by Peer(bctx).ping() round-tripping).

The server Context (next commit) extends this with lifespan/connection;
ClientContext will be an alias once ClientSession is reworked.
2026-04-16 21:53:11 +00:00
Max Isbey 870cb088c7 feat: PeerMixin and Peer wrapper
PeerMixin defines the typed server-to-client request methods (sample with
overloads, elicit_form, elicit_url, list_roots, ping) once. Each method
constrains `self: Outbound` so any class with send_request/notify can mix it
in — pyright checks the host structurally at the call site. The mixin does no
capability gating; that's the host's send_request's job.

Peer is a trivial standalone wrapper for when you have a bare Outbound (e.g.
a dispatcher) and want the typed sugar without writing your own host class.

6 tests over DirectDispatcher, 0.03s.
2026-04-16 21:53:11 +00:00
Max Isbey eb74b7c8c7 refactor: rename send_request to send_raw_request in JSONRPCDispatcher
Follows the Outbound Protocol rename in the previous commit. Mechanical rename
across JSONRPCDispatcher, _JSONRPCDispatchContext, and tests.
2026-04-16 21:49:25 +00:00
Max Isbey 0a8f0f4607 test: address 3.11/3.14 coverage instrumentation quirks
3.14: nested async-with arc misreporting on three create_task_group lines
(the documented AGENTS.md case) — pragma: no branch.

3.11: lines after async-CM exit with pytest.raises mis-traced in one test —
moved the asserts inside the context manager.
2026-04-16 21:43:14 +00:00
Max Isbey 53257cafff test: JSON-RPC-specific dispatcher tests + coverage to 100%
Covers behaviors with no DirectDispatcher analog: out-of-order response
correlation, INTERNAL_ERROR over the wire, peer-cancel in interrupt and signal
modes, CONNECTION_CLOSED on stream EOF mid-await, late-response drop,
raise_handler_exceptions propagation, ServerMessageMetadata tagging on
ctx.send_request, null-id JSONRPCError drop, ValidationError->INVALID_PARAMS,
contextvar propagation via _spawn, and the defensive Broken/Closed/WouldBlock
catches.

Two small src tweaks for coverage:
- _cancel_outbound: combine the two except arms into one tuple
- _dispatch: pragma no-branch on the final case (match is exhaustive over
  JSONRPCMessage; the no-match arc is unreachable)

43 tests, 100% coverage on all PR2 modules, 0.15s wall-clock.
2026-04-16 21:43:14 +00:00
Max Isbey 200a6070db feat: JSONRPCDispatcher exception boundary (chunk c)
_handle_request is now the single exception-to-wire boundary:
- MCPError -> JSONRPCError(e.error)
- pydantic ValidationError -> INVALID_PARAMS
- Exception -> INTERNAL_ERROR(str(e)), logged, optionally re-raised
- outer-cancel (run() TG shutdown) -> shielded REQUEST_CANCELLED write, re-raise
- peer-cancel (notifications/cancelled) -> scope swallows, no response written

dctx.close() runs in an inner finally so the back-channel shuts the moment the
handler exits. _write_result/_write_error swallow Broken/ClosedResourceError so
a dropped connection during the response write doesn't crash the dispatcher.

All 22 contract tests now pass against both DirectDispatcher and
JSONRPCDispatcher; chunk-c xfail markers removed.
2026-04-16 21:43:14 +00:00
Max Isbey 136a22d9b7 feat: JSONRPCDispatcher receive loop and dispatch (chunk b)
run() drives the receive loop in a per-request task group;
task_status.started() fires once send_request is usable. _dispatch routes each
inbound message synchronously (no awaits — send_nowait/_spawn only) to avoid
head-of-line blocking. _spawn propagates the sender's contextvars via
Context.run(tg.start_soon, ...) so auth/OTel set by ASGI middleware survive.
_fan_out_closed wakes pending send_request waiters with CONNECTION_CLOSED on
shutdown (called both post-EOF and in finally; idempotent).

Wire-param extraction (progressToken, cancelled.requestId, progress fields)
uses structural match patterns — runtime narrowing, no casts, no mcp.types
model coupling; malformed input fails to match and the correlation is skipped.

_handle_request is happy-path only here (run on_request, write response); the
exception-to-wire boundary lands in the next commit.

Dispatcher.run() Protocol gained a task_status kwarg (it's a contract-level
guarantee). DirectDispatcher.run() updated to match. running_pair now uses
tg.start so the test body runs only once the dispatcher is ready.

20 contract tests pass; the 2 needing the exception boundary are strict-xfail.
2026-04-16 21:43:14 +00:00
Max Isbey d84f82aa9c feat: JSONRPCDispatcher outbound side + parametrized contract tests
Chunk (a) of JSONRPCDispatcher: constructor, _Pending/_InFlight/_JSONRPCDispatchContext,
send_request/notify and helpers. run() is stubbed.

The Dispatcher contract tests are now parametrized over a pair_factory fixture
(direct + jsonrpc). The 9 jsonrpc cases are strict-xfail until run()/
_handle_request land in the next commits; once those pass, strict xfail flips
to XPASS and forces removal of the marker.

Factories return (client, server, close) so running_pair can shut down any
implementation uniformly.
2026-04-16 21:41:31 +00:00
Max Isbey bfb5a77127 refactor: rename Outbound.send_request to send_raw_request
The dispatcher-layer raw channel is now `send_raw_request(method, params) ->
dict`. This frees the `send_request` name for the typed surface
(`send_request(req: Request) -> Result`) that Connection/Context/Client add
in later PRs.

Mechanical rename across Outbound, Dispatcher, DispatchContext,
DirectDispatcher, _DirectDispatchContext, and all tests. `can_send_request`
(the transport capability flag) is unchanged — it names the capability, not
the method.
2026-04-16 21:38:32 +00:00
Max Isbey 1da25ec182 refactor: rename Dispatcher.call to send_request, replace RequestSender with Outbound
The design doc's `send_request = call` alias only makes the concrete class
satisfy RequestSender, not the abstract Dispatcher Protocol — so any consumer
typed against `Dispatcher[TT]` (Connection, ServerRunner) couldn't pass it to
something expecting a RequestSender without a cast or hand-written bridge.

RequestSender was also half a contract: every implementor (Dispatcher,
DispatchContext, Connection, Context) has `notify` too, and PeerMixin needs
both for its typed sugar (elicit/sample are requests, log is a notification).

Outbound(Protocol) declares both methods; Dispatcher and DispatchContext extend
it. PeerMixin will wrap an Outbound. One verb everywhere, no aliases, no extra
Protocols.

- Dispatcher.call -> send_request
- OnCall -> OnRequest, on_call -> on_request
- RequestSender -> Outbound (now also declares notify)
- Dispatcher(Outbound, Protocol[TT]), DispatchContext(Outbound, Protocol[TT])
2026-04-16 12:52:58 +00:00
Max Isbey 5540d807be fix: address coverage gaps and stale RequestSender docstring
- tests: replace unreachable 'return {}' with 'raise NotImplementedError'
  (already in coverage exclude_also) and collapse send_request+return into
  one statement
- dispatcher: RequestSender docstring no longer claims Dispatcher satisfies it
  (Dispatcher exposes call(), not send_request())
2026-04-16 12:03:48 +00:00
Max Isbey f332c596aa feat: add Dispatcher Protocol and DirectDispatcher
Introduces the Dispatcher abstraction that decouples MCP request/response
handling from JSON-RPC framing. A Dispatcher exposes call/notify for outbound
messages and run(on_call, on_notify) for inbound dispatch, with no knowledge
of MCP types or wire encoding.

- shared/dispatcher.py: Dispatcher, DispatchContext, RequestSender Protocols;
  CallOptions, OnCall/OnNotify, ProgressFnT, DispatchMiddleware
- shared/transport_context.py: TransportContext base dataclass
- shared/direct_dispatcher.py: in-memory Dispatcher impl that wires two peers
  with no transport; serves as a fast test substrate and second-impl proof
- shared/exceptions.py: NoBackChannelError(MCPError) for transports without a
  server-to-client request channel
- types: REQUEST_CANCELLED SDK error code

The JSON-RPC implementation and ServerRunner that consume this Protocol land
in follow-up PRs.
2026-04-16 11:56:49 +00:00
Felix Weinberger 2dfb51a4d1 fix(auth): coerce empty-string optional URL fields to None in OAuthClientMetadata (#2404) 2026-04-13 15:42:35 +01:00
Marcelo Trylesinski 37891f42a4 Add basic OpenTelemetry tracing for client and server requests (#2381) 2026-03-31 20:33:33 +00:00
Marcelo Trylesinski e6235d1667 Propagate contextvars.Context through anyio streams without modifying SessionMessage (#2298) 2026-03-31 12:49:38 -04:00
Max Isbey 67201a9bbd test: fix WS test port race; narrow to single smoke test covering both transport ends (#2267) 2026-03-18 15:48:30 +00:00
Max Isbey e1fd62e0f3 fix: close all memory stream ends in client transport cleanup (#2266) 2026-03-13 14:43:54 +00:00
Shivam Aggarwal 51c53f2c18 fix: accept wildcard media types in Accept header per RFC 7231 (#2152)
Co-authored-by: Shivam <shivam@Shivams-MacBook-Air-2.local>
2026-03-09 16:30:02 +00:00
Max Isbey 528abfab86 tests: remove lax-no-cover pragmas by moving assertions before cancellation (#2206) 2026-03-04 16:11:34 +00:00
Varun6578 b3149d2f33 fix: clean up SSE session on client disconnect (#2200)
Co-authored-by: Varun Sharma <sharmava@microsoft.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Max Isbey <224885523+maxisbey@users.noreply.github.com>
2026-03-04 14:45:11 +00:00
Max Isbey e82203bfc4 refactor: remove unused mcp.shared.progress module (#2080) 2026-02-18 13:10:02 +00:00
Felix Weinberger be5bb7c4f2 fix: normalize trailing slashes before length check in check_resource_allowed (#2074) 2026-02-17 14:34:59 +00:00
Max Isbey 705497a593 fix: allow null id in JSONRPCError per JSON-RPC 2.0 spec (#2056) 2026-02-17 10:30:34 +00:00
BabyChrist666 3b53fb9a00 fix: add HTTP readiness check to wait_for_server and remove dead code in SSE tests (#2073) 2026-02-17 08:34:47 +01:00
Max Isbey 0a22a9dc33 refactor: replace lowlevel Server decorators with on_* constructor kwargs (#1985) 2026-02-12 15:55:54 +00:00
Aaron Abbott f049c8e5c6 Fix leaked anyio streams in streamable_http (#1991)
Co-authored-by: Marcelo Trylesinski <marcelotryle@gmail.com>
2026-02-11 14:52:07 +00:00
Marcelo Trylesinski 1a8c14a5b8 refactor: replace mcp.types as types to from mcp import types (#1986) 2026-02-03 16:42:29 +00:00
Marcelo Trylesinski 4fc49c62bd feat: add ClientRequestContext type alias for client-side handlers (#1989) 2026-02-03 17:37:38 +01:00
Marcelo Trylesinski b1f7eec3cd refactor: split RequestContext between server and client (#1987) 2026-02-03 14:35:07 +01:00
Marcelo Trylesinski 21822053df Support different transports in Client (#1972) 2026-01-30 12:11:27 +00:00
Marcelo Trylesinski acba5478a9 refactor: McpError renamed to MCPError and flatten parameters (#1956)
Co-authored-by: Max Isbey <224885523+maxisbey@users.noreply.github.com>
2026-01-26 14:37:44 +01:00
Max Isbey a7ddfdae07 ci: add strict-no-cover to detect unnecessary coverage pragmas (#1897) 2026-01-23 21:00:20 +01:00
Marcelo Trylesinski f0ab53e194 Add meta to Client methods (#1923) 2026-01-22 14:50:39 +01:00
Marcelo Trylesinski d77292fb06 refactor: drop test classes (#1924) 2026-01-22 12:37:52 +01:00
Marcelo Trylesinski 5fdd48a0d3 Completely drop RootModel from types module (#1910) 2026-01-19 14:29:15 +01:00
Marcelo Trylesinski f4672c5084 Drop RootModel from JSONRPCMessage (#1908) 2026-01-19 14:04:15 +01:00
Felix Weinberger dcc9b4f8e0 refactor: use Client class in tests (#1900) 2026-01-17 09:46:58 +01:00
Felix Weinberger d41d0c0128 chore: add D212 lint rule to enforce Google-style docstrings (#1892) 2026-01-16 16:10:52 +00:00
Felix Weinberger df039bf97c Add ergonomic Client class for testing MCP servers (#1870) 2026-01-16 15:49:26 +00:00
Max Isbey 5d80f4efc8 refactor: move inline imports to module level (#1893) 2026-01-16 14:54:08 +00:00
Marcelo Trylesinski e94b386a13 refactor: use snake case instead of camel case in types (#1894) 2026-01-16 15:51:27 +01:00
Felix Weinberger cfb2909631 fix: change Resource URI fields from AnyUrl to str (#1863) 2026-01-16 09:58:57 +01:00
Marcelo Trylesinski 8893b022e8 Drop deprecated streamablehttp_client (#1836) 2026-01-15 11:02:24 +01:00
Marcelo Trylesinski 3ffe142e9a Support Python 3.14 (#1834) 2026-01-07 16:28:23 +00:00
Marcelo Trylesinski 6149b63a44 tests: add missing init files (#1831) 2026-01-06 19:52:09 +01:00