Commit Graph

5 Commits

Author SHA1 Message Date
Max Isbey c53aefd293 Close cancelled HTTP exchanges and harden auth validation
Review-feedback round on the conformance burn-down:

- Cancelled requests no longer leave the legacy streamable-HTTP POST
  hanging. The dispatcher emits a RequestSettled marker when a handler is
  cancelled without producing a response; the transport consumes it by
  closing the per-request stream, so the POST's SSE stream terminates
  without a response frame and JSON-response mode completes with 204 No
  Content (the client treats 202/204 alike). Per-request streams are
  released instead of leaking until session teardown, and a handler that
  survives the cancellation still delivers its normal response. The
  marker is type-visible on the dispatcher write stream and is stripped
  by every serializing transport, so it can never appear on a wire.
- A bearer token whose audience cannot be canonicalized (out-of-range or
  non-numeric port) is now rejected with the standard 401 invalid_token
  instead of raising through the auth middleware as a 500.
- The bundled authorization server's /register now accepts only https
  redirect URIs or http on a loopback host; other schemes on loopback
  hosts (ftp, ws, javascript, custom) are rejected.
- OAuth client scope selection falls back to the caller-configured
  OAuthClientMetadata.scope when neither the WWW-Authenticate challenge
  nor protected-resource metadata names scopes, matching the TypeScript
  SDK, so the documented migration path works as written.
- The cross-dispatcher contract that handler-raised MCPError subclasses
  surface to callers as plain MCPError is now pinned by an explicit test
  and documented; rehydrate with from_error when the subclass matters.
- Docs: migration notes for the bearer-challenge wire-shape changes and
  the cancellation wire spellings; story READMEs updated to the landed
  error contract; strict-capabilities doc corrected to state that
  resources/unsubscribe is gated by the base resources capability only.
2026-06-28 11:30:26 +00:00
Max Isbey 28f500c71a Add an opt-in strict_capabilities flag to Client and ClientSession
Client(..., strict_capabilities=True) rejects, before any request reaches
the transport, a call to a method whose required server capability the
connected server did not advertise -- for example list_resources() against
a server that only advertised tools, or subscribe_resource() when the
server's resources capability does not set subscribe. The rejection is an
MCPError with code -32601 (METHOD_NOT_FOUND) and data set to the method,
the same shape a compliant server returns for an unadvertised capability,
so opting in changes where the rejection happens, not what callers catch.

The default is False and unchanged: every request is sent and the server's
answer is surfaced. This mirrors the TypeScript SDK's
enforceStrictCapabilities option (also default-off). The same keyword-only
parameter exists on ClientSession for low-level users; Client forwards it.

The method-to-capability table lives in
mcp_types.methods.SERVER_CAPABILITY_REQUIREMENTS next to the other
per-method maps, with missing_server_capability() as its only evaluator,
so the check in ClientSession.send_request is a single data-driven gate
rather than a per-method condition, and the relationship is the same at
every protocol version. Because the gate reads server_capabilities, a bare
version pin (mode="2026-07-28" with no prior_discover=) would reject every
gated method; that combination is refused at Client construction with a
ValueError that names the fix.

The interaction-requirements entry for the lifecycle capability rule is no
longer marked untested: the new tests pin both the opt-in pre-wire
rejection and the default send-and-surface behaviour.
2026-06-27 18:53:13 +00:00
Max Isbey 884badc944 Use spec error codes for unhandled elicitation/create and roots/list
A client constructed without an elicitation_callback or
list_roots_callback still answers the server's request, via a default
callback that returns a JSON-RPC error. Both defaults used -32600
(invalid request). The spec assigns a specific code to each case:

- elicitation/create: -32602 (invalid params). A client with no
  callback declares no elicitation modes, so every incoming request
  names an undeclared mode, which clients MUST answer with -32602.
- roots/list: -32601 (method not found), the code clients SHOULD use
  when they do not support roots.

The default sampling callback keeps -32600: the spec assigns no code
to a client that does not support sampling. Error messages are
unchanged.

Update the affected tests and the interaction-requirement entries that
recorded the old codes, fix the code named in the client callbacks doc
page, and add a migration note.
2026-06-27 18:53:13 +00:00
Max 08b62308d4 Client auto-resolves InputRequiredResult via existing callbacks (SEP-2322) (#2998) 2026-06-26 17:35:23 +02:00
Max 411a6d3980 Rebuild the docs around tested examples; shrink README.v2.md to a pitch (#2978) 2026-06-26 12:49:19 +02:00