Skip to content

Commit ec9c5bc

Browse files
committed
Instantiate MCP resource related Resource dataclasses in a consistent way.
1 parent 389fe64 commit ec9c5bc

File tree

2 files changed

+68
-60
lines changed

2 files changed

+68
-60
lines changed

pydantic_ai_slim/pydantic_ai/_mcp.py

Lines changed: 1 addition & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
import base64
44
from collections.abc import Sequence
5-
from typing import TYPE_CHECKING, Literal
5+
from typing import Literal
66

77
from . import exceptions, messages
88

@@ -14,9 +14,6 @@
1414
'you can use the `mcp` optional group — `pip install "pydantic-ai-slim[mcp]"`'
1515
) from _import_error
1616

17-
if TYPE_CHECKING:
18-
from .mcp import Resource, ResourceTemplate, ServerCapabilities
19-
2017

2118
def map_from_mcp_params(params: mcp_types.CreateMessageRequestParams) -> list[messages.ModelMessage]:
2219
"""Convert from MCP create message request parameters to pydantic-ai messages."""
@@ -126,56 +123,3 @@ def map_from_sampling_content(
126123
return messages.TextPart(content=content.text)
127124
else:
128125
raise NotImplementedError('Image and Audio responses in sampling are not yet supported')
129-
130-
131-
def map_from_mcp_resource(mcp_resource: mcp_types.Resource) -> Resource:
132-
"""Convert from MCP Resource to native Pydantic AI Resource."""
133-
from .mcp import Resource, ResourceAnnotations
134-
135-
return Resource(
136-
uri=str(mcp_resource.uri),
137-
name=mcp_resource.name,
138-
title=mcp_resource.title,
139-
description=mcp_resource.description,
140-
mime_type=mcp_resource.mimeType,
141-
size=mcp_resource.size,
142-
annotations=(
143-
ResourceAnnotations(audience=mcp_resource.annotations.audience, priority=mcp_resource.annotations.priority)
144-
if mcp_resource.annotations
145-
else None
146-
),
147-
metadata=mcp_resource.meta,
148-
)
149-
150-
151-
def map_from_mcp_resource_template(mcp_template: mcp_types.ResourceTemplate) -> ResourceTemplate:
152-
"""Convert from MCP ResourceTemplate to native Pydantic AI ResourceTemplate."""
153-
from .mcp import ResourceAnnotations, ResourceTemplate
154-
155-
return ResourceTemplate(
156-
uri_template=mcp_template.uriTemplate,
157-
name=mcp_template.name,
158-
title=mcp_template.title,
159-
description=mcp_template.description,
160-
mime_type=mcp_template.mimeType,
161-
annotations=(
162-
ResourceAnnotations(audience=mcp_template.annotations.audience, priority=mcp_template.annotations.priority)
163-
if mcp_template.annotations
164-
else None
165-
),
166-
metadata=mcp_template.meta,
167-
)
168-
169-
170-
def map_from_mcp_server_capabilities(mcp_capabilities: mcp_types.ServerCapabilities) -> ServerCapabilities:
171-
"""Convert from MCP ServerCapabilities to native Pydantic AI ServerCapabilities."""
172-
from .mcp import ServerCapabilities
173-
174-
return ServerCapabilities(
175-
experimental=list(mcp_capabilities.experimental.keys()) if mcp_capabilities.experimental else None,
176-
logging=mcp_capabilities.logging is not None,
177-
prompts=mcp_capabilities.prompts is not None,
178-
resources=mcp_capabilities.resources is not None,
179-
tools=mcp_capabilities.tools is not None,
180-
completions=mcp_capabilities.completions is not None,
181-
)

pydantic_ai_slim/pydantic_ai/mcp.py

Lines changed: 67 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,15 @@ class ResourceAnnotations:
114114

115115
__repr__ = _utils.dataclasses_no_defaults_repr
116116

117+
@classmethod
118+
def from_mcp_sdk(cls, mcp_annotations: mcp_types.Annotations) -> ResourceAnnotations:
119+
"""Convert from MCP SDK Annotations to ResourceAnnotations.
120+
121+
Args:
122+
mcp_annotations: The MCP SDK annotations object.
123+
"""
124+
return cls(audience=mcp_annotations.audience, priority=mcp_annotations.priority)
125+
117126

118127
@dataclass(repr=False, kw_only=True)
119128
class BaseResource(ABC):
@@ -150,6 +159,26 @@ class Resource(BaseResource):
150159
size: int | None = None
151160
"""The size of the raw resource content in bytes (before base64 encoding), if known."""
152161

162+
@classmethod
163+
def from_mcp_sdk(cls, mcp_resource: mcp_types.Resource) -> Resource:
164+
"""Convert from MCP SDK Resource to PydanticAI Resource.
165+
166+
Args:
167+
mcp_resource: The MCP SDK Resource object.
168+
"""
169+
return cls(
170+
uri=str(mcp_resource.uri),
171+
name=mcp_resource.name,
172+
title=mcp_resource.title,
173+
description=mcp_resource.description,
174+
mime_type=mcp_resource.mimeType,
175+
size=mcp_resource.size,
176+
annotations=ResourceAnnotations.from_mcp_sdk(mcp_resource.annotations)
177+
if mcp_resource.annotations
178+
else None,
179+
metadata=mcp_resource.meta,
180+
)
181+
153182

154183
@dataclass(repr=False, kw_only=True)
155184
class ResourceTemplate(BaseResource):
@@ -158,6 +187,25 @@ class ResourceTemplate(BaseResource):
158187
uri_template: str
159188
"""URI template (RFC 6570) for constructing resource URIs."""
160189

190+
@classmethod
191+
def from_mcp_sdk(cls, mcp_template: mcp_types.ResourceTemplate) -> ResourceTemplate:
192+
"""Convert from MCP SDK ResourceTemplate to PydanticAI ResourceTemplate.
193+
194+
Args:
195+
mcp_template: The MCP SDK ResourceTemplate object.
196+
"""
197+
return cls(
198+
uri_template=mcp_template.uriTemplate,
199+
name=mcp_template.name,
200+
title=mcp_template.title,
201+
description=mcp_template.description,
202+
mime_type=mcp_template.mimeType,
203+
annotations=ResourceAnnotations.from_mcp_sdk(mcp_template.annotations)
204+
if mcp_template.annotations
205+
else None,
206+
metadata=mcp_template.meta,
207+
)
208+
161209

162210
@dataclass(repr=False, kw_only=True)
163211
class ServerCapabilities:
@@ -183,6 +231,22 @@ class ServerCapabilities:
183231

184232
__repr__ = _utils.dataclasses_no_defaults_repr
185233

234+
@classmethod
235+
def from_mcp_sdk(cls, mcp_capabilities: mcp_types.ServerCapabilities) -> ServerCapabilities:
236+
"""Convert from MCP SDK ServerCapabilities to PydanticAI ServerCapabilities.
237+
238+
Args:
239+
mcp_capabilities: The MCP SDK ServerCapabilities object.
240+
"""
241+
return cls(
242+
experimental=list(mcp_capabilities.experimental.keys()) if mcp_capabilities.experimental else None,
243+
logging=mcp_capabilities.logging is not None,
244+
prompts=mcp_capabilities.prompts is not None,
245+
resources=mcp_capabilities.resources is not None,
246+
tools=mcp_capabilities.tools is not None,
247+
completions=mcp_capabilities.completions is not None,
248+
)
249+
186250

187251
TOOL_SCHEMA_VALIDATOR = pydantic_core.SchemaValidator(
188252
schema=pydantic_core.core_schema.dict_schema(
@@ -486,7 +550,7 @@ async def list_resources(self) -> list[Resource]:
486550
result = await self._client.list_resources()
487551
except mcp_exceptions.McpError as e:
488552
raise MCPError.from_mcp_sdk_error(e) from e
489-
return [_mcp.map_from_mcp_resource(r) for r in result.resources]
553+
return [Resource.from_mcp_sdk(r) for r in result.resources]
490554

491555
async def list_resource_templates(self) -> list[ResourceTemplate]:
492556
"""Retrieve resource templates that are currently present on the server.
@@ -501,7 +565,7 @@ async def list_resource_templates(self) -> list[ResourceTemplate]:
501565
result = await self._client.list_resource_templates()
502566
except mcp_exceptions.McpError as e:
503567
raise MCPError.from_mcp_sdk_error(e) from e
504-
return [_mcp.map_from_mcp_resource_template(t) for t in result.resourceTemplates]
568+
return [ResourceTemplate.from_mcp_sdk(t) for t in result.resourceTemplates]
505569

506570
@overload
507571
async def read_resource(self, uri: str) -> str | messages.BinaryContent | list[str | messages.BinaryContent]: ...
@@ -564,7 +628,7 @@ async def __aenter__(self) -> Self:
564628
with anyio.fail_after(self.timeout):
565629
result = await self._client.initialize()
566630
self._server_info = result.serverInfo
567-
self._server_capabilities = _mcp.map_from_mcp_server_capabilities(result.capabilities)
631+
self._server_capabilities = ServerCapabilities.from_mcp_sdk(result.capabilities)
568632
self._instructions = result.instructions
569633
if log_level := self.log_level:
570634
await self._client.set_logging_level(log_level)

0 commit comments

Comments
 (0)