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 21 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

Validation Improvements

  • Enforce structured_content usage when output_schema is defined
  • Forbid content field when output_schema is present
  • Ensure errors also use structured_content for tools with output_schema
  • Add comprehensive validation tests for the new strict behavior
  • Update IntoCallToolResult implementations for consistent error handling

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

Copy link
Collaborator

Choose a reason for hiding this comment

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

It seems that there are no modifications here

@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

@JMLX42 JMLX42 requested a review from jokemanfire July 17, 2025 16:41
@JMLX42
Copy link
Author

JMLX42 commented Jul 22, 2025

@4t145 @jokemanfire FYI I already made an actual implementation that leverages this PR:

https://gitlab.com/lx-industries/rmcp-openapi/-/blob/4d6bdaac3780c8953051f5b90019227c01300bb0/src/tool_generator.rs#L223

and as far as I can tell, it works!

@JMLX42 JMLX42 marked this pull request as draft July 22, 2025 18:33
@JMLX42
Copy link
Author

JMLX42 commented Jul 22, 2025

Back to draft to add validation improvements:

  • Enforce structured_content usage when output_schema is defined
  • Forbid content field when output_schema is present
  • Ensure errors also use structured_content for tools with output_schema
  • Add comprehensive validation tests for the new strict behavior
  • Update IntoCallToolResult implementations for consistent error handling

@JMLX42
Copy link
Author

JMLX42 commented Jul 22, 2025

Back to draft to add validation improvements:

  • Enforce structured_content usage when output_schema is defined
  • Forbid content field when output_schema is present
  • Ensure errors also use structured_content for tools with output_schema
  • Add comprehensive validation tests for the new strict behavior
  • Update IntoCallToolResult implementations for consistent error handling
  1. Enhanced Validation in ToolRouter::call() (crates/rmcp/src/handler/server/router/tool.rs:250-269):
    • Added check that tools with output_schema must return structured_content (not None)
    • Added check that tools with output_schema cannot use regular content field
    • Maintained existing schema validation for structured_content
  2. Added Comprehensive Tests (crates/rmcp/tests/test_structured_output.rs):
    - test_output_schema_requires_structured_content() - verifies tools with schemas use structured content
    - test_output_schema_forbids_regular_content() - documents the validation logic
    - test_output_schema_error_must_be_structured() - covers error scenarios
    - test_structured_content_schema_validation() - confirms schema validation works
  3. Updated Documentation (TODO.md):
    - Marked validation improvements as complete
    - Added tracking for the new validation features
  4. Created Example (examples/servers/src/strict_output_validation.rs):
    - Demonstrates proper structured output usage
    - Shows what would fail validation (commented out)

Key Benefits:

  • Consistency: Tools that declare structured output via output_schema must consistently use structured_content for both success and error cases
  • Type Safety: All responses from structured tools are validated against their declared schemas
  • Clear Contract: When a tool has output_schema, clients can rely on getting structured responses
  • Breaking Change: This enforces stricter validation, ensuring tools are correctly implemented

Validation Logic:

  if let Some(ref output_schema) = item.attr.output_schema {
      // When output_schema is defined, structured_content is required
      if result.structured_content.is_none() {
          return Err(crate::ErrorData::invalid_params(
              "Tool with output_schema must return structured_content",
              None
          ));
      }

      // Ensure content is not used when output_schema is defined
      if result.content.is_some() {
          return Err(crate::ErrorData::invalid_params(
              "Tool with output_schema cannot use content field",
              None
          ));
      }

      // Validate the structured content against the schema
      validate_against_schema(result.structured_content.as_ref().unwrap(), output_schema)?;
  }

The implementation successfully addresses the original issue and ensures that when a tool declares an output_schema, it must consistently return structured content, providing better type safety and
clearer contracts for MCP clients.

This commit implements strict validation to ensure tools with output_schema
consistently use structured_content for both success and error responses.

Changes:
- Enhanced ToolRouter::call() validation to require structured_content when output_schema is present
- Added validation that tools with output_schema cannot use regular content field
- Added comprehensive tests covering the new strict validation behavior
- Created example demonstrating proper structured output usage
- Updated TODO.md to track validation improvements

This ensures consistent response format and better type safety for MCP clients.
@github-actions github-actions bot added the T-documentation Documentation improvements label Jul 22, 2025
@JMLX42 JMLX42 marked this pull request as ready for review July 22, 2025 19:56
@jokemanfire jokemanfire added this to the version 0.3.x milestone Jul 23, 2025
@@ -0,0 +1,142 @@
Add support for:

Copy link
Collaborator

Choose a reason for hiding this comment

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

We may should not contain this doc.

}
}
_ => None,
}
Copy link
Collaborator

Choose a reason for hiding this comment

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

It seems that there are no modifications here

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-documentation Documentation improvements 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