Repository navigation
fix(server): refuse includeContext thisServer/allServers without sampling.context - #3064
Conversation
…ling.context The 2025-11-25 spec soft-deprecates includeContext 'thisServer' and 'allServers': servers SHOULD only use them when the client declares ClientCapabilities.sampling.context. Server.createMessage already refuses tools/toolChoice to a client without sampling.tools, but forwarded includeContext unchecked. It now throws CapabilityNotSupported before sending, on connections negotiated at 2025-11-25 or later. Older revisions have no sampling.context capability, so they are not gated. Removes the e2e knownFailure for sampling:context:server-gated-by-capability and has the include-context passthrough client declare sampling.context.
🦋 Changeset detectedLatest commit: b7dea7f The changes in this PR will be included in the next version bump. This PR includes changesets to release 6 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/core
@modelcontextprotocol/server
@modelcontextprotocol/server-legacy
@modelcontextprotocol/express
@modelcontextprotocol/fastify
@modelcontextprotocol/hono
@modelcontextprotocol/node
commit: |
|
Read the diff (suite not run). The version gate |
Created by Claude Opus-5-5: the code, tests and this description were written entirely by Claude Opus-5-5. Any reply to review comments that is written by AI will say so.
No GitHub issue: this fixes the e2e knownFailure
sampling:context:server-gated-by-capabilityintest/e2e/requirements.ts. It is a few-line bug fix with tests, which CONTRIBUTING lets skip the issue step.What was broken
In the 2025-11-25 spec, the
includeContextvaluesthisServerandallServersare soft-deprecated, and servers SHOULD only use them when the client declaresClientCapabilities.sampling.context(spec: client/sampling#capabilities, and the JSDoc onCreateMessageRequestParams.includeContext/ClientCapabilities.sampling.context).Server.createMessage()refusestools/toolChoicewhen the client did not declaresampling.tools, but it forwardedincludeContextunchecked. So a server could sendincludeContext: 'thisServer'to a client that declared onlysampling: {}, and the request reached the client's sampling handler. The e2e manifest recorded this as a knownFailure on all sixinMemory/stdio/streamableHttpcells.Fix
Server.createMessage()now throwsSdkError(SdkErrorCode.CapabilityNotSupported, 'Client does not support sampling context capability.')before sending, whenincludeContextisthisServerorallServersand the client did not declaresampling.context. This mirrors the existingsampling.toolscheck right above it.'none'and an omittedincludeContextare unaffected.The check is skipped when the connection negotiated a revision before 2025-11-25 (
2025-06-18and earlier). Those revisions have nosampling.contextcapability, so a client there cannot declare it, and under those revisionsincludeContextneeds onlysampling. Gating them would break servers that are conformant for the revision they negotiated.The version check is a plain string compare against
'2025-11-25'. That is safe here becauseServer._negotiatedProtocolVersionis never a raw peer string. Theinitializehandler sets it to the client's requested version only when that version is in this server's own legacy supported-versions list, and otherwise to the first entry of that list (orLATEST_PROTOCOL_VERSION).connect()sets it only to a modern revision that the SDK implements and this instance lists. Revision identifiers are ISO dates, so string order is date order;isModernProtocolVersion()incore-internal/src/shared/protocolEras.tsrelies on the same property. On 2026-07-28-era requests,createMessagealready throws before this point (_assertPushApiInServedEra).ctx.mcpReq.requestSampling()and the legacyinputRequiredshim's sampling leg both callcreateMessage(), so they get the same refusal. On the shim, it surfaces as the shim's usual fulfilment failure.Other SDKs: python-sdk documents the rule in the
ServerSession.create_messagedocstring but does not enforce it. A code search forIncludeContextin the C# and Go SDKs found no such check. This PR follows the behaviour the e2e manifest already specifies for this SDK.Behaviour change (stated in the changeset): a server that sends
thisServer/allServerson a 2025-11-25 connection to a client withoutsampling.contextnow gets an error, and must omitincludeContextor pass'none'. The changeset is a patch: VERSIONING.md lists "bug fixes that correct behavior to match documented intent" as not breaking, and this makes the SDK follow the spec's SHOULD and the e2e requirement.Out of scope, possible follow-ups:
requiredClientCapabilitiesForInputRequestin core-internal) checkssampling.toolsbut notsampling.contexteither.v1.xbackport (same gap insrc/server/index.ts, same knownFailure).Open PR #3049 edits the same
createMessage()block, so whichever of the two merges second will need a small rebase.Changes
packages/server/src/server/server.ts: the capability check increateMessage().test/e2e/requirements.ts: removed the knownFailure forsampling:context:server-gated-by-capability.test/e2e/scenarios/sampling.test.ts: thesampling:create:include-contextpassthrough client now declaressampling: { context: {} }, because it sendsthisServer/allServers.test/integration/test/server.test.ts: added a test thatthisServerandallServersare rejected withoutsampling.context, and a test thatthisServerstill passes through on a2025-06-18connection..changeset/sampling-context-capability-gate.md: a patch changeset for@modelcontextprotocol/server.How I tested it
With the test changes only, on unmodified source:
cd test/e2e && npx vitest run scenarios/sampling.test.ts -t context:6 failed | 4 passed. Eachsampling:context:server-gated-by-capabilitycell fails withexpected { ok: true, … } to match object { ok: false, … }.cd test/integration && npx vitest run test/server.test.ts -t includeContext: the rejection test fails withpromise resolved "{ model: 'test-model', … }" instead of rejecting.With the fix (after
pnpm --filter @modelcontextprotocol/server build, because e2e importsdist):-t context:10 passed. These are the six gated cells plus the foursampling:create:include-contextcells (inMemory,stdio,streamableHttp,sse).-t includeContext:2 passed.2025-06-18test then failed withSdkError: Client does not support sampling context capability.I reverted that change.pnpm typecheck:allandpnpm lint:allpass.pnpm -r test: server 674/674, integration 446/446, and e2e2699 passed | 135 expected fail. The only failures wereprotocol:timeout:max-total [sse], which fails the same way withmain'sserver.tson my heavily loaded machine, andexamples/shared, whosebetter-sqlite3native binding my local install did not build.