Skip to content

Client

PydanticAI can act as an MCP client, connecting to MCP servers to use their tools.

Install

You need to either install pydantic-ai, orpydantic-ai-slim with the mcp optional group:

pip install "pydantic-ai-slim[mcp]"
uv add "pydantic-ai-slim[mcp]"

Note

MCP integration requires Python 3.10 or higher.

Usage

PydanticAI comes with two ways to connect to MCP servers:

Examples of both are shown below; mcp-run-python is used as the MCP server in both examples.

SSE Client

MCPServerHTTP connects over HTTP using the HTTP + Server Sent Events transport to a server.

Note

MCPServerHTTP requires an MCP server to be running and accepting HTTP connections before calling agent.run_mcp_servers(). Running the server is not managed by PydanticAI.

The name "HTTP" is used since this implemented will be adapted in future to use the new Streamable HTTP currently in development.

Before creating the SSE client, we need to run the server (docs here):

terminal (run sse server)
deno run \
  -N -R=node_modules -W=node_modules --node-modules-dir=auto \
  jsr:@pydantic/mcp-run-python sse
mcp_sse_client.py
from pydantic_ai import Agent
from pydantic_ai.mcp import MCPServerHTTP

server = MCPServerHTTP(url='http://localhost:3001/sse')  # (1)!
agent = Agent('openai:gpt-4o', mcp_servers=[server])  # (2)!


async def main():
    async with agent.run_mcp_servers():  # (3)!
        result = await agent.run('How many days between 2000-01-01 and 2025-03-18?')
    print(result.output)
    #> There are 9,208 days between January 1, 2000, and March 18, 2025.
  1. Define the MCP server with the URL used to connect.
  2. Create an agent with the MCP server attached.
  3. Create a client session to connect to the server.

(This example is complete, it can be run "as is" with Python 3.10+ — you'll need to add asyncio.run(main()) to run main)

What's happening here?

  • The model is receiving the prompt "how many days between 2000-01-01 and 2025-03-18?"
  • The model decides "Oh, I've got this run_python_code tool, that will be a good way to answer this question", and writes some python code to calculate the answer.
  • The model returns a tool call
  • PydanticAI sends the tool call to the MCP server using the SSE transport
  • The model is called again with the return value of running the code
  • The model returns the final answer

You can visualise this clearly, and even see the code that's run by adding three lines of code to instrument the example with logfire:

mcp_sse_client_logfire.py
import logfire

logfire.configure()
logfire.instrument_pydantic_ai()

Will display as follows:

Logfire run python code

MCP "stdio" Server

The other transport offered by MCP is the stdio transport where the server is run as a subprocess and communicates with the client over stdin and stdout. In this case, you'd use the MCPServerStdio class.

Note

When using MCPServerStdio servers, the agent.run_mcp_servers() context manager is responsible for starting and stopping the server.

mcp_stdio_client.py
from pydantic_ai import Agent
from pydantic_ai.mcp import MCPServerStdio

server = MCPServerStdio(  # (1)!
    'deno',
    args=[
        'run',
        '-N',
        '-R=node_modules',
        '-W=node_modules',
        '--node-modules-dir=auto',
        'jsr:@pydantic/mcp-run-python',
        'stdio',
    ]
)
agent = Agent('openai:gpt-4o', mcp_servers=[server])


async def main():
    async with agent.run_mcp_servers():
        result = await agent.run('How many days between 2000-01-01 and 2025-03-18?')
    print(result.output)
    #> There are 9,208 days between January 1, 2000, and March 18, 2025.
  1. See MCP Run Python for more information.

Using Tool Prefixes to Avoid Naming Conflicts

When connecting to multiple MCP servers that might provide tools with the same name, you can use the tool_prefix parameter to avoid naming conflicts. This parameter adds a prefix to all tool names from a specific server.

How It Works

  • If tool_prefix is set, all tools from that server will be prefixed with {tool_prefix}_
  • When listing tools, the prefixed names are shown to the model
  • When calling tools, the prefix is automatically removed before sending the request to the server

This allows you to use multiple servers that might have overlapping tool names without conflicts.

Example with HTTP Server

mcp_tool_prefix_http_client.py
from pydantic_ai import Agent
from pydantic_ai.mcp import MCPServerHTTP

# Create two servers with different prefixes
weather_server = MCPServerHTTP(
    url='http://localhost:3001/sse',
    tool_prefix='weather'  # Tools will be prefixed with 'weather_'
)

calculator_server = MCPServerHTTP(
    url='http://localhost:3002/sse',
    tool_prefix='calc'  # Tools will be prefixed with 'calc_'
)

# Both servers might have a tool named 'get_data', but they'll be exposed as:
# - 'weather_get_data'
# - 'calc_get_data'
agent = Agent('openai:gpt-4o', mcp_servers=[weather_server, calculator_server])

Example with Stdio Server

mcp_tool_prefix_stdio_client.py
from pydantic_ai import Agent
from pydantic_ai.mcp import MCPServerStdio

python_server = MCPServerStdio(
    'deno',
    args=[
        'run',
        '-N',
        'jsr:@pydantic/mcp-run-python',
        'stdio',
    ],
    tool_prefix='py'  # Tools will be prefixed with 'py_'
)

js_server = MCPServerStdio(
    'node',
    args=[
        'run',
        'mcp-js-server.js',
        'stdio',
    ],
    tool_prefix='js'  # Tools will be prefixed with 'js_'
)

agent = Agent('openai:gpt-4o', mcp_servers=[python_server, js_server])

When the model interacts with these servers, it will see the prefixed tool names, but the prefixes will be automatically handled when making tool calls.