-
Notifications
You must be signed in to change notification settings - Fork 261
Add support for Tool.outputSchema
and CallToolResult.structuredContent
#316
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Add support for Tool.outputSchema
and CallToolResult.structuredContent
#316
Conversation
- Add optional output_schema field to Tool struct for defining tool output structure - Update Tool::new() to initialize output_schema as None
- Add optional structured_content field for JSON object results - Make content field optional to support either structured or unstructured results - Add CallToolResult::structured() and structured_error() constructor methods
…ontent - Add validate() method to ensure content and structured_content are mutually exclusive - Implement custom Deserialize to enforce validation during deserialization - Update documentation to clarify the mutual exclusivity requirement
- Add output_schema field to ToolAttribute and ResolvedToolAttribute structs - Implement automatic output schema generation from return types - Support explicit output_schema attribute for manual specification - Generate schemas for Result<T, E> where T is not CallToolResult - Update tool generation to include output_schema in Tool struct
- Add Structured<T> wrapper type for explicit structured content - Implement IntoCallToolResult for Structured<T> with JSON serialization - Add support for Result<Structured<T>, E> conversions - Enable tools to return structured content through the trait system
- Handle Option<Vec<Content>> in CallToolResult.content - Add proper unwrapping for the optional content field - Fix compilation error in chat.rs
- Add output_schema field to Tool initialization in sampling_stdio example - Update test_tool_macros tests to handle Option<Vec<Content>> - Use as_ref() before calling first() on optional content field
- Add validate_against_schema function for basic type validation - Add note that full JSON Schema validation requires dedicated library - Document that actual validation should happen in tool handler
d9b8bfc
to
8769ec5
Compare
- Add output_schema field to Tool struct for defining output JSON schemas - Add structured_content field to CallToolResult (mutually exclusive with content) - Implement Structured<T> wrapper for type-safe structured outputs - Update #[tool] macro to automatically generate output schemas from return types - Add validation of structured outputs against their schemas - Update all examples and tests for breaking change (CallToolResult.content now Option) - Add comprehensive documentation and rustdoc - Add structured_output example demonstrating the feature BREAKING CHANGE: CallToolResult.content is now Option<Vec<Content>> instead of Vec<Content> Closes modelcontextprotocol#312
8769ec5
to
b174b63
Compare
|
The #[tool] macro requires Parameters<T> wrapper for tool inputs. This fixes the pre-existing broken doctest in the structured output documentation example.
Some doctest were apparently failing on |
} | ||
} | ||
_ => None, | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is the complexity of the circle here a bit high?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Simplify this please.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am also working on this, but I would like to merge your pr firstly. |
crates/rmcp-macros/src/tool.rs
Outdated
false | ||
}; | ||
|
||
if !is_call_tool_result { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think the output schema should can be provided by trait IntoCallToolResult
pub trait IntoCallToolResult {
fn into_call_tool_result(self) -> Result<CallToolResult, crate::ErrorData>;
// some interface like this
fn output_schema() -> Option<Value> {
None
}
}
And we should implement this for Json<T>
especially.
It's a bad idea to check the return type here, which could be a lot of work.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
/// })) | ||
/// } | ||
/// ``` | ||
pub struct Structured<T>(pub T); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We already have one: rmcp::handler::server::wrapper::Json
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@@ -19,6 +19,9 @@ pub struct Tool { | |||
pub description: Option<Cow<'static, str>>, | |||
/// A JSON Schema object defining the expected parameters for the tool | |||
pub input_schema: Arc<JsonObject>, | |||
/// An optional JSON Schema object defining the structure of the tool's output | |||
#[serde(skip_serializing_if = "Option::is_none")] | |||
pub output_schema: Option<Arc<JsonObject>>, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
May we need a method here to set input and output schema by type?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
- Remove Structured<T> type definition and implementations - Reuse existing Json<T> wrapper for structured content - Update IntoCallToolResult implementations to use Json<T> - Add JsonSchema implementation for Json<T> delegating to T - Update all examples and tests to use Json<T> instead of Structured<T> - Update documentation and exports BREAKING CHANGE: Structured<T> has been replaced with Json<T>. Users must update their code to use Json<T> for structured tool outputs.
- Add output_schema() method with default None implementation - Implement output_schema() for Json<T> to return cached schema - Implement output_schema() for Result<Json<T>, E> delegating to Json<T> - Enable trait-based schema generation for structured outputs
- Add extract_json_inner_type() helper to detect Json<T> types - Update schema generation to only occur for Json<T> wrapped types - Remove generic Result<T, E> detection in favor of specific Json<T> detection - Add comprehensive tests to verify schema generation behavior
- Add with_output_schema<T>() method to set output schema from type - Add with_input_schema<T>() method to set input schema from type - Both methods use cached_schema_for_type internally - Add comprehensive tests for builder methods
2a84b23
to
43a72da
Compare
@4t145 I just pushed an update that should address all your comments. |
- Add Default implementation for StructuredOutputServer - Fix collapsible else-if in simple-chat-client - No functional changes
Apply automatic formatting changes to: - examples/simple-chat-client/src/chat.rs - fix line wrapping - crates/rmcp-macros/src/tool.rs - format method chaining - examples/servers/src/structured_output.rs - reorder imports and format function signatures
I'm fixing the formatting and doc issues. IIRC the commits will be squashed so I won't fix the commit message lint issues unless asked to by a maintainer. |
@4t145 all done! |
we should create a 6-18 spec milestone ,for stracing the work before merging this pr @4t145 @alexhancock |
/// | ||
/// Note: This is a basic validation that only checks type compatibility. | ||
/// For full JSON Schema validation, a dedicated validation library would be needed. | ||
pub fn validate_against_schema( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we extract it like this ,For better scalability, And there seems to be some repetition here, How about this
pub fn validate_against_schema(
value: &serde_json::Value,
schema: &JsonObject,
) -> Result<(), crate::ErrorData> {
// Basic type validation
if let Some(schema_type) = schema.get("type").and_then(|t| t.as_str()) {
let value_type = get_json_value_type(value);
if schema_type != value_type {
return Err(crate::ErrorData::invalid_params(
format!(
"Value type does not match schema. Expected '{}', got '{}'",
schema_type, value_type
),
None,
));
}
}
Ok(())
}
fn get_json_value_type(value: &serde_json::Value) -> &'static str {
match value {
serde_json::Value::Null => "null",
serde_json::Value::Bool(_) => "boolean",
serde_json::Value::Number(_) => "number",
serde_json::Value::String(_) => "string",
serde_json::Value::Array(_) => "array",
serde_json::Value::Object(_) => "object",
}
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@jokemanfire I'm on it!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@jokemanfire done in a1ef39e
Add support for:
Tool.outputSchema
CallToolResult.structuredContent
Motivation and Context
Implements #312
First step toward MCP 2025-06-18 support.
How Has This Been Tested?
Comprehensive unit tests for the new structured output features we implemented. The tests cover:
CallToolResult::structured()
andCallToolResult::structured_error()
methodsoutput_schema
field functionalityIntoCallToolResult
trait implementation forStructured<T>
content
andstructured_content
The tests are located in
tests/test_structured_output.rs
and provide good coverage of the core functionality we added.Breaking Changes
Both
Tool.outputSchema
andCallToolResult.structuredContent
are optional.The only breaking change being that
CallToolResult.content
is now optional to support mutual exclusivity withstructured_content
.Types of changes
Checklist
Additional context
None for now.
Task List
Core Data Structures
output_schema: Option<Arc<JsonObject>>
field to Tool structstructured_content: Option<Value>
field to CallToolResult structCallToolResult::structured()
constructor methodCallToolResult::structured_error()
constructor methodMacro Support
output_schema
attribute for manual schema specificationType Conversion Infrastructure
Structured<T>
wrapper type for structured resultsIntoCallToolResult
forStructured<T>
IntoCallToolResult
for types that should produce structured contentTool Handler Updates
Testing
Documentation and Examples
Technical Considerations
Backward Compatibility
Performance
Error Handling
Dependencies
Timeline Estimate
Total estimated time: 14-20 hours
References