Repository navigation
mcp: include resultType on new-protocol responses - #1060
Merged
guglielmo-san merged 5 commits intoJul 13, 2026
Merged
Conversation
Wrap non-MRTR server result objects for 2026-07-28 requests so the wire response includes resultType without changing direct marshaling of public result structs. Fixes modelcontextprotocol#1032 Constraint: Preserve existing public result struct shape and direct JSON output for callers. Tested: go test ./mcp -run 'Test(ServerSessionHandle|CompleteResult|ClientConnectDiscover|InMemory_E2E_Discover)' -count=1; go test ./mcp -count=1; go test ./...
ychampion
force-pushed
the
fix-2026-result-type
branch
from
July 9, 2026 10:36
ee4b0cf to
95e8252
Compare
Constraint: MRTR responses must retain input_required instead of receiving the generic complete discriminator. Tested: go test ./mcp -run TestServerSessionHandle_SetsResultTypeOnNewProtocol -count=1; go test ./...; go vet ./...
guglielmo-san
approved these changes
Jul 10, 2026
guglielmo-san
enabled auto-merge (squash)
July 13, 2026 11:50
guglielmo-san
added a commit
that referenced
this pull request
Jul 13, 2026
This PR adds the resultType in the troubleshoot doc after PR #1060
This was referenced Sep 1, 2026
roncodingenthusiast
added a commit
to roncodingenthusiast/go-sdk
that referenced
this pull request
Sep 1, 2026
Protocol revision 2026-07-28 requires resultType on every result. CallToolResult, GetPromptResult and ReadResourceResult hold it in an unexported field, since they can be complete or input_required and so cannot embed completeResultWithType as the seven types in modelcontextprotocol#1060 do. Only handleMultiRoundTripResult writes that field, and it runs inside Server.callTool, getPrompt and readResource. A receiving middleware that returns a result instead of calling next -- an auth, rate limit or entitlement gate -- never reaches it, so the response ships without the field and strict clients reject it as malformed. Move the choice of complete versus input_required into setMultiRoundTripResultType, and call it from both places that need it. setCompleteResultType, which now labels results it did not before, is renamed annotateResultType and picks a branch by type switch. It reads inputRequests() rather than assuming complete, which matters because setResultType is unexported: populating InputRequests is a middleware's only way to ask for input, and labeling it complete would make a conforming client drop the elicitation. Recomputing is safe because a result that did reach the dispatcher was labeled from the same test. ServerSession.handle labeled only when the request opted in through _meta, while handleMultiRoundTripResult tests the session's negotiated version, which left the same bug open in the mirror direction. Both sites now call clientSupportsMultiRoundTrip so they cannot drift apart. annotateServerInfo keeps its existing _meta-only gate. Embedding completeResultWithType instead would label every result complete and clobber input_required, breaking multi-round-trip flows. Also correct ten comments naming an exported ResultType field and ResultTypeComplete/ResultTypeInputRequired constants, none of which exist. Two were unresolvable [CallToolResult.ResultType] doc links and now point at [CallToolResult.NeedsInput]. The exported API is unchanged and the conformance goldens do not move. Fixes modelcontextprotocol#1225 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
roncodingenthusiast
added a commit
to roncodingenthusiast/go-sdk
that referenced
this pull request
Sep 1, 2026
Protocol revision 2026-07-28 requires resultType on every result. CallToolResult, GetPromptResult and ReadResourceResult hold it in an unexported field, since they can be complete or input_required and so cannot embed completeResultWithType as the seven types in modelcontextprotocol#1060 do. Only handleMultiRoundTripResult writes that field, and it runs inside Server.callTool, getPrompt and readResource. A receiving middleware that returns a result instead of calling next -- an auth, rate limit or entitlement gate -- never reaches it, so the response ships without the field and strict clients reject it as malformed. Move the choice of complete versus input_required into setMultiRoundTripResultType, and call it from both places that need it. setCompleteResultType, which now labels results it did not before, is renamed annotateResultType and picks a branch by type switch. It reads inputRequests() rather than assuming complete, which matters because setResultType is unexported: populating InputRequests is a middleware's only way to ask for input, and labeling it complete would make a conforming client drop the elicitation. Recomputing is safe because a result that did reach the dispatcher was labeled from the same test. ServerSession.handle labeled only when the request opted in through _meta, while handleMultiRoundTripResult tests the session's negotiated version, which left the same bug open in the mirror direction. handle now tests usesNewProtocol or clientSupportsMultiRoundTrip, a superset of the dispatcher's test, so it can never label less than the dispatcher does. It does label more: a session that negotiated 2026-07-28 and sends no _meta now gets resultType where it did not before. annotateServerInfo keeps its existing _meta-only gate. Embedding completeResultWithType instead would label every result complete and clobber input_required, breaking multi-round-trip flows. Also correct ten comments naming an exported ResultType field and ResultTypeComplete/ResultTypeInputRequired constants, none of which exist. Two were unresolvable [CallToolResult.ResultType] doc links and now point at [CallToolResult.NeedsInput]. The exported API is unchanged and the conformance goldens do not move. Fixes modelcontextprotocol#1225 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
roncodingenthusiast
added a commit
to roncodingenthusiast/go-sdk
that referenced
this pull request
Sep 1, 2026
Protocol revision 2026-07-28 requires resultType on every result. CallToolResult, GetPromptResult and ReadResourceResult hold it in an unexported field, since they can be complete or input_required and so cannot embed completeResultWithType as the seven types in modelcontextprotocol#1060 do. Only handleMultiRoundTripResult writes that field, and it runs inside Server.callTool, getPrompt and readResource. A receiving middleware that returns a result instead of calling next -- an auth, rate limit or entitlement gate -- never reaches it, so the response ships without the field and strict clients reject it as malformed. Move the choice of complete versus input_required into setMultiRoundTripResultType, and call it from both places that need it. setCompleteResultType, which now labels results it did not before, is renamed annotateResultType and picks a branch by type switch. It reads inputRequests() rather than assuming complete, which matters because setResultType is unexported: populating InputRequests is a middleware's only way to ask for input, and labeling it complete would make a conforming client drop the elicitation. Recomputing is safe because a result that did reach the dispatcher was labeled from the same test. ServerSession.handle labeled only when the request opted in through _meta, while handleMultiRoundTripResult tests the session's negotiated version, which left the same bug open in the mirror direction. handle now tests usesNewProtocol or clientSupportsMultiRoundTrip, a superset of the dispatcher's test, so it can never label less than the dispatcher does. It does label more: a session that negotiated 2026-07-28 and sends no _meta now gets resultType where it did not before. annotateServerInfo keeps its existing _meta-only gate. Embedding completeResultWithType instead would label every result complete and clobber input_required, breaking multi-round-trip flows. Also correct ten comments naming an exported ResultType field and ResultTypeComplete/ResultTypeInputRequired constants, none of which exist. Two were unresolvable [CallToolResult.ResultType] doc links and now point at [CallToolResult.NeedsInput]. The exported API is unchanged and the conformance goldens do not move. Fixes modelcontextprotocol#1225 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
roncodingenthusiast
added a commit
to roncodingenthusiast/go-sdk
that referenced
this pull request
Sep 2, 2026
Protocol revision 2026-07-28 requires resultType on every result. CallToolResult, GetPromptResult and ReadResourceResult hold it in an unexported field, since they can be complete or input_required and so cannot embed completeResultWithType as the seven types in modelcontextprotocol#1060 do. Only handleMultiRoundTripResult writes that field, and it runs inside Server.callTool, getPrompt and readResource. A receiving middleware that returns a result instead of calling next -- an auth, rate limit or entitlement gate -- never reaches it, so the response ships without the field and strict clients reject it as malformed. Move the choice of complete versus input_required into setMultiRoundTripResultType, and call it from both places that need it. setCompleteResultType, which now labels results it did not before, is renamed annotateResultType and picks a branch by type switch. It reads inputRequests() rather than assuming complete, which matters because setResultType is unexported: populating InputRequests is a middleware's only way to ask for input, and labeling it complete would make a conforming client drop the elicitation. Recomputing is safe because a result that did reach the dispatcher was labeled from the same test. Embedding completeResultWithType instead would label every result complete and clobber input_required, breaking multi-round-trip flows. ServerSession.handle still gates on the request's _meta while handleMultiRoundTripResult gates on the session's negotiated version, so a session that negotiated 2026-07-28 and sends no _meta gets no resultType from a short-circuited request. Only a client that mixes protocol versions can reach that, so TestServerSessionHandle_ResultTypeGate records it rather than changing behavior to cover it. Also correct ten comments naming an exported ResultType field and ResultTypeComplete/ResultTypeInputRequired constants, none of which exist. Two were unresolvable [CallToolResult.ResultType] doc links and now point at [CallToolResult.NeedsInput]. The exported API is unchanged and the conformance goldens do not move. Fixes modelcontextprotocol#1225 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
roncodingenthusiast
added a commit
to roncodingenthusiast/go-sdk
that referenced
this pull request
Sep 2, 2026
Protocol revision 2026-07-28 requires resultType on every result. CallToolResult, GetPromptResult and ReadResourceResult hold it in an unexported field, since they can be complete or input_required and so cannot embed completeResultWithType as the seven types in modelcontextprotocol#1060 do. Only handleMultiRoundTripResult writes that field, and it runs inside Server.callTool, getPrompt and readResource. A receiving middleware that returns a result instead of calling next -- an auth, rate limit or entitlement gate -- never reaches it, so the response ships without the field and strict clients reject it as malformed. Move the choice of complete versus input_required into setMultiRoundTripResultType, and call it from both places that need it. setCompleteResultType, which now labels results it did not before, is renamed annotateResultType and picks a branch by type switch. It reads inputRequests() rather than assuming complete, which matters because setResultType is unexported: populating InputRequests is a middleware's only way to ask for input, and labeling it complete would make a conforming client drop the elicitation. Recomputing is safe because a result that did reach the dispatcher was labeled from the same test. Embedding completeResultWithType instead would label every result complete and clobber input_required, breaking multi-round-trip flows. A middleware can also return a typed nil, which satisfies Result, so ServerSession.handle now skips annotation for it. That case panicked before this change too, one frame further on in annotateServerInfo. ServerSession.handle still gates on the request's _meta while handleMultiRoundTripResult gates on the session's negotiated version, so a session that negotiated 2026-07-28 and sends no _meta gets no resultType from a short-circuited request. Only a client that mixes protocol versions can reach that, so TestServerSessionHandle_ResultTypeGate records it rather than changing behavior to cover it. Also correct ten comments naming an exported ResultType field and ResultTypeComplete/ResultTypeInputRequired constants, none of which exist. Two were unresolvable [CallToolResult.ResultType] doc links and now point at [CallToolResult.NeedsInput]. The exported API is unchanged and the conformance goldens do not move. Fixes modelcontextprotocol#1225 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
roncodingenthusiast
added a commit
to roncodingenthusiast/go-sdk
that referenced
this pull request
Sep 2, 2026
Protocol revision 2026-07-28 requires resultType on every result. CallToolResult, GetPromptResult and ReadResourceResult hold it in an unexported field, since they can be complete or input_required and so cannot embed completeResultWithType as the seven types in modelcontextprotocol#1060 do. Only handleMultiRoundTripResult writes that field, and it runs inside Server.callTool, getPrompt and readResource. A receiving middleware that returns a result instead of calling next -- an auth, rate limit or entitlement gate -- never reaches it, so the response ships without the field and strict clients reject it as malformed. Move the choice of complete versus input_required into setMultiRoundTripResultType, and call it from both places that need it. setCompleteResultType, which now labels results it did not before, is renamed annotateResultType and picks a branch by type switch. It reads inputRequests() rather than assuming complete, which matters because setResultType is unexported: populating InputRequests is a middleware's only way to ask for input, and labeling it complete would make a conforming client drop the elicitation. Recomputing is safe because a result that did reach the dispatcher was labeled from the same test. Embedding completeResultWithType instead would label every result complete and clobber input_required, breaking multi-round-trip flows. A middleware can also return a typed nil, which satisfies Result, and annotating one panics. That panic predates this change; it happened one frame further on, in annotateServerInfo. Result gains isNil, matching what Params already does, and ServerSession.handle skips annotation for a nil result. Result is sealed by isResult, so adding an unexported method to it is not a breaking change. ServerSession.handle still gates on the request's _meta while handleMultiRoundTripResult gates on the session's negotiated version, so a session that negotiated 2026-07-28 and sends no _meta gets no resultType from a short-circuited request. Only a client that mixes protocol versions can reach that, so TestServerSessionHandle_ResultTypeGate records it rather than changing behavior to cover it. Also correct ten comments naming an exported ResultType field and ResultTypeComplete/ResultTypeInputRequired constants, none of which exist. Two were unresolvable [CallToolResult.ResultType] doc links and now point at [CallToolResult.NeedsInput]. The exported API is unchanged and the conformance goldens do not move. Fixes modelcontextprotocol#1225 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
pull Bot
pushed a commit
to ankitm123/go-sdk
that referenced
this pull request
Sep 14, 2026
…textprotocol#1226) Fixes modelcontextprotocol#1225. See the [issue comment](modelcontextprotocol#1225 (comment)). ## The bug `2026-07-28` requires `resultType` on every result. `CallToolResult`, `GetPromptResult`, and `ReadResourceResult` keep `resultType` in an unexported field. Each of these results can be `complete` or `input_required`, so it cannot embed `completeResultWithType` the way the seven types in modelcontextprotocol#1060 do. Only `handleMultiRoundTripResult` sets that field. It runs inside `Server.callTool`, `getPrompt`, and `readResource`. Some middleware returns a result directly instead of calling `next` — for example, an auth, rate-limit, or entitlement gate. That middleware never reaches `handleMultiRoundTripResult`. The response then ships without the field, and strict clients reject it. ## The fix The choice between `complete` and `input_required` now happens inside `setMultiRoundTripResultType`. Both call sites that need this choice call that function. `setCompleteResultType` now labels results it did not label before, so it is renamed `annotateResultType`. The new function picks a branch with a type switch. Reading matters here. `setResultType` is unexported, so setting `InputRequests` is the only way for middleware to ask for input. If the code always labels results `complete`, the response ships as `{"resultType":"complete","inputRequests":{...}}`. A client that trusts the discriminator then drops the elicitation request. Recomputing the label every time is safe. A result that did reach the dispatcher already carries the same label from that test. This also covers one edge case: a handler that returns `InputRequests` again after the shim's single reinvocation. Before this fix, that case shipped with no `resultType`. Embedding `completeResultWithType` instead is not an option: it labels everything `complete` and overwrites `input_required`, breaking multi-round-trip flows. **Skip nil results.** Middleware can return a typed nil value, which satisfies the `Result` interface. `handle` now checks for that case before it calls the labeling function. Before this change, that same case also panicked, one frame later in `annotateServerInfo` and then `GetMeta`. ## Checks - `TestServerSessionHandle_SetsResultTypeWhenMiddlewareShortCircuits` fails on all three methods before the first change. Its fourth case fails if labeling overwrites a populated `InputRequests`. - `TestServerSessionHandle_ResultTypeGate` records which requests carry `resultType`, including one known gap. See "Separate from this PR" below. - `TestServerSessionHandle_NilResultFromMiddleware` panics without the nil check. - The complete-path cases added to `TestServerSessionHandle_SetsResultTypeOnNewProtocol` also pass before this change. They close a gap in modelcontextprotocol#1060, which added only three cases, and only for `input_required`. - The exported API is unchanged. `go doc -all ./mcp`, with comments stripped, is identical before and after this change. - With the reproduction from the issue, over the streamable transport: the dispatched `tools/call` and the short-circuited `tools/call` both now return `resultType: "complete"`. I could not run `./scripts/conformance.sh`. CONTRIBUTING.md names that script, but the repo only has `client-conformance.sh` and `server-conformance.sh`. Both scripts need GNU `timeout`, and macOS does not include it. ## Also here Ten comments named an exported `ResultType` field and `ResultTypeComplete` / `ResultTypeInputRequired` constants. None of those exist. Two of the comments were `[CallToolResult.ResultType]` doc links, and those links cannot resolve. They now point at `[CallToolResult.NeedsInput]` instead, because an unexported field is also not a valid link target. --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> Co-authored-by: Guglielmo Colombo <guglielmoc@google.com>
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.
Summary
resultType: "complete"to non-MRTR server result responses for 2026-07-28 requests.server/discover, list methods, and completion responses, and update expected conformance/example output.Why
Fixes #1032. The 2026-07-28 schema requires
resultTypeon result objects, but only the MRTR-capable results were setting it.Validation
go test ./mcp -run 'Test(ServerSessionHandle|CompleteResult|ClientConnectDiscover|InMemory_E2E_Discover)' -count=1— passed\n-go test ./mcp -count=1— passed\n-go test ./...— passed\n-go vet ./...— passed