Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
9fde764
Add initial list_resources() method to MCPServer.
fennb Sep 29, 2025
a7404a2
Add list_resource_templates() to MCPServer.
fennb Sep 29, 2025
ecea466
Workaround limitations with inline-snapshot & AnyUrl.
fennb Sep 29, 2025
1886299
Add basic read_resource() to MCPServer.
fennb Sep 29, 2025
dfb54db
Update mcp client docs to add Resources section.
fennb Sep 29, 2025
f52bb71
Organize imports in doc examples.
fennb Sep 29, 2025
232ff04
Update MCP resource example docs.
fennb Sep 29, 2025
38675da
Merge branch 'pydantic:main' into feature/1783-minimal-mcp-resources-…
fennb Oct 6, 2025
317416c
Create native Resource and ResourceTemplate types and update MCPServe…
fennb Oct 6, 2025
67f9b07
Update MCPServer.read_resource() to decode/return native types.
fennb Oct 6, 2025
e6cb086
Add native MCP ResourceAnnotations type.
fennb Oct 6, 2025
b8424d8
Allow MCPServer.read_resource() to read resources by Resource.
fennb Oct 6, 2025
50cab26
Merge branch 'main' into feature/1783-minimal-mcp-resources-support
fennb Oct 16, 2025
6857ef2
Bump dependency mcp>=1.18.0
fennb Oct 18, 2025
18743cd
Add test coverage for ResourceAnnotations now that we can.
fennb Oct 18, 2025
4e5d627
Add MCPServer.capabilities property.
fennb Oct 19, 2025
6f3a87a
Introduce native MCPError type and use it in MCP server resource meth…
fennb Oct 19, 2025
72d66ac
Simplify error messages.
fennb Oct 19, 2025
3000511
Add MCPServerError and use it appropriately upon upstream resource er…
fennb Oct 19, 2025
30f4e7f
Cleanup MCP error naming and usage.
fennb Oct 19, 2025
456e714
Increase MCP server test coverage.
fennb Oct 19, 2025
3d95521
Fix test coverage gap.
fennb Oct 19, 2025
918f85c
Merge branch 'main' into feature/1783-minimal-mcp-resources-support
fennb Oct 30, 2025
28cf761
Return [] | None on MCP resource methods when not found or no capabil…
fennb Nov 1, 2025
9804f18
Cleanup + relocate MCP errors.
fennb Nov 1, 2025
d173f88
Merge branch 'pydantic:main' into feature/1783-minimal-mcp-resources-…
fennb Nov 3, 2025
f7c5319
Move public MCP resources to appropriate place.
fennb Nov 3, 2025
21ad2b0
Improve clarity of MCP Resource handling documentation.
fennb Nov 3, 2025
14c3973
Test cleanups.
fennb Nov 3, 2025
44c327a
Improve type safety on MCPError.
fennb Nov 1, 2025
7346061
Update docs/mcp/client.md
fennb Nov 5, 2025
b9216b4
Update MCP resources docs example.
fennb Nov 5, 2025
3d47349
Docs cleanup.
fennb Nov 5, 2025
278d141
Cleanup MCP resource tests.
fennb Nov 5, 2025
2445540
Make error handing in MCP ResourceLink reading consistent.
fennb Nov 5, 2025
eb31106
Merge branch 'main' into feature/1783-minimal-mcp-resources-support
fennb Nov 5, 2025
196d139
Merge branch 'main' into feature/1783-minimal-mcp-resources-support
DouweM Nov 5, 2025
94f91ec
Merge branch 'main' into feature/1783-minimal-mcp-resources-support
DouweM Nov 5, 2025
509d6ef
Use future import annotations for cleaner syntax.
fennb Nov 6, 2025
a9e10cf
Merge branch 'main' into feature/1783-minimal-mcp-resources-support
fennb Nov 6, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
61 changes: 61 additions & 0 deletions docs/mcp/client.md
Original file line number Diff line number Diff line change
Expand Up @@ -311,6 +311,67 @@ agent = Agent('openai:gpt-5', toolsets=[weather_server, calculator_server])

MCP tools can include metadata that provides additional information about the tool's characteristics, which can be useful when [filtering tools][pydantic_ai.toolsets.FilteredToolset]. The `meta`, `annotations`, and `output_schema` fields can be found on the `metadata` dict on the [`ToolDefinition`][pydantic_ai.tools.ToolDefinition] object that's passed to filter functions.

## Resources

MCP servers can provide [resources](https://modelcontextprotocol.io/docs/concepts/resources) - files, data, or content that can be accessed by the client. Resources in MCP are application-driven, with host applications determining how to incorporate context manually, based on their needs. This means they will _not_ be exposed to the LLM automatically (unless a tool returns a `ResourceLink` or `EmbeddedResource`).

Pydantic AI provides methods to discover and read resources from MCP servers:

- [`list_resources()`][pydantic_ai.mcp.MCPServer.list_resources] - List all available resources on the server
- [`list_resource_templates()`][pydantic_ai.mcp.MCPServer.list_resource_templates] - List resource templates with parameter placeholders
- [`read_resource(uri)`][pydantic_ai.mcp.MCPServer.read_resource] - Read the contents of a specific resource by URI

Resources are automatically converted: text content is returned as `str`, and binary content is returned as [`BinaryContent`][pydantic_ai.messages.BinaryContent].

Before consuming resources, we need to run a server that exposes some:

```python {title="mcp_resource_server.py"}
from mcp.server.fastmcp import FastMCP

mcp = FastMCP('Pydantic AI MCP Server')
log_level = 'unset'


@mcp.resource('resource://user_name.txt', mime_type='text/plain')
async def user_name_resource() -> str:
return 'Alice'


if __name__ == '__main__':
mcp.run()
```

Then we can create the client:

```python {title="mcp_resources.py", requires="mcp_resource_server.py"}
import asyncio

from pydantic_ai.mcp import MCPServerStdio


async def main():
server = MCPServerStdio('python', args=['-m', 'mcp_resource_server'])

async with server:
# List all available resources
resources = await server.list_resources()
for resource in resources:
print(f' - {resource.name}: {resource.uri} ({resource.mime_type})')
#> - user_name_resource: resource://user_name.txt (text/plain)

# Read a text resource
user_name = await server.read_resource('resource://user_name.txt')
print(f'Text content: {user_name}')
#> Text content: Alice


if __name__ == '__main__':
asyncio.run(main())
```

_(This example is complete, it can be run "as is")_


## Custom TLS / SSL configuration

In some environments you need to tweak how HTTPS connections are established –
Expand Down
60 changes: 59 additions & 1 deletion pydantic_ai_slim/pydantic_ai/_mcp.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
from __future__ import annotations

import base64
from collections.abc import Sequence
from typing import Literal
from typing import TYPE_CHECKING, Literal

from . import exceptions, messages

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

if TYPE_CHECKING:
from .mcp import Resource, ResourceTemplate, ServerCapabilities
Comment on lines +17 to +18
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it makes more sense to define those here.

There's a weird local import on most of the functions in this file.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually... Do the methods here need to be functions here?

Since they are just transformations, can't they live in the dataclasses itself? e.g. instead of map_from_mcp_resource, you could do Resource.from_mcp_resource(mcp_resource).

Copy link
Author

@fennb fennb Nov 6, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep, totally. I originally implemented them like so mostly as that was (roughly) the pattern that already existed in that file but I actually prefer Resource.from_mcp_resource(mcp_resource) too. Will change.



def map_from_mcp_params(params: mcp_types.CreateMessageRequestParams) -> list[messages.ModelMessage]:
"""Convert from MCP create message request parameters to pydantic-ai messages."""
Expand Down Expand Up @@ -121,3 +126,56 @@ def map_from_sampling_content(
return messages.TextPart(content=content.text)
else:
raise NotImplementedError('Image and Audio responses in sampling are not yet supported')


def map_from_mcp_resource(mcp_resource: mcp_types.Resource) -> Resource:
"""Convert from MCP Resource to native Pydantic AI Resource."""
from .mcp import Resource, ResourceAnnotations

return Resource(
uri=str(mcp_resource.uri),
name=mcp_resource.name,
title=mcp_resource.title,
description=mcp_resource.description,
mime_type=mcp_resource.mimeType,
size=mcp_resource.size,
annotations=(
ResourceAnnotations(audience=mcp_resource.annotations.audience, priority=mcp_resource.annotations.priority)
if mcp_resource.annotations
else None
),
metadata=mcp_resource.meta,
)


def map_from_mcp_resource_template(mcp_template: mcp_types.ResourceTemplate) -> ResourceTemplate:
"""Convert from MCP ResourceTemplate to native Pydantic AI ResourceTemplate."""
from .mcp import ResourceAnnotations, ResourceTemplate

return ResourceTemplate(
uri_template=mcp_template.uriTemplate,
name=mcp_template.name,
title=mcp_template.title,
description=mcp_template.description,
mime_type=mcp_template.mimeType,
annotations=(
ResourceAnnotations(audience=mcp_template.annotations.audience, priority=mcp_template.annotations.priority)
if mcp_template.annotations
else None
),
metadata=mcp_template.meta,
)


def map_from_mcp_server_capabilities(mcp_capabilities: mcp_types.ServerCapabilities) -> ServerCapabilities:
"""Convert from MCP ServerCapabilities to native Pydantic AI ServerCapabilities."""
from .mcp import ServerCapabilities

return ServerCapabilities(
experimental=list(mcp_capabilities.experimental.keys()) if mcp_capabilities.experimental else None,
logging=mcp_capabilities.logging is not None,
prompts=mcp_capabilities.prompts is not None,
resources=mcp_capabilities.resources is not None,
tools=mcp_capabilities.tools is not None,
completions=mcp_capabilities.completions is not None,
)
Loading
Loading