Skip to content

Commit 6f3a87a

Browse files
committed
Introduce native MCPError type and use it in MCP server resource methods.
1 parent 4e5d627 commit 6f3a87a

File tree

3 files changed

+61
-3
lines changed

3 files changed

+61
-3
lines changed

pydantic_ai_slim/pydantic_ai/exceptions.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@
2323
'UnexpectedModelBehavior',
2424
'UsageLimitExceeded',
2525
'ModelHTTPError',
26+
'MCPError',
27+
'ServerCapabilitiesError',
2628
'FallbackExceptionGroup',
2729
)
2830

@@ -158,6 +160,24 @@ def __init__(self, status_code: int, model_name: str, body: object | None = None
158160
super().__init__(message)
159161

160162

163+
class MCPError(RuntimeError):
164+
"""Base class for errors occurring during interaction with an MCP server."""
165+
166+
message: str
167+
"""The error message."""
168+
169+
def __init__(self, message: str):
170+
self.message = message
171+
super().__init__(message)
172+
173+
def __str__(self) -> str:
174+
return self.message
175+
176+
177+
class ServerCapabilitiesError(MCPError):
178+
"""Raised when attempting to access server capabilities that aren't present."""
179+
180+
161181
class FallbackExceptionGroup(ExceptionGroup):
162182
"""A group of exceptions that can be raised when all fallback models fail."""
163183

pydantic_ai_slim/pydantic_ai/mcp.py

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -319,14 +319,29 @@ async def list_resources(self) -> list[_mcp.Resource]:
319319
Note:
320320
- We don't cache resources as they might change.
321321
- We also don't subscribe to resource changes to avoid complexity.
322+
323+
Raises:
324+
ServerCapabilitiesError: If the server does not support resources.
322325
"""
323326
async with self: # Ensure server is running
327+
if not self.capabilities.resources:
328+
raise exceptions.ServerCapabilitiesError(
329+
f'Server does not support resources capability. Available capabilities: {self.capabilities}'
330+
)
324331
result = await self._client.list_resources()
325332
return [_mcp.map_from_mcp_resource(r) for r in result.resources]
326333

327334
async def list_resource_templates(self) -> list[_mcp.ResourceTemplate]:
328-
"""Retrieve resource templates that are currently present on the server."""
335+
"""Retrieve resource templates that are currently present on the server.
336+
337+
Raises:
338+
ServerCapabilitiesError: If the server does not support resources.
339+
"""
329340
async with self: # Ensure server is running
341+
if not self.capabilities.resources:
342+
raise exceptions.ServerCapabilitiesError(
343+
f'Server does not support resources capability. Available capabilities: {self.capabilities}'
344+
)
330345
result = await self._client.list_resource_templates()
331346
return [_mcp.map_from_mcp_resource_template(t) for t in result.resourceTemplates]
332347

@@ -349,9 +364,16 @@ async def read_resource(
349364
Returns:
350365
The resource contents. If the resource has a single content item, returns that item directly.
351366
If the resource has multiple content items, returns a list of items.
367+
368+
Raises:
369+
ServerCapabilitiesError: If the server does not support resources.
352370
"""
353371
resource_uri = uri if isinstance(uri, str) else uri.uri
354372
async with self: # Ensure server is running
373+
if not self.capabilities.resources:
374+
raise exceptions.ServerCapabilitiesError(
375+
f'Server does not support resources capability. Available capabilities: {self.capabilities}'
376+
)
355377
result = await self._client.read_resource(AnyUrl(resource_uri))
356378
return (
357379
self._get_content(result.contents[0])

tests/test_mcp.py

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,9 @@
2323
ToolReturnPart,
2424
UserPromptPart,
2525
)
26-
from pydantic_ai._mcp import Resource
26+
from pydantic_ai._mcp import Resource, ServerCapabilities
2727
from pydantic_ai.agent import Agent
28-
from pydantic_ai.exceptions import ModelRetry, UnexpectedModelBehavior, UserError
28+
from pydantic_ai.exceptions import ModelRetry, ServerCapabilitiesError, UnexpectedModelBehavior, UserError
2929
from pydantic_ai.mcp import MCPServerStreamableHTTP, load_mcp_servers
3030
from pydantic_ai.models import Model
3131
from pydantic_ai.models.test import TestModel
@@ -1583,3 +1583,19 @@ async def test_capabilities(mcp_server: MCPServerStdio) -> None:
15831583
assert mcp_server.capabilities.logging is True
15841584
assert mcp_server.capabilities.completions is False
15851585
assert mcp_server.capabilities.experimental is None
1586+
1587+
1588+
async def test_resource_methods_without_capability(mcp_server: MCPServerStdio) -> None:
1589+
"""Test that resource methods raise ServerCapabilitiesError when resources capability is not available."""
1590+
async with mcp_server:
1591+
# Mock the capabilities to not support resources
1592+
mock_capabilities = ServerCapabilities(resources=False)
1593+
with patch.object(mcp_server, '_server_capabilities', mock_capabilities):
1594+
with pytest.raises(ServerCapabilitiesError, match='Server does not support resources capability'):
1595+
await mcp_server.list_resources()
1596+
1597+
with pytest.raises(ServerCapabilitiesError, match='Server does not support resources capability'):
1598+
await mcp_server.list_resource_templates()
1599+
1600+
with pytest.raises(ServerCapabilitiesError, match='Server does not support resources capability'):
1601+
await mcp_server.read_resource('resource://test')

0 commit comments

Comments
 (0)