Skip to content

Conversation

@JMLX42
Copy link
Contributor

@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
Contributor 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
Contributor 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
Contributor 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

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@jokemanfire my bad! Sorry.

Done in 43f08bf

@JMLX42
Copy link
Contributor 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
Contributor 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
TODO.md Outdated
@@ -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.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@jokemanfire sorry about that! fixed in cf52be9

}
}
_ => 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

@github-actions github-actions bot removed the T-documentation Documentation improvements label Jul 23, 2025
- Extract complex nested logic into dedicated helper function
- Replace deeply nested if-else chains with functional approach
- Use early returns and ? operator for cleaner code flow
- Reduce 46 lines to 7 lines in main logic while improving readability
@JMLX42 JMLX42 requested a review from jokemanfire July 23, 2025 08:57
@JMLX42
Copy link
Contributor Author

JMLX42 commented Jul 23, 2025

@4t145 fixed formatting: 5109fcb

Sorry about that.

jokemanfire
jokemanfire previously approved these changes Jul 24, 2025
@JMLX42 JMLX42 marked this pull request as draft July 25, 2025 17:30
@JMLX42
Copy link
Contributor Author

JMLX42 commented Jul 25, 2025

  • 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

After further testing with rmcp-openapi, it is obvious that Claude Code (cf anthropics/claude-code#4427) and even opencode ignore structuredContent entirely for now.

IMHO the MCP protocol documentation is misleading or Claude Code itself is not up to date for version 2025-06-18 (!):

Structured Content

Structured content is returned as a JSON object in the structuredContent field of a result.
For backwards compatibility, a tool that returns structured content SHOULD also return the serialized JSON in a TextContent block.

https://modelcontextprotocol.io/specification/2025-06-18/server/tools#structured-content

That "SHOULD" is actually a "MUST" for now in my experience (cf anthropics/claude-code#4427).

So I propose to revert 767d3ae.

Structured content is returned as a JSON object in the structuredContent
field of a result.For backwards compatibility, a tool that returns
structured content SHOULD also return the serialized JSON in a
TextContent block.

https://modelcontextprotocol.io/specification/2025-06-18/server/tools#structured-content

Tools may also provide an output schema for validation of structured
results. If an output schema is provided:

- Servers MUST provide structured results that conform to this schema.
- Clients SHOULD validate structured results against this schema.

https://modelcontextprotocol.io/specification/2025-06-18/server/tools#output-schema
@JMLX42
Copy link
Contributor Author

JMLX42 commented Jul 25, 2025

  • 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

After further testing with rmcp-openapi, it is obvious that Claude Code (cf anthropics/claude-code#4427) and even opencode ignore structuredContent entirely for now.

IMHO the MCP protocol documentation is misleading or Claude Code itself is not up to date for version 2025-06-18 (!):

Structured Content

Structured content is returned as a JSON object in the structuredContent field of a result.
For backwards compatibility, a tool that returns structured content SHOULD also return the serialized JSON in a TextContent block.

https://modelcontextprotocol.io/specification/2025-06-18/server/tools#structured-content

That "SHOULD" is actually a "MUST" for now in my experience (cf anthropics/claude-code#4427).

So I propose to revert 767d3ae.

It turns out that structuredContent MUST be set when outputSchema is set:

Tools may also provide an output schema for validation of structured results. If an output schema is provided:

  • Servers MUST provide structured results that conform to this schema.
  • Clients SHOULD validate structured results against this schema.

https://modelcontextprotocol.io/specification/2025-06-18/server/tools#output-schema

So instead of reverting 767d3ae I made the implementation compliant in 906812e

Update: tested and working with Claude Code and opencode via https://gitlab.com/lx-industries/rmcp-openapi/-/issues/37

@JMLX42 JMLX42 marked this pull request as ready for review July 25, 2025 18:58
@JMLX42 JMLX42 requested a review from jokemanfire July 26, 2025 09:44
@4t145
Copy link
Collaborator

4t145 commented Jul 28, 2025

Sorry for not reponde this last week, thanks for your work, I will have a good look this week.

@4t145 4t145 merged commit fbc7ab7 into modelcontextprotocol:main Jul 31, 2025
10 of 11 checks passed
@github-actions github-actions bot mentioned this pull request Jul 31, 2025
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