Skip to content

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

Open
wants to merge 19 commits into
base: main
Choose a base branch
from

Conversation

JMLX42
Copy link

@JMLX42 JMLX42 commented Jul 14, 2025

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() and CallToolResult::structured_error() methods
  • Tool output_schema field functionality
  • IntoCallToolResult trait implementation for Structured<T>
  • Mutual exclusivity validation between content and structured_content
  • Schema generation and serialization/deserialization

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 and CallToolResult.structuredContent are optional.

The only breaking change being that CallToolResult.content is now optional to support mutual exclusivity with structured_content.

Types of changes

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to change)
  • Documentation update

Checklist

  • I have read the MCP Documentation
  • My code follows the repository's style guidelines
  • New and existing tests pass locally
  • I have added appropriate error handling
  • I have added or updated documentation as needed

Additional context

None for now.

Task List

Core Data Structures

  • Add output_schema: Option<Arc<JsonObject>> field to Tool struct
  • Add structured_content: Option<Value> field to CallToolResult struct
  • Implement validation for mutually exclusive content/structuredContent fields
  • Add CallToolResult::structured() constructor method
  • Add CallToolResult::structured_error() constructor method

Macro Support

  • Parse function return types in #[tool] macro to generate output schemas
  • Support explicit output_schema attribute for manual schema specification
  • Generate schema using schemars for structured return types
  • Store output schema in generated tool metadata
  • Update tool_attr generation to include output_schema

Type Conversion Infrastructure

  • Create Structured<T> wrapper type for structured results
  • Implement IntoCallToolResult for Structured<T>
  • Implement IntoCallToolResult for types that should produce structured content
  • Add automatic JSON serialization for structured types
  • Implement schema validation in conversion logic

Tool Handler Updates

  • Update tool invocation to check for output_schema
  • Implement validation of structured output against schema
  • Handle conversion between Rust types and JSON values
  • Update error propagation for validation failures
  • Cache output schemas similar to input schemas
  • Update tool listing to include output schemas

Testing

  • Test Tool serialization/deserialization with output_schema
  • Test CallToolResult with structured_content
  • Test mutual exclusivity validation
  • Test schema validation for structured outputs
  • Test #[tool] macro with various return types
  • Test error cases (schema violations, invalid types)
  • Test backward compatibility with existing tools
  • Add integration tests for end-to-end scenarios

Documentation and Examples

  • Document Tool.outputSchema field usage
  • Document CallToolResult.structuredContent usage
  • Create example: simple tool with structured output
  • Create example: complex nested structures
  • Create example: error handling with structured content
  • Write migration guide for existing tools
  • Update API documentation
  • Add inline code documentation

Technical Considerations

Backward Compatibility

  • All changes must be backward compatible
  • Tools without output_schema continue to work as before
  • Clients that don't understand structured_content can still use content field

Performance

  • Schema generation should be cached
  • Validation should be efficient
  • Consider lazy evaluation where appropriate

Error Handling

  • Clear error messages for schema violations
  • Proper error propagation through the macro system
  • Graceful degradation when schemas can't be generated

Dependencies

  • schemars 1.0 for schema generation
  • serde_json for JSON manipulation
  • Existing MCP types and traits

Timeline Estimate

  • Core data structure updates: 2-3 hours
  • Macro enhancements: 4-6 hours
  • Type conversion and validation: 3-4 hours
  • Testing: 3-4 hours
  • Documentation: 2-3 hours

Total estimated time: 14-20 hours

References

@github-actions github-actions bot added T-documentation Documentation improvements T-core Core library changes T-model Model/data structure changes labels Jul 14, 2025
@JMLX42 JMLX42 marked this pull request as draft July 14, 2025 12:45
@github-actions github-actions bot added T-dependencies Dependencies related changes T-test Testing related changes T-config Configuration file changes T-examples Example code changes T-handler Handler implementation changes T-macros Macro changes labels Jul 14, 2025
@JMLX42 JMLX42 marked this pull request as ready for review July 14, 2025 14:09
JMLX42 added 8 commits July 14, 2025 17:55
- 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
@JMLX42 JMLX42 force-pushed the feature/output-schema branch from d9b8bfc to 8769ec5 Compare July 14, 2025 15:55
@github-actions github-actions bot removed the T-documentation Documentation improvements label Jul 14, 2025
- 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
@JMLX42 JMLX42 force-pushed the feature/output-schema branch from 8769ec5 to b174b63 Compare July 14, 2025 16:00
@JMLX42 JMLX42 marked this pull request as draft July 14, 2025 16:06
@JMLX42
Copy link
Author

JMLX42 commented Jul 14, 2025

cargo test fails on some doctest. I'm working on it.

The #[tool] macro requires Parameters<T> wrapper for tool inputs.
This fixes the pre-existing broken doctest in the structured output
documentation example.
@JMLX42
Copy link
Author

JMLX42 commented Jul 14, 2025

Some doctest were apparently failing on main already. I fixed them in cb28342

@JMLX42 JMLX42 marked this pull request as ready for review July 14, 2025 17:34
}
}
_ => None,
}
Copy link
Collaborator

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?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Simplify this please.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@4t145 done in 70bf2b1

@4t145
Copy link
Collaborator

4t145 commented Jul 15, 2025

I am also working on this, but I would like to merge your pr firstly.

false
};

if !is_call_tool_result {
Copy link
Collaborator

@4t145 4t145 Jul 15, 2025

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.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@4t145 Done in 3ed064d

/// }))
/// }
/// ```
pub struct Structured<T>(pub T);
Copy link
Collaborator

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

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@4t145 done in 1b03666

@@ -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>>,
Copy link
Collaborator

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?

Copy link
Author

@JMLX42 JMLX42 Jul 15, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@4t145 Done in 43a72da

@github-actions github-actions bot added the T-documentation Documentation improvements label Jul 15, 2025
JMLX42 added 4 commits July 15, 2025 17:14
- 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
@JMLX42 JMLX42 force-pushed the feature/output-schema branch from 2a84b23 to 43a72da Compare July 15, 2025 15:14
@github-actions github-actions bot removed the T-documentation Documentation improvements label Jul 15, 2025
@JMLX42
Copy link
Author

JMLX42 commented Jul 15, 2025

I am also working on this, but I would like to merge your pr firstly.

@4t145 I just pushed an update that should address all your comments.

JMLX42 added 3 commits July 15, 2025 17:20
- 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
@JMLX42
Copy link
Author

JMLX42 commented Jul 16, 2025

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.

@JMLX42
Copy link
Author

JMLX42 commented Jul 16, 2025

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!

@JMLX42 JMLX42 requested review from 4t145 and jokemanfire July 16, 2025 21:23
@jokemanfire
Copy link
Collaborator

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(
Copy link
Collaborator

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",
    }
}

Copy link
Author

@JMLX42 JMLX42 Jul 17, 2025

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!

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@JMLX42 JMLX42 requested a review from jokemanfire July 17, 2025 16:41
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
T-config Configuration file changes T-core Core library changes T-dependencies Dependencies related changes T-examples Example code changes T-handler Handler implementation changes T-macros Macro changes T-model Model/data structure changes T-test Testing related changes
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants