Skip to content

Commit f7c5319

Browse files
committed
Move public MCP resources to appropriate place.
1 parent d173f88 commit f7c5319

File tree

3 files changed

+108
-101
lines changed

3 files changed

+108
-101
lines changed

pydantic_ai_slim/pydantic_ai/_mcp.py

Lines changed: 15 additions & 92 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,8 @@
11
import base64
2-
from abc import ABC
32
from collections.abc import Sequence
4-
from dataclasses import dataclass
5-
from typing import Annotated, Any, Literal
3+
from typing import TYPE_CHECKING, Literal
64

7-
from pydantic import Field
8-
9-
from . import _utils, exceptions, messages
5+
from . import exceptions, messages
106

117
try:
128
from mcp import types as mcp_types
@@ -16,87 +12,8 @@
1612
'you can use the `mcp` optional group — `pip install "pydantic-ai-slim[mcp]"`'
1713
) from _import_error
1814

19-
20-
@dataclass(repr=False, kw_only=True)
21-
class ResourceAnnotations:
22-
"""Additional properties describing MCP entities."""
23-
24-
audience: list[mcp_types.Role] | None = None
25-
"""Intended audience for this entity."""
26-
27-
priority: Annotated[float, Field(ge=0.0, le=1.0)] | None = None
28-
"""Priority level for this entity, ranging from 0.0 to 1.0."""
29-
30-
__repr__ = _utils.dataclasses_no_defaults_repr
31-
32-
33-
@dataclass(repr=False, kw_only=True)
34-
class BaseResource(ABC):
35-
"""Base class for MCP resources."""
36-
37-
name: str
38-
"""The programmatic name of the resource."""
39-
40-
title: str | None = None
41-
"""Human-readable title for UI contexts."""
42-
43-
description: str | None = None
44-
"""A description of what this resource represents."""
45-
46-
mime_type: str | None = None
47-
"""The MIME type of the resource, if known."""
48-
49-
annotations: ResourceAnnotations | None = None
50-
"""Optional annotations for the resource."""
51-
52-
meta: dict[str, Any] | None = None
53-
"""Optional metadata for the resource."""
54-
55-
__repr__ = _utils.dataclasses_no_defaults_repr
56-
57-
58-
@dataclass(repr=False, kw_only=True)
59-
class Resource(BaseResource):
60-
"""A resource that can be read from an MCP server."""
61-
62-
uri: str
63-
"""The URI of the resource."""
64-
65-
size: int | None = None
66-
"""The size of the raw resource content in bytes (before base64 encoding), if known."""
67-
68-
69-
@dataclass(repr=False, kw_only=True)
70-
class ResourceTemplate(BaseResource):
71-
"""A template for parameterized resources on an MCP server."""
72-
73-
uri_template: str
74-
"""URI template (RFC 6570) for constructing resource URIs."""
75-
76-
77-
@dataclass(repr=False, kw_only=True)
78-
class ServerCapabilities:
79-
"""Capabilities that an MCP server supports."""
80-
81-
experimental: list[str] | None = None
82-
"""Experimental, non-standard capabilities that the server supports."""
83-
84-
logging: bool = False
85-
"""Whether the server supports sending log messages to the client."""
86-
87-
prompts: bool = False
88-
"""Whether the server offers any prompt templates."""
89-
90-
resources: bool = False
91-
"""Whether the server offers any resources to read."""
92-
93-
tools: bool = False
94-
"""Whether the server offers any tools to call."""
95-
96-
completions: bool = False
97-
"""Whether the server offers autocompletion suggestions for prompts and resources."""
98-
99-
__repr__ = _utils.dataclasses_no_defaults_repr
15+
if TYPE_CHECKING:
16+
from .mcp import Resource, ResourceTemplate, ServerCapabilities
10017

10118

10219
def map_from_mcp_params(params: mcp_types.CreateMessageRequestParams) -> list[messages.ModelMessage]:
@@ -209,8 +126,10 @@ def map_from_sampling_content(
209126
raise NotImplementedError('Image and Audio responses in sampling are not yet supported')
210127

211128

212-
def map_from_mcp_resource(mcp_resource: mcp_types.Resource) -> Resource:
129+
def map_from_mcp_resource(mcp_resource: mcp_types.Resource) -> 'Resource':
213130
"""Convert from MCP Resource to native Pydantic AI Resource."""
131+
from .mcp import Resource, ResourceAnnotations
132+
214133
return Resource(
215134
uri=str(mcp_resource.uri),
216135
name=mcp_resource.name,
@@ -223,12 +142,14 @@ def map_from_mcp_resource(mcp_resource: mcp_types.Resource) -> Resource:
223142
if mcp_resource.annotations
224143
else None
225144
),
226-
meta=mcp_resource.meta,
145+
metadata=mcp_resource.meta,
227146
)
228147

229148

230-
def map_from_mcp_resource_template(mcp_template: mcp_types.ResourceTemplate) -> ResourceTemplate:
149+
def map_from_mcp_resource_template(mcp_template: mcp_types.ResourceTemplate) -> 'ResourceTemplate':
231150
"""Convert from MCP ResourceTemplate to native Pydantic AI ResourceTemplate."""
151+
from .mcp import ResourceAnnotations, ResourceTemplate
152+
232153
return ResourceTemplate(
233154
uri_template=mcp_template.uriTemplate,
234155
name=mcp_template.name,
@@ -240,12 +161,14 @@ def map_from_mcp_resource_template(mcp_template: mcp_types.ResourceTemplate) ->
240161
if mcp_template.annotations
241162
else None
242163
),
243-
meta=mcp_template.meta,
164+
metadata=mcp_template.meta,
244165
)
245166

246167

247-
def map_from_mcp_server_capabilities(mcp_capabilities: mcp_types.ServerCapabilities) -> ServerCapabilities:
168+
def map_from_mcp_server_capabilities(mcp_capabilities: mcp_types.ServerCapabilities) -> 'ServerCapabilities':
248169
"""Convert from MCP ServerCapabilities to native Pydantic AI ServerCapabilities."""
170+
from .mcp import ServerCapabilities
171+
249172
return ServerCapabilities(
250173
experimental=list(mcp_capabilities.experimental.keys()) if mcp_capabilities.experimental else None,
251174
logging=mcp_capabilities.logging is not None,

pydantic_ai_slim/pydantic_ai/mcp.py

Lines changed: 92 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
from asyncio import Lock
88
from collections.abc import AsyncIterator, Awaitable, Callable, Sequence
99
from contextlib import AbstractAsyncContextManager, AsyncExitStack, asynccontextmanager
10-
from dataclasses import field, replace
10+
from dataclasses import dataclass, field, replace
1111
from datetime import timedelta
1212
from pathlib import Path
1313
from typing import Annotated, Any, overload
@@ -51,6 +51,9 @@
5151
'MCPServerStreamableHTTP',
5252
'load_mcp_servers',
5353
'MCPError',
54+
'Resource',
55+
'ResourceTemplate',
56+
'ServerCapabilities',
5457
)
5558

5659

@@ -96,6 +99,88 @@ def __str__(self) -> str:
9699
return f'{self.message} (code: {self.code})'
97100

98101

102+
@dataclass(repr=False, kw_only=True)
103+
class ResourceAnnotations:
104+
"""Additional properties describing MCP entities."""
105+
106+
audience: list[mcp_types.Role] | None = None
107+
"""Intended audience for this entity."""
108+
109+
priority: Annotated[float, Field(ge=0.0, le=1.0)] | None = None
110+
"""Priority level for this entity, ranging from 0.0 to 1.0."""
111+
112+
__repr__ = _utils.dataclasses_no_defaults_repr
113+
114+
115+
@dataclass(repr=False, kw_only=True)
116+
class BaseResource(ABC):
117+
"""Base class for MCP resources."""
118+
119+
name: str
120+
"""The programmatic name of the resource."""
121+
122+
title: str | None = None
123+
"""Human-readable title for UI contexts."""
124+
125+
description: str | None = None
126+
"""A description of what this resource represents."""
127+
128+
mime_type: str | None = None
129+
"""The MIME type of the resource, if known."""
130+
131+
annotations: ResourceAnnotations | None = None
132+
"""Optional annotations for the resource."""
133+
134+
metadata: dict[str, Any] | None = None
135+
"""Optional metadata for the resource."""
136+
137+
__repr__ = _utils.dataclasses_no_defaults_repr
138+
139+
140+
@dataclass(repr=False, kw_only=True)
141+
class Resource(BaseResource):
142+
"""A resource that can be read from an MCP server."""
143+
144+
uri: str
145+
"""The URI of the resource."""
146+
147+
size: int | None = None
148+
"""The size of the raw resource content in bytes (before base64 encoding), if known."""
149+
150+
151+
@dataclass(repr=False, kw_only=True)
152+
class ResourceTemplate(BaseResource):
153+
"""A template for parameterized resources on an MCP server."""
154+
155+
uri_template: str
156+
"""URI template (RFC 6570) for constructing resource URIs."""
157+
158+
159+
@dataclass(repr=False, kw_only=True)
160+
class ServerCapabilities:
161+
"""Capabilities that an MCP server supports."""
162+
163+
experimental: list[str] | None = None
164+
"""Experimental, non-standard capabilities that the server supports."""
165+
166+
logging: bool = False
167+
"""Whether the server supports sending log messages to the client."""
168+
169+
prompts: bool = False
170+
"""Whether the server offers any prompt templates."""
171+
172+
resources: bool = False
173+
"""Whether the server offers any resources to read."""
174+
175+
tools: bool = False
176+
"""Whether the server offers any tools to call."""
177+
178+
completions: bool = False
179+
"""Whether the server offers autocompletion suggestions for prompts and resources."""
180+
181+
__repr__ = _utils.dataclasses_no_defaults_repr
182+
183+
99184
TOOL_SCHEMA_VALIDATOR = pydantic_core.SchemaValidator(
100185
schema=pydantic_core.core_schema.dict_schema(
101186
pydantic_core.core_schema.str_schema(), pydantic_core.core_schema.any_schema()
@@ -164,7 +249,7 @@ class MCPServer(AbstractToolset[Any], ABC):
164249
_read_stream: MemoryObjectReceiveStream[SessionMessage | Exception]
165250
_write_stream: MemoryObjectSendStream[SessionMessage]
166251
_server_info: mcp_types.Implementation
167-
_server_capabilities: _mcp.ServerCapabilities
252+
_server_capabilities: ServerCapabilities
168253

169254
def __init__(
170255
self,
@@ -244,7 +329,7 @@ def server_info(self) -> mcp_types.Implementation:
244329
return self._server_info
245330

246331
@property
247-
def capabilities(self) -> _mcp.ServerCapabilities:
332+
def capabilities(self) -> ServerCapabilities:
248333
"""Access the capabilities advertised by the MCP server during initialization."""
249334
if getattr(self, '_server_capabilities', None) is None:
250335
raise AttributeError(
@@ -364,7 +449,7 @@ def tool_for_tool_def(self, tool_def: ToolDefinition) -> ToolsetTool[Any]:
364449
args_validator=TOOL_SCHEMA_VALIDATOR,
365450
)
366451

367-
async def list_resources(self) -> list[_mcp.Resource]:
452+
async def list_resources(self) -> list[Resource]:
368453
"""Retrieve resources that are currently present on the server.
369454
370455
Note:
@@ -383,7 +468,7 @@ async def list_resources(self) -> list[_mcp.Resource]:
383468
raise MCPError.from_mcp_sdk_error(e) from e
384469
return [_mcp.map_from_mcp_resource(r) for r in result.resources]
385470

386-
async def list_resource_templates(self) -> list[_mcp.ResourceTemplate]:
471+
async def list_resource_templates(self) -> list[ResourceTemplate]:
387472
"""Retrieve resource templates that are currently present on the server.
388473
389474
Raises:
@@ -405,11 +490,11 @@ async def read_resource(
405490

406491
@overload
407492
async def read_resource(
408-
self, uri: _mcp.Resource
493+
self, uri: Resource
409494
) -> str | messages.BinaryContent | list[str | messages.BinaryContent] | None: ...
410495

411496
async def read_resource(
412-
self, uri: str | _mcp.Resource
497+
self, uri: str | Resource
413498
) -> str | messages.BinaryContent | list[str | messages.BinaryContent] | None:
414499
"""Read the contents of a specific resource by URI.
415500

tests/test_mcp.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,14 +23,13 @@
2323
ToolReturnPart,
2424
UserPromptPart,
2525
)
26-
from pydantic_ai._mcp import Resource, ServerCapabilities
2726
from pydantic_ai.agent import Agent
2827
from pydantic_ai.exceptions import (
2928
ModelRetry,
3029
UnexpectedModelBehavior,
3130
UserError,
3231
)
33-
from pydantic_ai.mcp import MCPError, MCPServerStreamableHTTP, load_mcp_servers
32+
from pydantic_ai.mcp import MCPError, MCPServerStreamableHTTP, Resource, ServerCapabilities, load_mcp_servers
3433
from pydantic_ai.models import Model
3534
from pydantic_ai.models.test import TestModel
3635
from pydantic_ai.tools import RunContext

0 commit comments

Comments
 (0)