feat: relax outputSchema to accept non-object JSON Schema types (SEP-2106)#895
Open
branben wants to merge 4 commits into
Open
feat: relax outputSchema to accept non-object JSON Schema types (SEP-2106)#895branben wants to merge 4 commits into
branben wants to merge 4 commits into
Conversation
added 3 commits
June 6, 2026 13:36
… (SEP-2106) Split validate_and_strip into validate_and_strip_input (keeps type:"object" check for inputSchema) and validate_and_strip_output (accepts any JSON Schema root type per SEP-2106). Update schema_for_output to use the new output variant. Flip test_schema_for_output_rejects_primitive to assert Ok and rename to test_schema_for_output_accepts_primitive. Add test_schema_for_output_accepts_array and test_schema_for_output_strips_title_for_primitive. Update with_output_schema doc comment to remove incorrect "root type object" panic reference.
Add tests verifying schema_for_output accepts non-object types: - test_tool_builder_methods: primitive (i32), array (Vec<String>), option - test_structured_output: tool returning Json<Vec<T>> and Json<i32> - test_json_schema_detection: Json<Vec<T>>, Result<Json<Vec<T>>,E>, Json<String> - tool_traits: ToolBase::output_schema with Vec<AddOutput> output type
Add tests identified during code review: - description stripping for primitive types - composition types (Option<String> with anyOf/oneOf/null) - cache correctness (Arc::ptr_eq for repeated calls) - schema_for_input rejecting array types (not just primitives) - schema_for_output accepting unit type ()
Contributor
|
thanks @branben if you can run a fmt over this to get CI happy - I think this is then good to merge, thanks! |
Author
just checked on this @michaelneale happy I could contribute! |
4 tasks
DaleSeo
reviewed
Jun 23, 2026
| /// Strip top-level `title`/`description` from a JSON schema for outputSchema. | ||
| /// Unlike inputSchema, outputSchema may have any JSON Schema 2020-12 root type | ||
| /// (objects, arrays, primitives, compositions) per SEP-2106. | ||
| fn validate_and_strip_output(raw: &Arc<JsonObject>) -> Arc<JsonObject> { |
Member
There was a problem hiding this comment.
This performs no validation.
Suggested change
| fn validate_and_strip_output(raw: &Arc<JsonObject>) -> Arc<JsonObject> { | |
| fn strip_output(raw: &Arc<JsonObject>) -> Arc<JsonObject> { |
| /// # Panics | ||
| /// | ||
| /// Panics if the generated schema does not have root type "object" as required by MCP specification. | ||
| /// Panics if output schema generation fails. |
Member
There was a problem hiding this comment.
It won't likely panic because schema_for_output no longer return Err.
| /// Top-level "title" and "description" are always removed. | ||
| pub fn schema_for_output<T: JsonSchema + std::any::Any>() -> Result<Arc<JsonObject>, String> { | ||
| thread_local! { | ||
| static CACHE_FOR_OUTPUT: std::sync::RwLock<HashMap<TypeId, Result<Arc<JsonObject>, String>>> = Default::default(); |
Member
There was a problem hiding this comment.
I think we can cache the success value only.
Suggested change
| static CACHE_FOR_OUTPUT: std::sync::RwLock<HashMap<TypeId, Result<Arc<JsonObject>, String>>> = Default::default(); | |
| static CACHE_FOR_OUTPUT: std::sync::RwLock<HashMap<TypeId, Arc<JsonObject>>> = Default::default(); |
| assert!(tool.output_schema.is_some()); | ||
|
|
||
| let schema_str = serde_json::to_string(tool.output_schema.as_ref().unwrap()).unwrap(); | ||
| assert!(schema_str.contains("\"type\":\"integer\"")); |
Member
There was a problem hiding this comment.
To prevent false positives
Suggested change
| assert!(schema_str.contains("\"type\":\"integer\"")); | |
| assert_eq!(schema.get("type"), Some(&serde_json::json!("integer"))); |
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
Relax
schema_for_outputto accept any JSON Schema 2020-12 root type (arrays, primitives, compositions) foroutputSchema, while keepingschema_for_inputenforcingtype: "object". This implements SEP-2106, which was accepted May 18 2026.PR #860 (merged Jun 2 2026) added title/desc stripping and input validation. This PR covers the remaining work: decoupling the type gate so output schemas are no longer rejected for non-object types.
Changes
crates/rmcp/src/handler/server/common.rs: Splitvalidate_and_stripinto:validate_and_strip_input— keepstype: "object"check for inputSchemavalidate_and_strip_output— strips title/desc, no type check (accepts any JSON Schema root type)crates/rmcp/src/model/tool.rs: Updatedwith_output_schemadoc comment to remove incorrect "root type object" panic referenceOption<T>), unit type,Json<T>macro path,ToolBasetrait path, cache correctness, and negative tests for input rejectionBackward Compatibility
schema_for_outputreturn type unchanged (Result<Arc<JsonObject>, String>)Verification
cargo test -p rmcp --all-features: 424 passed, 0 failedcargo clippy --all-targets --all-features -- -D warnings: No issues found