Skip to content

Relax outputSchema to any JSON Schema 2020-12 document per SEP-2106#1568

Open
jayaraman-venkatesan wants to merge 2 commits intomodelcontextprotocol:mainfrom
jayaraman-venkatesan:feature/sep-2106-output-schema-relaxation
Open

Relax outputSchema to any JSON Schema 2020-12 document per SEP-2106#1568
jayaraman-venkatesan wants to merge 2 commits intomodelcontextprotocol:mainfrom
jayaraman-venkatesan:feature/sep-2106-output-schema-relaxation

Conversation

@jayaraman-venkatesan
Copy link
Copy Markdown
Contributor

@jayaraman-venkatesan jayaraman-venkatesan commented May 9, 2026

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 & outputSchema Conform to JSON Schema 2020-12

Closes #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 required outputSchema.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

C# return type Before After
int Foo() => 72; schema {"type":"object","properties":{"result":{"type":"integer"}},"required":["result"]}; structuredContent {"result":72} schema {"type":"integer"}; structuredContent 72
string Foo() => "Paris"; {"result":"Paris"} "Paris"
string[] Foo() => […]; {"result":[…]} […]
Person? Foo() => null; {"result": null} null
Person Foo() => new(...); {"name":…,"age":…} (already an object — unchanged) {"name":…,"age":…} (unchanged)

inputSchema is not changed — tool inputs are still required to have type: "object", which is consistent with SEP-2106 (it loosens outputSchema only).

Source-level changes

  • src/ModelContextProtocol.Core/McpJsonUtilities.cs — added IsValidJsonSchemaDocument accepting JSON object or boolean. Kept IsValidMcpToolSchema unchanged for InputSchema (still strict).
  • src/ModelContextProtocol.Core/Protocol/Tool.csOutputSchema setter switched to IsValidJsonSchemaDocument; XML doc rewritten to reference SEP-2106 and the new contract.
  • src/ModelContextProtocol.Core/Server/AIFunctionMcpServerTool.cs — deleted the _structuredOutputRequiresWrapping field, the out bool parameter on CreateOutputSchema, the entire wrapping/normalization block in CreateOutputSchema, the wrapping block in CreateStructuredResponse, and constructor wiring around the flag.

Contract widening

The IsValidJsonSchemaDocument validator accepts a slightly broader set than just JSON objects. The full acceptance/rejection table:

OutputSchema value Accepted? Notes
{"type":"object","properties":…} Conventional schema
{"type":"array","items":…} Array schemas — primary SEP-2106 motivation
{"type":"string"}, {"type":"integer"}, etc. Primitives
{"type":["object","null"], …} Type-arrays for nullable schemas
{} "No constraints" object schema
{"oneOf":[…]} Compositions without explicit type
true JSON Schema 2020-12 §4.3 boolean schema — "matches anything"
false JSON Schema 2020-12 §4.3 boolean schema — "matches nothing"
null literal Throws ArgumentException
Bare numbers, strings, arrays Throws ArgumentException

Behavioural 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/primitive structuredContent and want to stay interoperable with old clients SHOULD also emit a TextContent block 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 in post-2106.md of the demo repo.

The demo wires up McpServer and McpClient over a System.IO.Pipelines pipe, registers tools via McpServerTool.Create(...) against this branch's <ProjectReference>, and prints outputSchema from tools/list followed by structuredContent from each tools/call. It confirms the wire shape end-to-end against a real client, not just unit-test mocks.

Did not implement

  • inputSchema validation - explicitly unchanged per SEP-2106.
  • JSON Schema 2020-12 keyword-level structural validation - IsValidJsonSchemaDocument is intentionally only a structural check (object or boolean). No $ref resolution, no oneOf/anyOf keyword validation.
  • Auto-emitting a TextContent mirror 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.
  • TDD red→green confirmed for all flipped/new tests.
  • End-to-end demo (jayaraman-venkatesan/sep-2106-demo) shows expected wire shapes.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

SEP-2106: Tools inputSchema/outputSchema conform to JSON Schema 2020-12

1 participant