SEP-2106: Tools inputSchema & outputSchema Conform to JSON Schema 2020-12 — rust-sdk implementation
Spec PR: modelcontextprotocol/modelcontextprotocol#2106
Track: Specification · Stage: accepted · Priority: P1
Needs code changes: Yes (Medium)
Summary
Brings tool schemas to full JSON Schema 2020-12:
inputSchema: keep type: "object" required, but allow any 2020-12 keyword — composition (oneOf/anyOf/allOf/not), conditional (if/then/else), references ($ref/$defs/$anchor), etc.
outputSchema: fully support 2020-12 — arrays, primitives, objects, compositions (not just objects).
structuredContent: accept any JSON value validated by outputSchema (not only an object).
Motivated by real friction: tools that naturally return arrays must currently wrap them in a
container object.
Why this needs code changes in rust-sdk
Current state in the SDK (already partly there):
crates/rmcp/src/model/tool.rs: Tool.input_schema: Arc<JsonObject> and output_schema: Option<Arc<JsonObject>>. JsonObject here is the schema document (always a JSON object), so this typing is fine even for non-object schemas — no change needed to those field types.
CallToolResult.structured_content is already Option<Value> (crates/rmcp/src/model.rs ~line 2780), so the result type already accepts arrays/primitives. Good — no model change there.
Tool::with_input_schema::<T: JsonSchema>() / with_output_schema::<T: JsonSchema>() derive schemas via schemars (Cargo.toml pins schemars = "1.x", which emits JSON Schema 2020-12).
So the type system mostly already allows this. The real gaps are validation, helpers, and the tool macro:
- The
#[tool]/tool_router proc-macro path and with_output_schema::<T>() should work when T is a non-object (e.g. Vec<Forecast>). Verify the macro doesn't force type: object.
- Any server-side validation that checks
structured_content against output_schema must accept non-object roots and 2020-12 keywords (oneOf/anyOf/allOf/if/then/else/$ref/$defs).
- Confirm
serde round-trips of Value structured content for array/primitive payloads (covered by model/serde_impl.rs).
Proposed work
Affected areas
crates/rmcp/src/model/tool.rs, crates/rmcp/src/model.rs (CallToolResult), crates/rmcp-macros/ (the #[tool] derive), handler/server/tool.rs.
Notes / risks
- Low risk:
schemars already emits 2020-12 and structured_content is already Option<Value>. Bulk of the work is the macro/validation audit plus tests, not new types.
Related existing issues
SEP-2106: Tools
inputSchema&outputSchemaConform to JSON Schema 2020-12 — rust-sdk implementationSpec PR: modelcontextprotocol/modelcontextprotocol#2106
Track: Specification · Stage: accepted · Priority: P1
Needs code changes: Yes (Medium)
Summary
Brings tool schemas to full JSON Schema 2020-12:
inputSchema: keeptype: "object"required, but allow any 2020-12 keyword — composition (oneOf/anyOf/allOf/not), conditional (if/then/else), references ($ref/$defs/$anchor), etc.outputSchema: fully support 2020-12 — arrays, primitives, objects, compositions (not just objects).structuredContent: accept any JSON value validated byoutputSchema(not only an object).Motivated by real friction: tools that naturally return arrays must currently wrap them in a
container object.
Why this needs code changes in rust-sdk
Current state in the SDK (already partly there):
crates/rmcp/src/model/tool.rs:Tool.input_schema: Arc<JsonObject>andoutput_schema: Option<Arc<JsonObject>>.JsonObjecthere is the schema document (always a JSON object), so this typing is fine even for non-object schemas — no change needed to those field types.CallToolResult.structured_contentis alreadyOption<Value>(crates/rmcp/src/model.rs~line 2780), so the result type already accepts arrays/primitives. Good — no model change there.Tool::with_input_schema::<T: JsonSchema>()/with_output_schema::<T: JsonSchema>()derive schemas viaschemars(Cargo.toml pinsschemars = "1.x", which emits JSON Schema 2020-12).So the type system mostly already allows this. The real gaps are validation, helpers, and the
toolmacro:#[tool]/tool_routerproc-macro path andwith_output_schema::<T>()should work whenTis a non-object (e.g.Vec<Forecast>). Verify the macro doesn't forcetype: object.structured_contentagainstoutput_schemamust accept non-object roots and 2020-12 keywords (oneOf/anyOf/allOf/if/then/else/$ref/$defs).serderound-trips ofValuestructured content for array/primitive payloads (covered bymodel/serde_impl.rs).Proposed work
schemars 1.xoutput (composition/conditional/$ref) is accepted end-to-end throughTool::new+with_output_schema.#[tool]proc-macro (in thermcp-macroscrate) handles a non-object return type foroutput_schemawithout forcingtype: object.structured_content↔output_schemavalidation for object-only assumptions; allow array/primitive roots.Vec<Forecast>as top-levelstructuredContent).Affected areas
crates/rmcp/src/model/tool.rs,crates/rmcp/src/model.rs(CallToolResult),crates/rmcp-macros/(the#[tool]derive),handler/server/tool.rs.Notes / risks
schemarsalready emits 2020-12 andstructured_contentis alreadyOption<Value>. Bulk of the work is the macro/validation audit plus tests, not new types.Related existing issues