diff --git a/python/packages/autogen-ext/src/autogen_ext/tools/mcp/_base.py b/python/packages/autogen-ext/src/autogen_ext/tools/mcp/_base.py index 488984ede072..a8a5e00e9790 100644 --- a/python/packages/autogen-ext/src/autogen_ext/tools/mcp/_base.py +++ b/python/packages/autogen-ext/src/autogen_ext/tools/mcp/_base.py @@ -7,7 +7,9 @@ from autogen_core.tools import BaseTool from autogen_core.utils import schema_to_pydantic_model from mcp import ClientSession, Tool +from mcp.types import AnyUrl, EmbeddedResource, ImageContent, TextContent from pydantic import BaseModel +import json from ._config import McpServerParams from ._session import create_mcp_server_session @@ -115,6 +117,27 @@ async def from_server_params(cls, server_params: TServerParams, tool_name: str) return cls(server_params=server_params, tool=matching_tool) + def return_value_as_string(self, value: list) -> str: + """Return a string representation of the result.""" + + def serialize_item(item): + if isinstance(item, (TextContent, ImageContent)): + return item.model_dump() + elif isinstance(item, EmbeddedResource): + type = item.type + resource = {} + for key, val in item.resource.model_dump().items(): + if isinstance(val, AnyUrl): + resource[key] = str(val) + else: + resource[key] = val + annotations = item.annotations.model_dump() if item.annotations else None + return {"type": type, "resource": resource, "annotations": annotations} + else: + return str(item) + + return json.dumps([serialize_item(item) for item in value]) + def _format_errors(self, error: Exception) -> str: """Recursively format errors into a string.""" diff --git a/python/packages/autogen-ext/tests/tools/test_mcp_tools.py b/python/packages/autogen-ext/tests/tools/test_mcp_tools.py index 10f17c2c35e0..5d9897ac6721 100644 --- a/python/packages/autogen-ext/tests/tools/test_mcp_tools.py +++ b/python/packages/autogen-ext/tests/tools/test_mcp_tools.py @@ -16,6 +16,14 @@ mcp_server_tools, ) from mcp import ClientSession, Tool +from mcp.types import ( + AnyUrl, + Annotations, + EmbeddedResource, + ImageContent, + TextContent, + TextResourceContents, +) @pytest.fixture @@ -182,6 +190,53 @@ async def test_adapter_from_server_params( ) +@pytest.mark.asyncio +async def test_adapter_from_server_params_with_return_value_as_string( + sample_tool: Tool, + sample_server_params: StdioServerParams, + mock_session: AsyncMock, + monkeypatch: pytest.MonkeyPatch, +) -> None: + """Test that adapter can be created from server parameters.""" + mock_context = AsyncMock() + mock_context.__aenter__.return_value = mock_session + monkeypatch.setattr( + "autogen_ext.tools.mcp._base.create_mcp_server_session", + lambda *args, **kwargs: mock_context, # type: ignore + ) + + mock_session.list_tools.return_value.tools = [sample_tool] + + adapter = await StdioMcpToolAdapter.from_server_params(sample_server_params, "test_tool") + + assert ( + adapter.return_value_as_string( + [ + TextContent( + text="this is a sample text", + type="text", + annotations=Annotations(audience=["user", "assistant"], priority=0.7), + ), + ImageContent( + data="this is a sample base64 encoded image", + mimeType="image/png", + type="image", + annotations=None, + ), + EmbeddedResource( + type="resource", + resource=TextResourceContents( + text="this is a sample text", + uri=AnyUrl(url="http://example.com/test"), + ), + annotations=Annotations(audience=["user"], priority=0.3), + ), + ] + ) + == '[{"type": "text", "text": "this is a sample text", "annotations": {"audience": ["user", "assistant"], "priority": 0.7}}, {"type": "image", "data": "this is a sample base64 encoded image", "mimeType": "image/png", "annotations": null}, {"type": "resource", "resource": {"uri": "http://example.com/test", "mimeType": null, "text": "this is a sample text"}, "annotations": {"audience": ["user"], "priority": 0.3}}]' + ) + + @pytest.mark.asyncio async def test_adapter_from_factory( sample_tool: Tool, @@ -421,7 +476,8 @@ async def test_mcp_server_github() -> None: assert len(tools) == 1 tool = tools[0] result = await tool.run_json( - {"owner": "microsoft", "repo": "autogen", "path": "python", "branch": "main"}, CancellationToken() + {"owner": "microsoft", "repo": "autogen", "path": "python", "branch": "main"}, + CancellationToken(), ) assert result is not None