feat(server): complete the stateless call — result stamping, cache hints, and the error status matrix#2306
Merged
felixweinberger merged 8 commits intoJun 16, 2026
Conversation
🦋 Changeset detectedLatest commit: cefb94b The changes in this PR will be included in the next version bump. This PR includes changesets to release 7 packages
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
@modelcontextprotocol/client
@modelcontextprotocol/codemod
@modelcontextprotocol/server
@modelcontextprotocol/server-legacy
@modelcontextprotocol/express
@modelcontextprotocol/fastify
@modelcontextprotocol/hono
@modelcontextprotocol/node
commit: |
@modelcontextprotocol/client
@modelcontextprotocol/codemod
@modelcontextprotocol/server
@modelcontextprotocol/server-legacy
@modelcontextprotocol/express
@modelcontextprotocol/fastify
@modelcontextprotocol/hono
@modelcontextprotocol/node
commit: |
37c10ad to
7a1300e
Compare
Closed
9 tasks
7a1300e to
fbb552a
Compare
…e the ladder status table Adds MissingRequiredClientCapabilityError (-32003) with data.requiredCapabilities, recognized by ProtocolError.fromError from the error code and data shape, plus the shared helpers for computing which required client capabilities a request's declared capabilities are missing (with a method-keyed requirement table for the pre-dispatch gate, currently empty). The ladder status table gains explicit parse-error and invalid-request rows; handler-originated codes keep answering in-band on HTTP 200 and invalid params stays unmapped (the envelope rung carries its own 400).
…ode seam The 2026-era codec's encodeResult now applies the outbound encode contract as two pure steps: the resultType stamp (handler-provided values pass through only for the multi round-trip methods whose result vocabulary goes beyond 'complete'; a stray non-complete value elsewhere fails loudly) followed by the cache fill, which gives complete results of the six cacheable operations the revision's required ttlMs and cacheScope fields. Values are resolved most specific author first: valid handler-returned fields, then a configured cache hint attached by the server layer through a never-serialized symbol carrier, then the conservative defaults (ttlMs 0, cacheScope private). The 2025-era codec remains the identity, and a suppression suite pins what is never stamped: legacy traffic, input_required results, non-cacheable operations, era-removed methods, client-emitted requests, and error responses.
…ults Adds ServerOptions.cacheHints (per-operation hints for the results the SDK builds itself, including server/discover and the list operations) and an optional cacheHint member on the registerResource config (per-resource hints for resources/read). Configured hints are validated when configured (RangeError on an invalid ttlMs or cacheScope) and are attached to results on a symbol-keyed carrier that only the 2026-era encode seam reads, so cache fields returned by a handler always win and 2025-era responses never change. CacheHint/CacheScope are exported from the server package.
…fore dispatch createMcpHandler now checks each modern request against the method-keyed client capability requirement table before constructing a per-request instance: when the request's declared clientCapabilities are missing a required capability, the entry answers the typed -32003 error with data.requiredCapabilities and HTTP 400, echoing the request id. Emitting at the entry (rather than at handler time) pins the spec-mandated 400 independently of how dispatch- and handler-produced errors are mapped to HTTP statuses. No method served on the 2026-07-28 registry has a static requirement yet, so production behavior is unchanged until such methods exist.
…ng-capability error Migration-guide sections and changesets for the new ServerOptions.cacheHints / registerResource cacheHint options, the always-present ttlMs/cacheScope fields on cacheable 2026-07-28 results, and MissingRequiredClientCapabilityError.
…y-rung metadata - isValidCacheTtlMs now requires a safe integer: the wire schemas validate ttlMs as an integer within the safe range, so a handler-returned value like 1e20 was emitted only to fail wire validation downstream. Such values now fall through to the configured hint / defaults like other invalid values, and the configuration-time RangeError covers them too. The validity-gate tests gain the unsafe-integer, NaN and Infinity cases. - The validation ladder's client-capabilities rung descriptor said evaluatedAt: 'dispatch' while the implemented gate runs at the HTTP entry, pre-dispatch. The descriptor now uses a dedicated 'pre-dispatch' value and its rationale qualifies the documented precedence: with the requirement table empty the order is preserved vacuously; once a served method gains a requirement entry, the entry must consult the method registry before the gate for the documented order to stay observable. Data/doc only — the gate itself is unchanged. - One JSDoc sentence on the resultType pass-through row: non-'complete' strings returned by handlers of the multi round-trip methods are forwarded verbatim, so their validity is the handler author's responsibility.
- ServerOptions.cacheHints re-spelled the six cacheable operations as a string-literal union; it is now keyed by CacheableResultMethod so the closed operation list has a single source of truth (the option's JSDoc still names the operations). No new exports; the accepted keys are unchanged. - Add per-operation cache-hint coverage for prompts/list, resources/list and resources/templates/list, completing the op-level surface (tools/list, resources/read and server/discover were already covered). - Changeset wording: note that registerResource now interprets a cacheHint key in its config object (observable for untyped callers), and clarify that the -32003 pre-dispatch gate changes no behavior until a served method actually carries a capability requirement.
…onfigured authors - attachCacheHintFallback previously kept an already-attached per-resource hint as a whole object, so a partial registerResource cacheHint (say only cacheScope) shadowed the per-operation ServerOptions.cacheHints entry entirely and its ttlMs fell back to the default 0. The two configured hints are now combined per field: for each of ttlMs and cacheScope the per-resource value wins when set and the per-operation value fills the fields it leaves unset, with the defaults only applying to fields neither author sets. Handler-returned values, the encode-time validity gate, the configuration-time RangeError and the defaults are unchanged. - Tests: add the field-mixing cases (per-resource cacheScope with per-operation ttlMs and the reverse, both authors setting the same fields, a field neither sets, resources/read with no configuration at all) to the cache-hint suite, plus a unit-level per-field merge case in the encode-contract suite. No existing test cases changed; the cache-hint suite's header comment now says the precedence chain resolves per field. - Docs: the ServerOptions.cacheHints JSDoc, migration.md and the cacheable result changeset now state that resolution is per field, most specific author first.
b37a565 to
cefb94b
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Completes the modern (2026-07-28) stateless call on top of the per-request serving entry: results of the cacheable operations are stamped and filled with the revision's required cache fields, servers can configure those values through new cache-hint options, the error→HTTP status table gains its remaining rows, and the
-32003missing-client-capability error ships as a typed class with a pre-dispatch gate at the HTTP entry.Motivation and Context
The 2026-07-28 draft requires every result to carry a
resultTypediscriminator and requiresttlMs/cacheScopeon the cacheable results (tools/list,prompts/list,resources/list,resources/templates/list,resources/read,server/discover). The serving core landed in the previous PRs of this stack; this PR makes the responses it produces spec-complete and gives servers a way to express a real cache policy, while keeping 2025-era responses byte-identical.encodeResultnow stampsresultType(handler-provided values pass through only for the multi round-trip methods, whose results may beinput_required; a stray non-completevalue elsewhere fails loudly as an internal error) and then fillsttlMs/cacheScopeon complete results of the cacheable operations — valid handler-returned values first, then the configured hint, thenttlMs: 0/cacheScope: 'private'. The 2025-era codec remains the identity; a suppression test suite pins everything that must never be stamped (legacy traffic,input_requiredresults, non-cacheable operations, era-removed methods, client-emitted requests, error responses), with consumer-authored cache fields on 2025-era results passing through unchanged.ServerOptions.cacheHints(per operation, for the results the SDK builds itself) andregisterResource(..., { cacheHint })(per resource). Optional, validated at configuration time (RangeError), and carried to the encode seam on a never-serialized property so 2025-era responses cannot change.MissingRequiredClientCapabilityError(-32003): typed error class withdata.requiredCapabilities, recognized from the error code and data shape.createMcpHandlerrefuses a request that requires a client capability missing from the request's declaredclientCapabilitiesbefore dispatch, with HTTP status 400. No method served on the 2026-07-28 registry has a static requirement yet, so production behavior is unchanged until such methods exist; the gate, error shape and status are pinned by tests.How Has This Been Tested?
Breaking Changes
None. All new options and exports are additive and optional; 2026-07-28 responses gain the fields the draft requires, and 2025-era responses are unchanged (verified by the negative-vocabulary suites and the unchanged e2e/conformance baselines).
Types of changes
Checklist
Additional context