Support mixed content types from the mcp server tool call response#2517
Support mixed content types from the mcp server tool call response#2517crivetimihai merged 6 commits intomainfrom
Conversation
cdcddf0 to
8d08aaa
Compare
Code Review & Fixes AppliedI've reviewed this PR and found a critical bug in the original implementation that would have caused Issues Found
Changes Made
Verification
Commits squashed and rebased onto main. Will do integration testing next. |
… response Closes #2512 This fix addresses tool invocation failures for tools that return complex content types (like ResourceLink, ImageContent, AudioContent) or contain Pydantic-specific types like AnyUrl. Root causes fixed: 1. tool_service.py: Usage of model_dump() without mode='json' preserved pydantic.AnyUrl objects, violating internal model's str type constraints. 2. streamablehttp_transport.py: Code blindly assumed types.TextContent, accessing .text on every item, which crashed for ResourceLink or ImageContent. Changes: - Updated tool_service.py to use model_dump(by_alias=True, mode='json'), forcing conversion of AnyUrl to JSON-compatible strings. - Refactored streamablehttp_transport.py to inspect content.type and correctly map to proper MCP SDK types (TextContent, ImageContent, AudioContent, ResourceLink, EmbeddedResource) ensuring full protocol compatibility. - Updated return type annotation to include all MCP content types. Signed-off-by: Mihai Criveti <crivetimihai@gmail.com>
8d08aaa to
30071fe
Compare
Addresses dropped metadata fields identified in PR #2517 review: - Preserve annotations and _meta for TextContent, ImageContent, AudioContent - Preserve size and _meta for ResourceLink (critical for file metadata) - Handle EmbeddedResource via model_validate Add comprehensive regression tests for: - Mixed content types (text, image, audio, resource_link, embedded) - Metadata preservation (annotations, _meta, size) - Unknown content type fallback - Missing optional metadata handling Closes #2512 Signed-off-by: Mihai Criveti <crivetimihai@gmail.com>
Additional Fixes (Commit 57d7728)Addressed the review findings regarding metadata preservation: Changes
Test ResultsAll 74 tests in |
…tibility mcpgateway.common.models.Annotations is a different Pydantic class from mcp.types.Annotations. Passing gateway Annotations directly to MCP SDK types causes ValidationError at runtime when real MCP responses include annotations. Fix: - Add _convert_annotations() helper to convert gateway Annotations to dict - Add _convert_meta() helper for consistent meta handling - Apply conversion to all content types (text, image, audio, resource_link) Add regression tests using actual gateway model types: - test_call_tool_with_gateway_model_annotations - test_call_tool_with_gateway_model_image_annotations These tests use mcpgateway.common.models.TextContent/ImageContent with mcpgateway.common.models.Annotations to verify the conversion works. Signed-off-by: Mihai Criveti <crivetimihai@gmail.com>
Fix: Annotations Type Mismatch (Commit c7376f2)Addressed the annotations type compatibility issue identified in review: Problem
SolutionAdded helper functions to convert gateway types to dict before passing to MCP SDK: def _convert_annotations(ann: Any) -> dict[str, Any] | None:
if ann is None:
return None
if isinstance(ann, dict):
return ann
if hasattr(ann, "model_dump"):
return ann.model_dump(by_alias=True, mode="json")
return NoneApplied to all content types: TextContent, ImageContent, AudioContent, ResourceLink. New Regression Tests
These tests verify the conversion works with real gateway types, not just mock dicts. Test ResultsAll 76 tests pass + 22 doctests pass. |
Add explicit tests for the AnyUrl serialization fix (Issue #2512 root cause): - test_anyurl_serialization_without_mode_json - demonstrates the problem - test_anyurl_serialization_with_mode_json - verifies the fix - test_resource_link_anyurl_serialization - ResourceLink uri field - test_tool_result_with_resource_link_serialization - ToolResult with ResourceLink - test_mixed_content_with_anyurl_serialization - mixed content types These tests verify that mode='json' in model_dump() correctly serializes AnyUrl objects to strings, preventing validation errors when content is passed to MCP SDK types. Signed-off-by: Mihai Criveti <crivetimihai@gmail.com>
AnyUrl Serialization Tests Added (Commit e841781)Added explicit tests for the New Tests in
|
| Test | Purpose |
|---|---|
test_anyurl_serialization_without_mode_json |
Demonstrates AnyUrl stays as object without fix |
test_anyurl_serialization_with_mode_json |
Verifies mode='json' serializes AnyUrl to string |
test_resource_link_anyurl_serialization |
ResourceLink uri field serialization |
test_tool_result_with_resource_link_serialization |
ToolResult containing ResourceLink |
test_mixed_content_with_anyurl_serialization |
Mixed content types with AnyUrl |
These tests explicitly cover the model_dump(by_alias=True, mode="json") code path at tool_service.py:3192.
Summary of All Commits
30071fe32- Initial fix: return MCP SDK types instead of dicts57d772800- Metadata preservation (annotations, _meta, size)c7376f2b1- Annotations type conversion (gateway → dict → MCP SDK)e84178119- AnyUrl serialization tests
All residual risks from review have been addressed.
…meta Signed-off-by: Mihai Criveti <crivetimihai@gmail.com>
Integration Test ReportTimestamp: 2026-01-27T14:59:42 UTC Test Results: 17/17 PASSED ✅
Testing Methodology
Commits in This PR
Status: Ready for merge 🚀 |
Signed-off-by: Mihai Criveti <crivetimihai@gmail.com>
…BM#2517) * fix(transport): support mixed content types from MCP server tool call response Closes IBM#2512 This fix addresses tool invocation failures for tools that return complex content types (like ResourceLink, ImageContent, AudioContent) or contain Pydantic-specific types like AnyUrl. Root causes fixed: 1. tool_service.py: Usage of model_dump() without mode='json' preserved pydantic.AnyUrl objects, violating internal model's str type constraints. 2. streamablehttp_transport.py: Code blindly assumed types.TextContent, accessing .text on every item, which crashed for ResourceLink or ImageContent. Changes: - Updated tool_service.py to use model_dump(by_alias=True, mode='json'), forcing conversion of AnyUrl to JSON-compatible strings. - Refactored streamablehttp_transport.py to inspect content.type and correctly map to proper MCP SDK types (TextContent, ImageContent, AudioContent, ResourceLink, EmbeddedResource) ensuring full protocol compatibility. - Updated return type annotation to include all MCP content types. Signed-off-by: Mihai Criveti <crivetimihai@gmail.com> * fix(transport): preserve metadata in mixed content type conversion Addresses dropped metadata fields identified in PR IBM#2517 review: - Preserve annotations and _meta for TextContent, ImageContent, AudioContent - Preserve size and _meta for ResourceLink (critical for file metadata) - Handle EmbeddedResource via model_validate Add comprehensive regression tests for: - Mixed content types (text, image, audio, resource_link, embedded) - Metadata preservation (annotations, _meta, size) - Unknown content type fallback - Missing optional metadata handling Closes IBM#2512 Signed-off-by: Mihai Criveti <crivetimihai@gmail.com> * fix(transport): convert gateway Annotations to dict for MCP SDK compatibility mcpgateway.common.models.Annotations is a different Pydantic class from mcp.types.Annotations. Passing gateway Annotations directly to MCP SDK types causes ValidationError at runtime when real MCP responses include annotations. Fix: - Add _convert_annotations() helper to convert gateway Annotations to dict - Add _convert_meta() helper for consistent meta handling - Apply conversion to all content types (text, image, audio, resource_link) Add regression tests using actual gateway model types: - test_call_tool_with_gateway_model_annotations - test_call_tool_with_gateway_model_image_annotations These tests use mcpgateway.common.models.TextContent/ImageContent with mcpgateway.common.models.Annotations to verify the conversion works. Signed-off-by: Mihai Criveti <crivetimihai@gmail.com> * test(tool_service): add AnyUrl serialization tests for mode='json' fix Add explicit tests for the AnyUrl serialization fix (Issue IBM#2512 root cause): - test_anyurl_serialization_without_mode_json - demonstrates the problem - test_anyurl_serialization_with_mode_json - verifies the fix - test_resource_link_anyurl_serialization - ResourceLink uri field - test_tool_result_with_resource_link_serialization - ToolResult with ResourceLink - test_mixed_content_with_anyurl_serialization - mixed content types These tests verify that mode='json' in model_dump() correctly serializes AnyUrl objects to strings, preventing validation errors when content is passed to MCP SDK types. Signed-off-by: Mihai Criveti <crivetimihai@gmail.com> * docs(transport): add docstrings to _convert_annotations and _convert_meta Signed-off-by: Mihai Criveti <crivetimihai@gmail.com> * docs(transport): add Args/Returns to helper function docstrings Signed-off-by: Mihai Criveti <crivetimihai@gmail.com> --------- Signed-off-by: Mihai Criveti <crivetimihai@gmail.com> Co-authored-by: Mihai Criveti <crivetimihai@gmail.com> Signed-off-by: hughhennnelly <hughhennelly06@gmail.com>
…BM#2517) * fix(transport): support mixed content types from MCP server tool call response Closes IBM#2512 This fix addresses tool invocation failures for tools that return complex content types (like ResourceLink, ImageContent, AudioContent) or contain Pydantic-specific types like AnyUrl. Root causes fixed: 1. tool_service.py: Usage of model_dump() without mode='json' preserved pydantic.AnyUrl objects, violating internal model's str type constraints. 2. streamablehttp_transport.py: Code blindly assumed types.TextContent, accessing .text on every item, which crashed for ResourceLink or ImageContent. Changes: - Updated tool_service.py to use model_dump(by_alias=True, mode='json'), forcing conversion of AnyUrl to JSON-compatible strings. - Refactored streamablehttp_transport.py to inspect content.type and correctly map to proper MCP SDK types (TextContent, ImageContent, AudioContent, ResourceLink, EmbeddedResource) ensuring full protocol compatibility. - Updated return type annotation to include all MCP content types. Signed-off-by: Mihai Criveti <crivetimihai@gmail.com> * fix(transport): preserve metadata in mixed content type conversion Addresses dropped metadata fields identified in PR IBM#2517 review: - Preserve annotations and _meta for TextContent, ImageContent, AudioContent - Preserve size and _meta for ResourceLink (critical for file metadata) - Handle EmbeddedResource via model_validate Add comprehensive regression tests for: - Mixed content types (text, image, audio, resource_link, embedded) - Metadata preservation (annotations, _meta, size) - Unknown content type fallback - Missing optional metadata handling Closes IBM#2512 Signed-off-by: Mihai Criveti <crivetimihai@gmail.com> * fix(transport): convert gateway Annotations to dict for MCP SDK compatibility mcpgateway.common.models.Annotations is a different Pydantic class from mcp.types.Annotations. Passing gateway Annotations directly to MCP SDK types causes ValidationError at runtime when real MCP responses include annotations. Fix: - Add _convert_annotations() helper to convert gateway Annotations to dict - Add _convert_meta() helper for consistent meta handling - Apply conversion to all content types (text, image, audio, resource_link) Add regression tests using actual gateway model types: - test_call_tool_with_gateway_model_annotations - test_call_tool_with_gateway_model_image_annotations These tests use mcpgateway.common.models.TextContent/ImageContent with mcpgateway.common.models.Annotations to verify the conversion works. Signed-off-by: Mihai Criveti <crivetimihai@gmail.com> * test(tool_service): add AnyUrl serialization tests for mode='json' fix Add explicit tests for the AnyUrl serialization fix (Issue IBM#2512 root cause): - test_anyurl_serialization_without_mode_json - demonstrates the problem - test_anyurl_serialization_with_mode_json - verifies the fix - test_resource_link_anyurl_serialization - ResourceLink uri field - test_tool_result_with_resource_link_serialization - ToolResult with ResourceLink - test_mixed_content_with_anyurl_serialization - mixed content types These tests verify that mode='json' in model_dump() correctly serializes AnyUrl objects to strings, preventing validation errors when content is passed to MCP SDK types. Signed-off-by: Mihai Criveti <crivetimihai@gmail.com> * docs(transport): add docstrings to _convert_annotations and _convert_meta Signed-off-by: Mihai Criveti <crivetimihai@gmail.com> * docs(transport): add Args/Returns to helper function docstrings Signed-off-by: Mihai Criveti <crivetimihai@gmail.com> --------- Signed-off-by: Mihai Criveti <crivetimihai@gmail.com> Co-authored-by: Mihai Criveti <crivetimihai@gmail.com>
🐛 Bug-fix PR
Closes #2512
📌 Summary
Fixed tool invocation failures for tools that return complex content types (like
ResourceLink,ImageContent) or contain Pydantic-specific types like AnyUrl. It ensures robust handling of diverse tool outputs in the Gateway.🔁 Reproduction Steps
Initially fails with ValidationError (Input should be a valid string, received AnyUrl).
Attempting to fix validation reveals AttributeError: 'ResourceLink' object has no attribute 'text'.
🐞 Root Cause
tool_service.py: usage of .model_dump() without mode='json' preserved pydantic.AnyUrl objects, violating the internal model's str type constraints.streamablehttp_transport.py: The code blindly iterated over results assuming types.TextContent, accessing .text on every item, which crashed for ResourceLink or ImageContent.#2512 (comment)
💡 Fix Description
🧪 Verification
make lintmake testmake coverage📐 MCP Compliance (if relevant)
✅ Checklist
make black isort pre-commit)