Skip to content

Commit d094a3f

Browse files
Upgrade_mcp_version (#6814)
Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com>
1 parent ca39adc commit d094a3f

4 files changed

Lines changed: 71 additions & 32 deletions

File tree

python/packages/autogen-ext/pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -150,7 +150,7 @@ semantic-kernel-all = [
150150

151151
rich = ["rich>=13.9.4"]
152152

153-
mcp = ["mcp>=1.8.1"]
153+
mcp = ["mcp>=1.11.0"]
154154
canvas = [
155155
"unidiff>=0.7.5",
156156
]

python/packages/autogen-ext/src/autogen_ext/tools/mcp/_base.py

Lines changed: 33 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
import builtins
33
import json
44
from abc import ABC
5-
from typing import Any, Dict, Generic, Type, TypeVar
5+
from typing import Any, Dict, Generic, Sequence, Type, TypeVar
66

77
from autogen_core import CancellationToken
88
from autogen_core.tools import BaseTool
@@ -11,7 +11,7 @@
1111
from pydantic.networks import AnyUrl
1212

1313
from mcp import ClientSession, Tool
14-
from mcp.types import EmbeddedResource, ImageContent, TextContent
14+
from mcp.types import AudioContent, ContentBlock, EmbeddedResource, ImageContent, ResourceLink, TextContent
1515

1616
from ._config import McpServerParams
1717
from ._session import create_mcp_server_session
@@ -75,21 +75,20 @@ async def run(self, args: BaseModel, cancellation_token: CancellationToken) -> A
7575
await session.initialize()
7676
return await self._run(args=kwargs, cancellation_token=cancellation_token, session=session)
7777

78-
def _normalize_payload_to_content_list(
79-
self, payload: list[TextContent | ImageContent | EmbeddedResource]
80-
) -> list[TextContent | ImageContent | EmbeddedResource]:
78+
def _normalize_payload_to_content_list(self, payload: Sequence[ContentBlock]) -> list[ContentBlock]:
8179
"""
8280
Normalizes a raw tool output payload into a list of content items.
83-
- If payload is already a list of (TextContent, ImageContent, EmbeddedResource), it's returned as is.
84-
- If payload is a single TextContent, ImageContent, or EmbeddedResource, it's wrapped in a list.
81+
- If payload is already a sequence of ContentBlock items, it's converted to a list and returned.
82+
- If payload is a single ContentBlock item, it's wrapped in a list.
8583
- If payload is a string, it's wrapped in [TextContent(text=payload)].
8684
- Otherwise, the payload is stringified and wrapped in [TextContent(text=str(payload))].
8785
"""
88-
if isinstance(payload, list) and all(
89-
isinstance(item, (TextContent, ImageContent, EmbeddedResource)) for item in payload
86+
if isinstance(payload, Sequence) and all(
87+
isinstance(item, (TextContent, ImageContent, EmbeddedResource, AudioContent, ResourceLink))
88+
for item in payload
9089
):
91-
return payload
92-
elif isinstance(payload, (TextContent, ImageContent, EmbeddedResource)):
90+
return list(payload)
91+
elif isinstance(payload, (TextContent, ImageContent, EmbeddedResource, AudioContent, ResourceLink)):
9392
return [payload]
9493
elif isinstance(payload, str):
9594
return [TextContent(text=payload, type="text")]
@@ -154,18 +153,37 @@ def return_value_as_string(self, value: list[Any]) -> str:
154153
"""Return a string representation of the result."""
155154

156155
def serialize_item(item: Any) -> dict[str, Any]:
157-
if isinstance(item, (TextContent, ImageContent)):
158-
return item.model_dump()
156+
if isinstance(item, (TextContent, ImageContent, AudioContent)):
157+
dumped = item.model_dump()
158+
# Remove the 'meta' field if it exists and is None (for backward compatibility)
159+
if dumped.get("meta") is None:
160+
dumped.pop("meta", None)
161+
return dumped
159162
elif isinstance(item, EmbeddedResource):
160163
type = item.type
161164
resource = {}
162165
for key, val in item.resource.model_dump().items():
166+
# Skip 'meta' field if it's None (for backward compatibility)
167+
if key == "meta" and val is None:
168+
continue
163169
if isinstance(val, AnyUrl):
164170
resource[key] = str(val)
165171
else:
166172
resource[key] = val
167-
annotations = item.annotations.model_dump() if item.annotations else None
168-
return {"type": type, "resource": resource, "annotations": annotations}
173+
dumped_annotations = item.annotations.model_dump() if item.annotations else None
174+
# Remove 'meta' from annotations if it exists and is None
175+
if dumped_annotations and dumped_annotations.get("meta") is None:
176+
dumped_annotations.pop("meta", None)
177+
return {"type": type, "resource": resource, "annotations": dumped_annotations}
178+
elif isinstance(item, ResourceLink):
179+
dumped = item.model_dump()
180+
# Remove the 'meta' field if it exists and is None (for backward compatibility)
181+
if dumped.get("meta") is None:
182+
dumped.pop("meta", None)
183+
# Convert AnyUrl to string for JSON serialization
184+
if "uri" in dumped and isinstance(dumped["uri"], AnyUrl):
185+
dumped["uri"] = str(dumped["uri"])
186+
return dumped
169187
else:
170188
return {}
171189

python/packages/autogen-ext/tests/tools/test_mcp_tools.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
Annotations,
2828
EmbeddedResource,
2929
ImageContent,
30+
ResourceLink,
3031
TextContent,
3132
TextResourceContents,
3233
)
@@ -909,3 +910,21 @@ async def test_mcp_tool_adapter_run_cancelled_during_call(
909910
await adapter._run(args=args, cancellation_token=cancellation_token, session=mock_session) # type: ignore[reportPrivateUsage]
910911

911912
mock_session.call_tool.assert_called_once_with(name=sample_tool.name, arguments=args)
913+
914+
915+
def test_return_value_as_string_with_resource_link(sample_tool: Tool, sample_server_params: StdioServerParams) -> None:
916+
"""Test return_value_as_string handles ResourceLink objects correctly."""
917+
adapter = StdioMcpToolAdapter(server_params=sample_server_params, tool=sample_tool)
918+
919+
# Test ResourceLink with meta field
920+
resource_link = ResourceLink(
921+
name="test_link",
922+
type="resource_link",
923+
uri=AnyUrl(url="http://example.com"),
924+
)
925+
926+
result = adapter.return_value_as_string([resource_link])
927+
# Verify the JSON serialization contains expected fields
928+
assert '"type": "resource_link"' in result
929+
assert '"name": "test_link"' in result
930+
assert '"uri": "http://example.com/"' in result # AnyUrl normalizes with trailing slash

python/uv.lock

Lines changed: 18 additions & 16 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)