Relax outputSchema to any JSON Schema 2020-12 document per SEP-2106#1568
Open
jayaraman-venkatesan wants to merge 2 commits intomodelcontextprotocol:mainfrom
Open
Conversation
…o any JSON Schema 2020-12 document per SEP-2106
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.
feat: relax outputSchema to any JSON Schema 2020-12 document per SEP-2106
Summary
Brings the C# SDK into compliance with SEP-2106 — Tools
inputSchema&outputSchemaConform to JSON Schema 2020-12Closes #1550
The SDK previously wrapped any non-object output schema in a
{"type":"object","properties":{"result":<schema>},"required":["result"]}envelope at registration, and wrapped the corresponding return value in{"result": <value>}at every invocation. Both wrappings exist because the older MCP spec requiredoutputSchema.type == "object". SEP-2106 lifts that restriction. This PR removes both ends of the wrapping plumbing, relaxes the validator, and lets natural JSON shapes flow through.What changes on the wire
int Foo() => 72;{"type":"object","properties":{"result":{"type":"integer"}},"required":["result"]};structuredContent{"result":72}{"type":"integer"};structuredContent72string Foo() => "Paris";{"result":"Paris"}"Paris"string[] Foo() => […];{"result":[…]}[…]Person? Foo() => null;{"result": null}nullPerson Foo() => new(...);{"name":…,"age":…}(already an object — unchanged){"name":…,"age":…}(unchanged)inputSchemais not changed — tool inputs are still required to havetype: "object", which is consistent with SEP-2106 (it loosensoutputSchemaonly).Source-level changes
src/ModelContextProtocol.Core/McpJsonUtilities.cs— addedIsValidJsonSchemaDocumentaccepting JSON object or boolean. KeptIsValidMcpToolSchemaunchanged forInputSchema(still strict).src/ModelContextProtocol.Core/Protocol/Tool.cs—OutputSchemasetter switched toIsValidJsonSchemaDocument; XML doc rewritten to reference SEP-2106 and the new contract.src/ModelContextProtocol.Core/Server/AIFunctionMcpServerTool.cs— deleted the_structuredOutputRequiresWrappingfield, theout boolparameter onCreateOutputSchema, the entire wrapping/normalization block inCreateOutputSchema, the wrapping block inCreateStructuredResponse, and constructor wiring around the flag.Contract widening
The
IsValidJsonSchemaDocumentvalidator accepts a slightly broader set than just JSON objects. The full acceptance/rejection table:OutputSchemavalue{"type":"object","properties":…}{"type":"array","items":…}{"type":"string"},{"type":"integer"}, etc.{"type":["object","null"], …}{}{"oneOf":[…]}typetruefalsenullliteralArgumentExceptionArgumentExceptionBehavioural break call-out
Source-breaking for tools that returned non-object types and whose downstream consumers relied on the
{"result": …}envelope. After this PR the same tool emits the raw value. Per SEP-2106 §"Backward Compatibility": servers that emit array/primitivestructuredContentand want to stay interoperable with old clients SHOULD also emit aTextContentblock with the serialized JSON.The SDK doesn't auto-add such a block, that remains the tool author's choice.
End-to-end smoke test
Beyond unit tests, I built a small in-process client/server demo against this branch that prints both phases of MCP exchange (advertising and invocation) for tools whose return types span the interesting shapes —
int,string,string[], nullable record, plain record. Side-by-side before/after tables of what came out are captured inpost-2106.mdof the demo repo.dotnet run):Post-2106.mdThe demo wires up
McpServerandMcpClientover aSystem.IO.Pipelinespipe, registers tools viaMcpServerTool.Create(...)against this branch's<ProjectReference>, and printsoutputSchemafromtools/listfollowed bystructuredContentfrom eachtools/call. It confirms the wire shape end-to-end against a real client, not just unit-test mocks.Did not implement
inputSchemavalidation - explicitly unchanged per SEP-2106.IsValidJsonSchemaDocumentis intentionally only a structural check (object or boolean). No$refresolution, nooneOf/anyOfkeyword validation.TextContentmirror of structured content for old-client interop - per SEP-2106 that's a server-author choice, not an SDK default.Test plan
dotnet test tests/ModelContextProtocol.Tests(net10.0) — 1904/0/5.jayaraman-venkatesan/sep-2106-demo) shows expected wire shapes.