|
2 | 2 | import builtins |
3 | 3 | import json |
4 | 4 | from abc import ABC |
5 | | -from typing import Any, Dict, Generic, Type, TypeVar |
| 5 | +from typing import Any, Dict, Generic, Sequence, Type, TypeVar |
6 | 6 |
|
7 | 7 | from autogen_core import CancellationToken |
8 | 8 | from autogen_core.tools import BaseTool |
|
11 | 11 | from pydantic.networks import AnyUrl |
12 | 12 |
|
13 | 13 | from mcp import ClientSession, Tool |
14 | | -from mcp.types import EmbeddedResource, ImageContent, TextContent |
| 14 | +from mcp.types import AudioContent, ContentBlock, EmbeddedResource, ImageContent, ResourceLink, TextContent |
15 | 15 |
|
16 | 16 | from ._config import McpServerParams |
17 | 17 | from ._session import create_mcp_server_session |
@@ -75,21 +75,20 @@ async def run(self, args: BaseModel, cancellation_token: CancellationToken) -> A |
75 | 75 | await session.initialize() |
76 | 76 | return await self._run(args=kwargs, cancellation_token=cancellation_token, session=session) |
77 | 77 |
|
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]: |
81 | 79 | """ |
82 | 80 | 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. |
85 | 83 | - If payload is a string, it's wrapped in [TextContent(text=payload)]. |
86 | 84 | - Otherwise, the payload is stringified and wrapped in [TextContent(text=str(payload))]. |
87 | 85 | """ |
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 |
90 | 89 | ): |
91 | | - return payload |
92 | | - elif isinstance(payload, (TextContent, ImageContent, EmbeddedResource)): |
| 90 | + return list(payload) |
| 91 | + elif isinstance(payload, (TextContent, ImageContent, EmbeddedResource, AudioContent, ResourceLink)): |
93 | 92 | return [payload] |
94 | 93 | elif isinstance(payload, str): |
95 | 94 | return [TextContent(text=payload, type="text")] |
@@ -154,18 +153,37 @@ def return_value_as_string(self, value: list[Any]) -> str: |
154 | 153 | """Return a string representation of the result.""" |
155 | 154 |
|
156 | 155 | 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 |
159 | 162 | elif isinstance(item, EmbeddedResource): |
160 | 163 | type = item.type |
161 | 164 | resource = {} |
162 | 165 | 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 |
163 | 169 | if isinstance(val, AnyUrl): |
164 | 170 | resource[key] = str(val) |
165 | 171 | else: |
166 | 172 | 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 |
169 | 187 | else: |
170 | 188 | return {} |
171 | 189 |
|
|
0 commit comments