[ Skip to content ](https://ai.pydantic.dev/mcp/client/#client)
**Join us at the inaugural PyAI Conf in San Francisco on March 10th![Learn More](https://pyai.events/?utm_source=pydantic-ai) **
[ ![logo](https://ai.pydantic.dev/img/logo-white.svg) ](https://ai.pydantic.dev/ "Pydantic AI")
Pydantic AI
Client
Type to start searching
[ pydantic/pydantic-ai
  * v1.63.0
  * 15.1k
  * 1.7k

](https://github.com/pydantic/pydantic-ai "Go to repository")
[ ![logo](https://ai.pydantic.dev/img/logo-white.svg) ](https://ai.pydantic.dev/ "Pydantic AI") Pydantic AI
[ pydantic/pydantic-ai
  * v1.63.0
  * 15.1k
  * 1.7k

](https://github.com/pydantic/pydantic-ai "Go to repository")
  * [ Pydantic AI  ](https://ai.pydantic.dev/)
  * [ Installation  ](https://ai.pydantic.dev/install/)
  * [ Getting Help  ](https://ai.pydantic.dev/help/)
  * [ Troubleshooting  ](https://ai.pydantic.dev/troubleshooting/)
  * [ Pydantic AI Gateway  ](https://ai.pydantic.dev/gateway/)
  * Documentation
    * Core Concepts
      * [ Agents  ](https://ai.pydantic.dev/agent/)
      * [ Dependencies  ](https://ai.pydantic.dev/dependencies/)
      * [ Function Tools  ](https://ai.pydantic.dev/tools/)
      * [ Output  ](https://ai.pydantic.dev/output/)
      * [ Messages and chat history  ](https://ai.pydantic.dev/message-history/)
      * [ Direct Model Requests  ](https://ai.pydantic.dev/direct/)
    * Models & Providers
      * [ Overview  ](https://ai.pydantic.dev/models/overview/)
      * [ OpenAI  ](https://ai.pydantic.dev/models/openai/)
      * [ Anthropic  ](https://ai.pydantic.dev/models/anthropic/)
      * [ Google  ](https://ai.pydantic.dev/models/google/)
      * [ xAI  ](https://ai.pydantic.dev/models/xai/)
      * [ Bedrock  ](https://ai.pydantic.dev/models/bedrock/)
      * [ Cerebras  ](https://ai.pydantic.dev/models/cerebras/)
      * [ Cohere  ](https://ai.pydantic.dev/models/cohere/)
      * [ Groq  ](https://ai.pydantic.dev/models/groq/)
      * [ Hugging Face  ](https://ai.pydantic.dev/models/huggingface/)
      * [ Mistral  ](https://ai.pydantic.dev/models/mistral/)
      * [ OpenRouter  ](https://ai.pydantic.dev/models/openrouter/)
      * [ Outlines  ](https://ai.pydantic.dev/models/outlines/)
    * Tools & Toolsets
      * [ Function Tools  ](https://ai.pydantic.dev/tools/)
      * [ Advanced Tool Features  ](https://ai.pydantic.dev/tools-advanced/)
      * [ Toolsets  ](https://ai.pydantic.dev/toolsets/)
      * [ Deferred Tools  ](https://ai.pydantic.dev/deferred-tools/)
      * [ Built-in Tools  ](https://ai.pydantic.dev/builtin-tools/)
      * [ Common Tools  ](https://ai.pydantic.dev/common-tools/)
      * [ Third-Party Tools  ](https://ai.pydantic.dev/third-party-tools/)
    * Advanced Features
      * [ Image, Audio, Video & Document Input  ](https://ai.pydantic.dev/input/)
      * [ Thinking  ](https://ai.pydantic.dev/thinking/)
      * [ HTTP Request Retries  ](https://ai.pydantic.dev/retries/)
    * MCP
      * [ Overview  ](https://ai.pydantic.dev/mcp/overview/)
      * Client  [ Client  ](https://ai.pydantic.dev/mcp/client/)
        * [ Install  ](https://ai.pydantic.dev/mcp/client/#install)
        * [ Usage  ](https://ai.pydantic.dev/mcp/client/#usage)
          * [ Streamable HTTP Client  ](https://ai.pydantic.dev/mcp/client/#streamable-http-client)
          * [ SSE Client  ](https://ai.pydantic.dev/mcp/client/#sse-client)
          * [ MCP "stdio" Server  ](https://ai.pydantic.dev/mcp/client/#mcp-stdio-server)
        * [ Loading MCP Servers from Configuration  ](https://ai.pydantic.dev/mcp/client/#loading-mcp-servers-from-configuration)
          * [ Configuration Format  ](https://ai.pydantic.dev/mcp/client/#configuration-format)
          * [ Environment Variables  ](https://ai.pydantic.dev/mcp/client/#environment-variables)
          * [ Usage  ](https://ai.pydantic.dev/mcp/client/#usage_1)
        * [ Tool call customization  ](https://ai.pydantic.dev/mcp/client/#tool-call-customization)
        * [ Using Tool Prefixes to Avoid Naming Conflicts  ](https://ai.pydantic.dev/mcp/client/#using-tool-prefixes-to-avoid-naming-conflicts)
        * [ Server Instructions  ](https://ai.pydantic.dev/mcp/client/#server-instructions)
        * [ Tool metadata  ](https://ai.pydantic.dev/mcp/client/#tool-metadata)
        * [ Resources  ](https://ai.pydantic.dev/mcp/client/#resources)
        * [ Custom TLS / SSL configuration  ](https://ai.pydantic.dev/mcp/client/#custom-tls-ssl-configuration)
        * [ Client Identification  ](https://ai.pydantic.dev/mcp/client/#client-identification)
        * [ MCP Sampling  ](https://ai.pydantic.dev/mcp/client/#mcp-sampling)
        * [ Elicitation  ](https://ai.pydantic.dev/mcp/client/#elicitation)
          * [ How Elicitation works  ](https://ai.pydantic.dev/mcp/client/#how-elicitation-works)
          * [ Setting up Elicitation  ](https://ai.pydantic.dev/mcp/client/#setting-up-elicitation)
          * [ Supported Schema Types  ](https://ai.pydantic.dev/mcp/client/#supported-schema-types)
          * [ Security  ](https://ai.pydantic.dev/mcp/client/#security)
      * [ FastMCP Client  ](https://ai.pydantic.dev/mcp/fastmcp-client/)
      * [ Server  ](https://ai.pydantic.dev/mcp/server/)
    * [ Multi-Agent Patterns  ](https://ai.pydantic.dev/multi-agent-applications/)
    * [ Web Chat UI  ](https://ai.pydantic.dev/web/)
    * [ Embeddings  ](https://ai.pydantic.dev/embeddings/)
    * [ Testing  ](https://ai.pydantic.dev/testing/)
  * Pydantic Evals
    * [ Overview  ](https://ai.pydantic.dev/evals/)
    * Getting Started
      * [ Quick Start  ](https://ai.pydantic.dev/evals/quick-start/)
      * [ Core Concepts  ](https://ai.pydantic.dev/evals/core-concepts/)
    * Evaluators
      * [ Overview  ](https://ai.pydantic.dev/evals/evaluators/overview/)
      * [ Built-in Evaluators  ](https://ai.pydantic.dev/evals/evaluators/built-in/)
      * [ LLM Judge  ](https://ai.pydantic.dev/evals/evaluators/llm-judge/)
      * [ Custom Evaluators  ](https://ai.pydantic.dev/evals/evaluators/custom/)
      * [ Report Evaluators  ](https://ai.pydantic.dev/evals/evaluators/report-evaluators/)
      * [ Span-Based  ](https://ai.pydantic.dev/evals/evaluators/span-based/)
    * How-To Guides
      * [ Logfire Integration  ](https://ai.pydantic.dev/evals/how-to/logfire-integration/)
      * [ Dataset Management  ](https://ai.pydantic.dev/evals/how-to/dataset-management/)
      * [ Dataset Serialization  ](https://ai.pydantic.dev/evals/how-to/dataset-serialization/)
      * [ Concurrency & Performance  ](https://ai.pydantic.dev/evals/how-to/concurrency/)
      * [ Multi-Run Evaluation  ](https://ai.pydantic.dev/evals/how-to/multi-run/)
      * [ Retry Strategies  ](https://ai.pydantic.dev/evals/how-to/retry-strategies/)
      * [ Metrics & Attributes  ](https://ai.pydantic.dev/evals/how-to/metrics-attributes/)
    * Examples
      * [ Simple Validation  ](https://ai.pydantic.dev/evals/examples/simple-validation/)
  * Pydantic Graph
    * [ Overview  ](https://ai.pydantic.dev/graph/)
    * [ Beta API  ](https://ai.pydantic.dev/graph/beta/)
      * [ Steps  ](https://ai.pydantic.dev/graph/beta/steps/)
      * [ Joins & Reducers  ](https://ai.pydantic.dev/graph/beta/joins/)
      * [ Decisions  ](https://ai.pydantic.dev/graph/beta/decisions/)
      * [ Parallel Execution  ](https://ai.pydantic.dev/graph/beta/parallel/)
  * Integrations
    * [ Debugging & Monitoring with Pydantic Logfire  ](https://ai.pydantic.dev/logfire/)
    * Durable Execution
      * [ Overview  ](https://ai.pydantic.dev/durable_execution/overview/)
      * [ Temporal  ](https://ai.pydantic.dev/durable_execution/temporal/)
      * [ DBOS  ](https://ai.pydantic.dev/durable_execution/dbos/)
      * [ Prefect  ](https://ai.pydantic.dev/durable_execution/prefect/)
    * UI Event Streams
      * [ Overview  ](https://ai.pydantic.dev/ui/overview/)
      * [ AG-UI  ](https://ai.pydantic.dev/ui/ag-ui/)
      * [ Vercel AI  ](https://ai.pydantic.dev/ui/vercel-ai/)
    * [ Agent2Agent (A2A)  ](https://ai.pydantic.dev/a2a/)
  * Related Packages
    * [ clai  ](https://ai.pydantic.dev/cli/)
  * Examples
    * [ Setup  ](https://ai.pydantic.dev/examples/setup/)
    * Getting Started
      * [ Pydantic Model  ](https://ai.pydantic.dev/examples/pydantic-model/)
      * [ Weather agent  ](https://ai.pydantic.dev/examples/weather-agent/)
    * Conversational Agents
      * [ Chat App with FastAPI  ](https://ai.pydantic.dev/examples/chat-app/)
      * [ Bank support  ](https://ai.pydantic.dev/examples/bank-support/)
    * Data & Analytics
      * [ SQL Generation  ](https://ai.pydantic.dev/examples/sql-gen/)
      * [ Data Analyst  ](https://ai.pydantic.dev/examples/data-analyst/)
      * [ RAG  ](https://ai.pydantic.dev/examples/rag/)
    * Streaming
      * [ Stream markdown  ](https://ai.pydantic.dev/examples/stream-markdown/)
      * [ Stream whales  ](https://ai.pydantic.dev/examples/stream-whales/)
    * Complex Workflows
      * [ Flight booking  ](https://ai.pydantic.dev/examples/flight-booking/)
      * [ Question Graph  ](https://ai.pydantic.dev/examples/question-graph/)
    * Business Applications
      * [ Slack Lead Qualifier with Modal  ](https://ai.pydantic.dev/examples/slack-lead-qualifier/)
    * UI Examples
      * [ Agent User Interaction (AG-UI)  ](https://ai.pydantic.dev/examples/ag-ui/)
  * API Reference
    * pydantic_ai
      * [ pydantic_ai.ag_ui  ](https://ai.pydantic.dev/api/ag_ui/)
      * [ pydantic_ai.agent  ](https://ai.pydantic.dev/api/agent/)
      * [ pydantic_ai.builtin_tools  ](https://ai.pydantic.dev/api/builtin_tools/)
      * [ pydantic_ai.common_tools  ](https://ai.pydantic.dev/api/common_tools/)
      * [ pydantic_ai — Concurrency  ](https://ai.pydantic.dev/api/concurrency/)
      * [ pydantic_ai.direct  ](https://ai.pydantic.dev/api/direct/)
      * [ pydantic_ai.durable_exec  ](https://ai.pydantic.dev/api/durable_exec/)
      * [ pydantic_ai.embeddings  ](https://ai.pydantic.dev/api/embeddings/)
      * [ pydantic_ai.exceptions  ](https://ai.pydantic.dev/api/exceptions/)
      * [ pydantic_ai.ext  ](https://ai.pydantic.dev/api/ext/)
      * [ pydantic_ai.format_prompt  ](https://ai.pydantic.dev/api/format_prompt/)
      * [ pydantic_ai.mcp  ](https://ai.pydantic.dev/api/mcp/)
      * [ pydantic_ai.messages  ](https://ai.pydantic.dev/api/messages/)
      * [ pydantic_ai.models.anthropic  ](https://ai.pydantic.dev/api/models/anthropic/)
      * [ pydantic_ai.models  ](https://ai.pydantic.dev/api/models/base/)
      * [ pydantic_ai.models.bedrock  ](https://ai.pydantic.dev/api/models/bedrock/)
      * [ pydantic_ai.models.cerebras  ](https://ai.pydantic.dev/api/models/cerebras/)
      * [ pydantic_ai.models.cohere  ](https://ai.pydantic.dev/api/models/cohere/)
      * [ pydantic_ai.models.fallback  ](https://ai.pydantic.dev/api/models/fallback/)
      * [ pydantic_ai.models.function  ](https://ai.pydantic.dev/api/models/function/)
      * [ pydantic_ai.models.google  ](https://ai.pydantic.dev/api/models/google/)
      * [ pydantic_ai.models.xai  ](https://ai.pydantic.dev/api/models/xai/)
      * [ pydantic_ai.models.groq  ](https://ai.pydantic.dev/api/models/groq/)
      * [ pydantic_ai.models.huggingface  ](https://ai.pydantic.dev/api/models/huggingface/)
      * [ pydantic_ai.models.instrumented  ](https://ai.pydantic.dev/api/models/instrumented/)
      * [ pydantic_ai.models.mcp_sampling  ](https://ai.pydantic.dev/api/models/mcp-sampling/)
      * [ pydantic_ai.models.mistral  ](https://ai.pydantic.dev/api/models/mistral/)
      * [ pydantic_ai.models.openai  ](https://ai.pydantic.dev/api/models/openai/)
      * [ pydantic_ai.models.openrouter  ](https://ai.pydantic.dev/api/models/openrouter/)
      * [ pydantic_ai.models.outlines  ](https://ai.pydantic.dev/api/models/outlines/)
      * [ pydantic_ai.models.test  ](https://ai.pydantic.dev/api/models/test/)
      * [ pydantic_ai.models.wrapper  ](https://ai.pydantic.dev/api/models/wrapper/)
      * [ pydantic_ai.output  ](https://ai.pydantic.dev/api/output/)
      * [ pydantic_ai.profiles  ](https://ai.pydantic.dev/api/profiles/)
      * [ pydantic_ai.providers  ](https://ai.pydantic.dev/api/providers/)
      * [ pydantic_ai.result  ](https://ai.pydantic.dev/api/result/)
      * [ pydantic_ai.retries  ](https://ai.pydantic.dev/api/retries/)
      * [ pydantic_ai.run  ](https://ai.pydantic.dev/api/run/)
      * [ pydantic_ai.settings  ](https://ai.pydantic.dev/api/settings/)
      * [ pydantic_ai.tools  ](https://ai.pydantic.dev/api/tools/)
      * [ pydantic_ai.toolsets  ](https://ai.pydantic.dev/api/toolsets/)
      * [ pydantic_ai.ui.ag_ui  ](https://ai.pydantic.dev/api/ui/ag_ui/)
      * [ pydantic_ai.ui  ](https://ai.pydantic.dev/api/ui/base/)
      * [ pydantic_ai.ui.vercel_ai  ](https://ai.pydantic.dev/api/ui/vercel_ai/)
      * [ pydantic_ai.usage  ](https://ai.pydantic.dev/api/usage/)
    * pydantic_evals
      * [ pydantic_evals.dataset  ](https://ai.pydantic.dev/api/pydantic_evals/dataset/)
      * [ pydantic_evals.evaluators  ](https://ai.pydantic.dev/api/pydantic_evals/evaluators/)
      * [ pydantic_evals.reporting  ](https://ai.pydantic.dev/api/pydantic_evals/reporting/)
      * [ pydantic_evals.otel  ](https://ai.pydantic.dev/api/pydantic_evals/otel/)
      * [ pydantic_evals.generation  ](https://ai.pydantic.dev/api/pydantic_evals/generation/)
    * pydantic_graph
      * [ pydantic_graph  ](https://ai.pydantic.dev/api/pydantic_graph/graph/)
      * [ pydantic_graph.nodes  ](https://ai.pydantic.dev/api/pydantic_graph/nodes/)
      * [ pydantic_graph.persistence  ](https://ai.pydantic.dev/api/pydantic_graph/persistence/)
      * [ pydantic_graph.mermaid  ](https://ai.pydantic.dev/api/pydantic_graph/mermaid/)
      * [ pydantic_graph.exceptions  ](https://ai.pydantic.dev/api/pydantic_graph/exceptions/)
      * Beta API
        * [ pydantic_graph.beta  ](https://ai.pydantic.dev/api/pydantic_graph/beta/)
        * [ pydantic_graph.beta.graph  ](https://ai.pydantic.dev/api/pydantic_graph/beta_graph/)
        * [ pydantic_graph.beta.graph_builder  ](https://ai.pydantic.dev/api/pydantic_graph/beta_graph_builder/)
        * [ pydantic_graph.beta.step  ](https://ai.pydantic.dev/api/pydantic_graph/beta_step/)
        * [ pydantic_graph.beta.join  ](https://ai.pydantic.dev/api/pydantic_graph/beta_join/)
        * [ pydantic_graph.beta.decision  ](https://ai.pydantic.dev/api/pydantic_graph/beta_decision/)
        * [ pydantic_graph.beta.node  ](https://ai.pydantic.dev/api/pydantic_graph/beta_node/)
    * fasta2a
      * [ fasta2a  ](https://ai.pydantic.dev/api/fasta2a/)
  * Project
    * [ Contributing  ](https://ai.pydantic.dev/contributing/)
    * [ Upgrade Guide  ](https://ai.pydantic.dev/changelog/)
    * [ Version policy  ](https://ai.pydantic.dev/version-policy/)


  * [ Install  ](https://ai.pydantic.dev/mcp/client/#install)
  * [ Usage  ](https://ai.pydantic.dev/mcp/client/#usage)
    * [ Streamable HTTP Client  ](https://ai.pydantic.dev/mcp/client/#streamable-http-client)
    * [ SSE Client  ](https://ai.pydantic.dev/mcp/client/#sse-client)
    * [ MCP "stdio" Server  ](https://ai.pydantic.dev/mcp/client/#mcp-stdio-server)
  * [ Loading MCP Servers from Configuration  ](https://ai.pydantic.dev/mcp/client/#loading-mcp-servers-from-configuration)
    * [ Configuration Format  ](https://ai.pydantic.dev/mcp/client/#configuration-format)
    * [ Environment Variables  ](https://ai.pydantic.dev/mcp/client/#environment-variables)
    * [ Usage  ](https://ai.pydantic.dev/mcp/client/#usage_1)
  * [ Tool call customization  ](https://ai.pydantic.dev/mcp/client/#tool-call-customization)
  * [ Using Tool Prefixes to Avoid Naming Conflicts  ](https://ai.pydantic.dev/mcp/client/#using-tool-prefixes-to-avoid-naming-conflicts)
  * [ Server Instructions  ](https://ai.pydantic.dev/mcp/client/#server-instructions)
  * [ Tool metadata  ](https://ai.pydantic.dev/mcp/client/#tool-metadata)
  * [ Resources  ](https://ai.pydantic.dev/mcp/client/#resources)
  * [ Custom TLS / SSL configuration  ](https://ai.pydantic.dev/mcp/client/#custom-tls-ssl-configuration)
  * [ Client Identification  ](https://ai.pydantic.dev/mcp/client/#client-identification)
  * [ MCP Sampling  ](https://ai.pydantic.dev/mcp/client/#mcp-sampling)
  * [ Elicitation  ](https://ai.pydantic.dev/mcp/client/#elicitation)
    * [ How Elicitation works  ](https://ai.pydantic.dev/mcp/client/#how-elicitation-works)
    * [ Setting up Elicitation  ](https://ai.pydantic.dev/mcp/client/#setting-up-elicitation)
    * [ Supported Schema Types  ](https://ai.pydantic.dev/mcp/client/#supported-schema-types)
    * [ Security  ](https://ai.pydantic.dev/mcp/client/#security)


  1. [ Pydantic AI  ](https://ai.pydantic.dev/)
  2. [ Documentation  ](https://ai.pydantic.dev/agent/)
  3. [ MCP  ](https://ai.pydantic.dev/mcp/overview/)


# Client
Pydantic AI can act as an [MCP client](https://modelcontextprotocol.io/quickstart/client), connecting to MCP servers to use their tools.
## Install
You need to either install [`pydantic-ai`](https://ai.pydantic.dev/install/), or [`pydantic-ai-slim`](https://ai.pydantic.dev/install/#slim-install) with the `mcp` optional group:
[pip](https://ai.pydantic.dev/mcp/client/#__tabbed_1_1)[uv](https://ai.pydantic.dev/mcp/client/#__tabbed_1_2)
```
pip install "pydantic-ai-slim[mcp]"

```

```
uv add "pydantic-ai-slim[mcp]"

```

## Usage
Pydantic AI comes with three ways to connect to MCP servers:
  * [`MCPServerStreamableHTTP`](https://ai.pydantic.dev/api/mcp/#pydantic_ai.mcp.MCPServerStreamableHTTP "MCPServerStreamableHTTP") which connects to an MCP server using the [Streamable HTTP](https://modelcontextprotocol.io/introduction#streamable-http) transport
  * [`MCPServerSSE`](https://ai.pydantic.dev/api/mcp/#pydantic_ai.mcp.MCPServerSSE "MCPServerSSE") which connects to an MCP server using the [HTTP SSE](https://spec.modelcontextprotocol.io/specification/2024-11-05/basic/transports/#http-with-sse) transport
  * [`MCPServerStdio`](https://ai.pydantic.dev/api/mcp/#pydantic_ai.mcp.MCPServerStdio "MCPServerStdio") which runs the server as a subprocess and connects to it using the [stdio](https://spec.modelcontextprotocol.io/specification/2024-11-05/basic/transports/#stdio) transport


Examples of all three are shown below.
Each MCP server instance is a [toolset](https://ai.pydantic.dev/toolsets/) and can be registered with an [`Agent`](https://ai.pydantic.dev/api/agent/#pydantic_ai.agent.Agent "Agent



      dataclass
  ") using the `toolsets` argument.
You can use the [`async with agent`](https://ai.pydantic.dev/api/agent/#pydantic_ai.agent.Agent.__aenter__ "__aenter__



      async
  ") context manager to open and close connections to all registered servers (and in the case of stdio servers, start and stop the subprocesses) around the context where they'll be used in agent runs. You can also use [`async with server`](https://ai.pydantic.dev/api/mcp/#pydantic_ai.mcp.MCPServer.__aenter__ "__aenter__



      async
  ") to manage the connection or subprocess of a specific server, for example if you'd like to use it with multiple agents. If you don't explicitly enter one of these context managers to set up the server, this will be done automatically when it's needed (e.g. to list the available tools or call a specific tool), but it's more efficient to do so around the entire context where you expect the servers to be used.
### Streamable HTTP Client
[`MCPServerStreamableHTTP`](https://ai.pydantic.dev/api/mcp/#pydantic_ai.mcp.MCPServerStreamableHTTP "MCPServerStreamableHTTP") connects over HTTP using the [Streamable HTTP](https://modelcontextprotocol.io/introduction#streamable-http) transport to a server.
Note
[`MCPServerStreamableHTTP`](https://ai.pydantic.dev/api/mcp/#pydantic_ai.mcp.MCPServerStreamableHTTP "MCPServerStreamableHTTP") requires an MCP server to be running and accepting HTTP connections before running the agent. Running the server is not managed by Pydantic AI.
Before creating the Streamable HTTP client, we need to run a server that supports the Streamable HTTP transport.
streamable_http_server.py```
from mcp.server.fastmcp import FastMCP

app = FastMCP()

@app.tool()
def add(a: int, b: int) -> int:
    return a + b

if __name__ == '__main__':
    app.run(transport='streamable-http')

```

Then we can create the client:
[With Pydantic AI Gateway](https://ai.pydantic.dev/mcp/client/#__tabbed_2_1)[Directly to Provider API](https://ai.pydantic.dev/mcp/client/#__tabbed_2_2)
[Learn about Gateway](https://ai.pydantic.dev/gateway) mcp_streamable_http_client.py```
from pydantic_ai import Agent
from pydantic_ai.mcp import MCPServerStreamableHTTP

server = MCPServerStreamableHTTP('http://localhost:8000/mcp')  [](https://ai.pydantic.dev/mcp/client/#__code_3_annotation_1)
agent = Agent('gateway/openai:gpt-5.2', toolsets=[server])  [](https://ai.pydantic.dev/mcp/client/#__code_3_annotation_2)

async def main():
    result = await agent.run('What is 7 plus 5?')
    print(result.output)
    #> The answer is 12.

```

mcp_streamable_http_client.py```
from pydantic_ai import Agent
from pydantic_ai.mcp import MCPServerStreamableHTTP

server = MCPServerStreamableHTTP('http://localhost:8000/mcp')

[](https://ai.pydantic.dev/mcp/client/#__code_4_annotation_1)
agent = Agent('openai:gpt-5.2', toolsets=[server])

[](https://ai.pydantic.dev/mcp/client/#__code_4_annotation_2)

async def main():
    result = await agent.run('What is 7 plus 5?')
    print(result.output)
    #> The answer is 12.

```

  1. Define the MCP server with the URL used to connect.
  2. Create an agent with the MCP server attached.


_(This example is complete, it can be run "as is" — you'll need to add`asyncio.run(main())` to run `main`)_
**What's happening here?**
  * The model receives the prompt "What is 7 plus 5?"
  * The model decides "Oh, I've got this `add` tool, that will be a good way to answer this question"
  * The model returns a tool call
  * Pydantic AI sends the tool call to the MCP server using the Streamable HTTP transport
  * The model is called again with the return value of running the `add` tool (12)
  * The model returns the final answer


You can visualise this clearly, and even see the tool call, by adding three lines of code to instrument the example with [logfire](https://logfire.pydantic.dev/docs):
mcp_sse_client_logfire.py```
import logfire

logfire.configure()
logfire.instrument_pydantic_ai()

```

### SSE Client
[`MCPServerSSE`](https://ai.pydantic.dev/api/mcp/#pydantic_ai.mcp.MCPServerSSE "MCPServerSSE") connects over HTTP using the [HTTP + Server Sent Events transport](https://spec.modelcontextprotocol.io/specification/2024-11-05/basic/transports/#http-with-sse) to a server.
Note
The SSE transport in MCP is deprecated, you should use Streamable HTTP instead.
Before creating the SSE client, we need to run a server that supports the SSE transport.
sse_server.py```
from mcp.server.fastmcp import FastMCP

app = FastMCP()

@app.tool()
def add(a: int, b: int) -> int:
    return a + b

if __name__ == '__main__':
    app.run(transport='sse')

```

Then we can create the client:
[With Pydantic AI Gateway](https://ai.pydantic.dev/mcp/client/#__tabbed_3_1)[Directly to Provider API](https://ai.pydantic.dev/mcp/client/#__tabbed_3_2)
[Learn about Gateway](https://ai.pydantic.dev/gateway) mcp_sse_client.py```
from pydantic_ai import Agent
from pydantic_ai.mcp import MCPServerSSE

server = MCPServerSSE('http://localhost:3001/sse')  [](https://ai.pydantic.dev/mcp/client/#__code_7_annotation_1)
agent = Agent('gateway/openai:gpt-5.2', toolsets=[server])  [](https://ai.pydantic.dev/mcp/client/#__code_7_annotation_2)


async def main():
    result = await agent.run('What is 7 plus 5?')
    print(result.output)
    #> The answer is 12.

```

mcp_sse_client.py```
from pydantic_ai import Agent
from pydantic_ai.mcp import MCPServerSSE

server = MCPServerSSE('http://localhost:3001/sse')

[](https://ai.pydantic.dev/mcp/client/#__code_8_annotation_1)
agent = Agent('openai:gpt-5.2', toolsets=[server])

[](https://ai.pydantic.dev/mcp/client/#__code_8_annotation_2)


async def main():
    result = await agent.run('What is 7 plus 5?')
    print(result.output)
    #> The answer is 12.

```

  1. Define the MCP server with the URL used to connect.
  2. Create an agent with the MCP server attached.


_(This example is complete, it can be run "as is" — you'll need to add`asyncio.run(main())` to run `main`)_
### MCP "stdio" Server
MCP also offers [stdio transport](https://spec.modelcontextprotocol.io/specification/2024-11-05/basic/transports/#stdio) 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`](https://ai.pydantic.dev/api/mcp/#pydantic_ai.mcp.MCPServerStdio "MCPServerStdio") class.
In this example we use a simple MCP server that provides weather tools.
[With Pydantic AI Gateway](https://ai.pydantic.dev/mcp/client/#__tabbed_4_1)[Directly to Provider API](https://ai.pydantic.dev/mcp/client/#__tabbed_4_2)
[Learn about Gateway](https://ai.pydantic.dev/gateway) mcp_stdio_client.py```
from pydantic_ai import Agent
from pydantic_ai.mcp import MCPServerStdio

server = MCPServerStdio('python', args=['mcp_server.py'], timeout=10)
agent = Agent('gateway/openai:gpt-5.2', toolsets=[server])


async def main():
    result = await agent.run('What is the weather in Paris?')
    print(result.output)
    #> The weather in Paris is sunny and 26 degrees Celsius.

```

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

server = MCPServerStdio('python', args=['mcp_server.py'], timeout=10)
agent = Agent('openai:gpt-5.2', toolsets=[server])


async def main():
    result = await agent.run('What is the weather in Paris?')
    print(result.output)
    #> The weather in Paris is sunny and 26 degrees Celsius.

```

## Loading MCP Servers from Configuration
Instead of creating MCP server instances individually in code, you can load multiple servers from a JSON configuration file using [`load_mcp_servers()`](https://ai.pydantic.dev/api/mcp/#pydantic_ai.mcp.load_mcp_servers "load_mcp_servers").
This is particularly useful when you need to manage multiple MCP servers or want to configure servers externally without modifying code.
### Configuration Format
The configuration file should be a JSON file with an `mcpServers` object containing server definitions. Each server is identified by a unique key and contains the configuration for that server type:
mcp_config.json```
{
  "mcpServers": {
    "python-runner": {
        "command": "uv",
        "args": ["run", "mcp-run-python", "stdio"]
    },
    "weather": {
      "command": "python",
      "args": ["mcp_server.py"]
    },
    "weather-api": {
      "url": "http://localhost:3001/sse"
    },
    "calculator": {
      "url": "http://localhost:8000/mcp"
    }
  }
}

```

Note
The MCP server is only inferred to be an SSE server because of the `/sse` suffix. Any other server with the "url" field will be inferred to be a Streamable HTTP server.
We made this decision given that the SSE transport is deprecated.
### Environment Variables
The configuration file supports environment variable expansion using the `${VAR}` and `${VAR:-default}` syntax, [like Claude Code](https://code.claude.com/docs/en/mcp#environment-variable-expansion-in-mcp-json). This is useful for keeping sensitive information like API keys or host names out of your configuration files:
mcp_config_with_env.json```
{
  "mcpServers": {
    "python-runner": {
      "command": "${PYTHON_CMD:-python3}",
      "args": ["run", "${MCP_MODULE}", "stdio"],
      "env": {
        "API_KEY": "${MY_API_KEY}"
      }
    },
    "weather-api": {
      "url": "https://${SERVER_HOST:-localhost}:${SERVER_PORT:-8080}/sse"
    }
  }
}

```

When loading this configuration with [`load_mcp_servers()`](https://ai.pydantic.dev/api/mcp/#pydantic_ai.mcp.load_mcp_servers "load_mcp_servers"):
  * `${VAR}` references will be replaced with the corresponding environment variable values.
  * `${VAR:-default}` references will use the environment variable value if set, otherwise the default value.


Warning
If a referenced environment variable using `${VAR}` syntax is not defined, a `ValueError` will be raised. Use the `${VAR:-default}` syntax to provide a fallback value.
### Usage
[With Pydantic AI Gateway](https://ai.pydantic.dev/mcp/client/#__tabbed_5_1)[Directly to Provider API](https://ai.pydantic.dev/mcp/client/#__tabbed_5_2)
[Learn about Gateway](https://ai.pydantic.dev/gateway) mcp_config_loader.py```
from pydantic_ai import Agent
from pydantic_ai.mcp import load_mcp_servers

# Load all servers from configuration file
servers = load_mcp_servers('mcp_config.json')

# Create agent with all loaded servers
agent = Agent('gateway/openai:gpt-5.2', toolsets=servers)

async def main():
    result = await agent.run('What is 7 plus 5?')
    print(result.output)

```

mcp_config_loader.py```
from pydantic_ai import Agent
from pydantic_ai.mcp import load_mcp_servers

# Load all servers from configuration file
servers = load_mcp_servers('mcp_config.json')

# Create agent with all loaded servers
agent = Agent('openai:gpt-5.2', toolsets=servers)

async def main():
    result = await agent.run('What is 7 plus 5?')
    print(result.output)

```

_(This example is complete, it can be run "as is" — you'll need to add`asyncio.run(main())` to run `main`)_
## Tool call customization
The MCP servers provide the ability to set a `process_tool_call` which allows the customization of tool call requests and their responses.
A common use case for this is to inject metadata to the requests which the server call needs:
mcp_process_tool_call.py```
from typing import Any

from pydantic_ai import Agent, RunContext
from pydantic_ai.mcp import CallToolFunc, MCPServerStdio, ToolResult
from pydantic_ai.models.test import TestModel


async def process_tool_call(
    ctx: RunContext[int],
    call_tool: CallToolFunc,
    name: str,
    tool_args: dict[str, Any],
) -> ToolResult:
    """A tool call processor that passes along the deps."""
    return await call_tool(name, tool_args, {'deps': ctx.deps})


server = MCPServerStdio('python', args=['mcp_server.py'], process_tool_call=process_tool_call)
agent = Agent(
    model=TestModel(call_tools=['echo_deps']),
    deps_type=int,
    toolsets=[server]
)


async def main():
    result = await agent.run('Echo with deps set to 42', deps=42)
    print(result.output)
    #> {"echo_deps":{"echo":"This is an echo message","deps":42}}

```

How to access the metadata is MCP server SDK specific. For example with the [MCP Python SDK](https://github.com/modelcontextprotocol/python-sdk), it is accessible via the [`ctx: Context`](https://github.com/modelcontextprotocol/python-sdk#context) argument that can be included on tool call handlers:
mcp_server.py```
from typing import Any

from mcp.server.fastmcp import Context, FastMCP
from mcp.server.session import ServerSession

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


@mcp.tool()
async def echo_deps(ctx: Context[ServerSession, None]) -> dict[str, Any]:
    """Echo the run context.

    Args:
        ctx: Context object containing request and session information.

    Returns:
        Dictionary with an echo message and the deps.
    """
    await ctx.info('This is an info message')

    deps: Any = getattr(ctx.request_context.meta, 'deps')
    return {'echo': 'This is an echo message', 'deps': deps}

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

```

## 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.
This allows you to use multiple servers that might have overlapping tool names without conflicts:
[With Pydantic AI Gateway](https://ai.pydantic.dev/mcp/client/#__tabbed_6_1)[Directly to Provider API](https://ai.pydantic.dev/mcp/client/#__tabbed_6_2)
[Learn about Gateway](https://ai.pydantic.dev/gateway) mcp_tool_prefix_http_client.py```
from pydantic_ai import Agent
from pydantic_ai.mcp import MCPServerSSE

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

calculator_server = MCPServerSSE(
    '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('gateway/openai:gpt-5.2', toolsets=[weather_server, calculator_server])

```

mcp_tool_prefix_http_client.py```
from pydantic_ai import Agent
from pydantic_ai.mcp import MCPServerSSE

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

calculator_server = MCPServerSSE(
    '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-5.2', toolsets=[weather_server, calculator_server])

```

## Server Instructions
MCP servers can provide instructions during initialization that give context about how to best interact with the server's tools. These instructions are accessible via the [`instructions`](https://ai.pydantic.dev/api/mcp/#pydantic_ai.mcp.MCPServer.instructions "instructions



      property
  ") property after the server connection is established.
[With Pydantic AI Gateway](https://ai.pydantic.dev/mcp/client/#__tabbed_7_1)[Directly to Provider API](https://ai.pydantic.dev/mcp/client/#__tabbed_7_2)
[Learn about Gateway](https://ai.pydantic.dev/gateway) mcp_server_instructions.py```
from pydantic_ai import Agent
from pydantic_ai.mcp import MCPServerStreamableHTTP

server = MCPServerStreamableHTTP('http://localhost:8000/mcp')
agent = Agent('gateway/openai:gpt-5.2', toolsets=[server])

@agent.instructions
async def mcp_server_instructions():
    return server.instructions  [](https://ai.pydantic.dev/mcp/client/#__code_19_annotation_1)

async def main():
    result = await agent.run('What is 7 plus 5?')
    print(result.output)
    #> The answer is 12.

```

mcp_server_instructions.py```
from pydantic_ai import Agent
from pydantic_ai.mcp import MCPServerStreamableHTTP

server = MCPServerStreamableHTTP('http://localhost:8000/mcp')
agent = Agent('openai:gpt-5.2', toolsets=[server])

@agent.instructions
async def mcp_server_instructions():
    return server.instructions

[](https://ai.pydantic.dev/mcp/client/#__code_20_annotation_1)

async def main():
    result = await agent.run('What is 7 plus 5?')
    print(result.output)
    #> The answer is 12.

```

  1. The server connection is guaranteed to be established by this point, so `server.instructions` is available.


## Tool metadata
MCP tools can include metadata that provides additional information about the tool's characteristics, which can be useful when [filtering tools](https://ai.pydantic.dev/api/toolsets/#pydantic_ai.toolsets.FilteredToolset "FilteredToolset



      dataclass
  "). The `meta`, `annotations`, and `output_schema` fields can be found on the `metadata` dict on the [`ToolDefinition`](https://ai.pydantic.dev/api/tools/#pydantic_ai.tools.ToolDefinition "ToolDefinition



      dataclass
  ") 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()`](https://ai.pydantic.dev/api/mcp/#pydantic_ai.mcp.MCPServer.list_resources "list_resources



      async
  ") - List all available resources on the server
  * [`list_resource_templates()`](https://ai.pydantic.dev/api/mcp/#pydantic_ai.mcp.MCPServer.list_resource_templates "list_resource_templates



      async
  ") - List resource templates with parameter placeholders
  * [`read_resource(uri)`](https://ai.pydantic.dev/api/mcp/#pydantic_ai.mcp.MCPServer.read_resource "read_resource



      async
  ") - 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`](https://ai.pydantic.dev/api/messages/#pydantic_ai.messages.BinaryContent "BinaryContent").
Before consuming resources, we need to run a server that exposes some:
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:
mcp_resources.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 – for example to trust an internal Certificate Authority, present a client certificate for **mTLS** , or (during local development only!) disable certificate verification altogether. All HTTP-based MCP client classes ([`MCPServerStreamableHTTP`](https://ai.pydantic.dev/api/mcp/#pydantic_ai.mcp.MCPServerStreamableHTTP "MCPServerStreamableHTTP") and [`MCPServerSSE`](https://ai.pydantic.dev/api/mcp/#pydantic_ai.mcp.MCPServerSSE "MCPServerSSE")) expose an `http_client` parameter that lets you pass your own pre-configured [`httpx.AsyncClient`](https://www.python-httpx.org/async/).
[With Pydantic AI Gateway](https://ai.pydantic.dev/mcp/client/#__tabbed_8_1)[Directly to Provider API](https://ai.pydantic.dev/mcp/client/#__tabbed_8_2)
[Learn about Gateway](https://ai.pydantic.dev/gateway) mcp_custom_tls_client.py```
import ssl

import httpx

from pydantic_ai import Agent
from pydantic_ai.mcp import MCPServerSSE

# Trust an internal / self-signed CA
ssl_ctx = ssl.create_default_context(cafile='/etc/ssl/private/my_company_ca.pem')

# OPTIONAL: if the server requires **mutual TLS** load your client certificate
ssl_ctx.load_cert_chain(certfile='/etc/ssl/certs/client.crt', keyfile='/etc/ssl/private/client.key',)

http_client = httpx.AsyncClient(
    verify=ssl_ctx,
    timeout=httpx.Timeout(10.0),
)

server = MCPServerSSE(
    'http://localhost:3001/sse',
    http_client=http_client,  [](https://ai.pydantic.dev/mcp/client/#__code_23_annotation_1)
)
agent = Agent('gateway/openai:gpt-5.2', toolsets=[server])

async def main():
    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.

```

mcp_custom_tls_client.py```
import ssl

import httpx

from pydantic_ai import Agent
from pydantic_ai.mcp import MCPServerSSE

# Trust an internal / self-signed CA
ssl_ctx = ssl.create_default_context(cafile='/etc/ssl/private/my_company_ca.pem')

# OPTIONAL: if the server requires **mutual TLS** load your client certificate
ssl_ctx.load_cert_chain(certfile='/etc/ssl/certs/client.crt', keyfile='/etc/ssl/private/client.key',)

http_client = httpx.AsyncClient(
    verify=ssl_ctx,
    timeout=httpx.Timeout(10.0),
)

server = MCPServerSSE(
    'http://localhost:3001/sse',
    http_client=http_client,

[](https://ai.pydantic.dev/mcp/client/#__code_24_annotation_1)
)
agent = Agent('openai:gpt-5.2', toolsets=[server])

async def main():
    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. When you supply `http_client`, Pydantic AI re-uses this client for every request. Anything supported by **httpx** (`verify`, `cert`, custom proxies, timeouts, etc.) therefore applies to all MCP traffic.


## Client Identification
When connecting to an MCP server, you can optionally specify an [Implementation](https://modelcontextprotocol.io/specification/2025-11-25/schema#implementation) object as client information that will be sent to the server during initialization. This is useful for:
  * Identifying your application in server logs
  * Allowing servers to provide custom behavior based on the client
  * Debugging and monitoring MCP connections
  * Version-specific feature negotiation


All MCP client classes ([`MCPServerStdio`](https://ai.pydantic.dev/api/mcp/#pydantic_ai.mcp.MCPServerStdio "MCPServerStdio"), [`MCPServerStreamableHTTP`](https://ai.pydantic.dev/api/mcp/#pydantic_ai.mcp.MCPServerStreamableHTTP "MCPServerStreamableHTTP"), and [`MCPServerSSE`](https://ai.pydantic.dev/api/mcp/#pydantic_ai.mcp.MCPServerSSE "MCPServerSSE")) support the `client_info` parameter:
mcp_client_with_name.py```
from mcp import types as mcp_types

from pydantic_ai.mcp import MCPServerSSE

server = MCPServerSSE(
    'http://localhost:3001/sse',
    client_info=mcp_types.Implementation(
        name='MyApplication',
        version='2.1.0',
    ),
)

```

## MCP Sampling
What is MCP Sampling?
In MCP [sampling](https://modelcontextprotocol.io/docs/concepts/sampling) is a system by which an MCP server can make LLM calls via the MCP client - effectively proxying requests to an LLM via the client over whatever transport is being used.
Sampling is extremely useful when MCP servers need to use Gen AI but you don't want to provision them each with their own LLM credentials or when a public MCP server would like the connecting client to pay for LLM calls.
Confusingly it has nothing to do with the concept of "sampling" in observability, or frankly the concept of "sampling" in any other domain.
Sampling Diagram
Here's a mermaid diagram that may or may not make the data flow clearer:
Pydantic AI supports sampling as both a client and server. See the [server](https://ai.pydantic.dev/mcp/server/#mcp-sampling) documentation for details on how to use sampling within a server.
Sampling is automatically supported by Pydantic AI agents when they act as a client.
To be able to use sampling, an MCP server instance needs to have a [`sampling_model`](https://ai.pydantic.dev/api/mcp/#pydantic_ai.mcp.MCPServer.sampling_model "sampling_model



      instance-attribute
  ") set. This can be done either directly on the server using the constructor keyword argument or the property, or by using [`agent.set_mcp_sampling_model()`](https://ai.pydantic.dev/api/agent/#pydantic_ai.agent.Agent.set_mcp_sampling_model "set_mcp_sampling_model") to set the agent's model or one specified as an argument as the sampling model on all MCP servers registered with that agent.
Let's say we have an MCP server that wants to use sampling (in this case to generate an SVG as per the tool arguments).
Sampling MCP Server
generate_svg.py```
import re
from pathlib import Path

from mcp import SamplingMessage
from mcp.server.fastmcp import Context, FastMCP
from mcp.types import TextContent

app = FastMCP()


@app.tool()
async def image_generator(ctx: Context, subject: str, style: str) -> str:
    prompt = f'{subject=} {style=}'
    # `ctx.session.create_message` is the sampling call
    result = await ctx.session.create_message(
        [SamplingMessage(role='user', content=TextContent(type='text', text=prompt))],
        max_tokens=1_024,
        system_prompt='Generate an SVG image as per the user input',
    )
    assert isinstance(result.content, TextContent)

    path = Path(f'{subject}_{style}.svg')
    # remove triple backticks if the svg was returned within markdown
    if m := re.search(r'^```\w*$(.+?)```$', result.content.text, re.S | re.M):
        path.write_text(m.group(1), encoding='utf-8')
    else:
        path.write_text(result.content.text, encoding='utf-8')
    return f'See {path}'


if __name__ == '__main__':
    # run the server via stdio
    app.run()

```

Using this server with an `Agent` will automatically allow sampling:
sampling_mcp_client.py```
from pydantic_ai import Agent
from pydantic_ai.mcp import MCPServerStdio

server = MCPServerStdio('python', args=['generate_svg.py'])
agent = Agent('openai:gpt-5.2', toolsets=[server])


async def main():
    agent.set_mcp_sampling_model()
    result = await agent.run('Create an image of a robot in a punk style.')
    print(result.output)
    #> Image file written to robot_punk.svg.

```

_(This example is complete, it can be run "as is")_
You can disallow sampling by setting [`allow_sampling=False`](https://ai.pydantic.dev/api/mcp/#pydantic_ai.mcp.MCPServer.allow_sampling "allow_sampling



      instance-attribute
  ") when creating the server reference, e.g.:
sampling_disallowed.py```
from pydantic_ai.mcp import MCPServerStdio

server = MCPServerStdio(
    'python',
    args=['generate_svg.py'],
    allow_sampling=False,
)

```

## Elicitation
In MCP, [elicitation](https://modelcontextprotocol.io/docs/concepts/elicitation) allows a server to request for [structured input](https://modelcontextprotocol.io/specification/2025-06-18/client/elicitation#supported-schema-types) from the client for missing or additional context during a session.
Elicitation let models essentially say "Hold on - I need to know X before i can continue" rather than requiring everything upfront or taking a shot in the dark.
### How Elicitation works
Elicitation introduces a new protocol message type called [`ElicitRequest`](https://modelcontextprotocol.io/specification/2025-06-18/schema#elicitrequest), which is sent from the server to the client when it needs additional information. The client can then respond with an [`ElicitResult`](https://modelcontextprotocol.io/specification/2025-06-18/schema#elicitresult) or an `ErrorData` message.
Here's a typical interaction:
  * User makes a request to the MCP server (e.g. "Book a table at that Italian place")
  * The server identifies that it needs more information (e.g. "Which Italian place?", "What date and time?")
  * The server sends an `ElicitRequest` to the client asking for the missing information.
  * The client receives the request, presents it to the user (e.g. via a terminal prompt, GUI dialog, or web interface).
  * User provides the requested information, `decline` or `cancel` the request.
  * The client sends an `ElicitResult` back to the server with the user's response.
  * With the structured data, the server can continue processing the original request.


This allows for a more interactive and user-friendly experience, especially for multi-staged workflows. Instead of requiring all information upfront, the server can ask for it as needed, making the interaction feel more natural.
### Setting up Elicitation
To enable elicitation, provide an [`elicitation_callback`](https://ai.pydantic.dev/api/mcp/#pydantic_ai.mcp.MCPServer.elicitation_callback "elicitation_callback



      class-attribute
      instance-attribute
  ") function when creating your MCP server instance:
restaurant_server.py```
from mcp.server.fastmcp import Context, FastMCP
from pydantic import BaseModel, Field

mcp = FastMCP(name='Restaurant Booking')


class BookingDetails(BaseModel):
    """Schema for restaurant booking information."""

    restaurant: str = Field(description='Choose a restaurant')
    party_size: int = Field(description='Number of people', ge=1, le=8)
    date: str = Field(description='Reservation date (DD-MM-YYYY)')


@mcp.tool()
async def book_table(ctx: Context) -> str:
    """Book a restaurant table with user input."""
    # Ask user for booking details using Pydantic schema
    result = await ctx.elicit(message='Please provide your booking details:', schema=BookingDetails)

    if result.action == 'accept' and result.data:
        booking = result.data
        return f'✅ Booked table for {booking.party_size} at {booking.restaurant} on {booking.date}'
    elif result.action == 'decline':
        return 'No problem! Maybe another time.'
    else:  # cancel
        return 'Booking cancelled.'


if __name__ == '__main__':
    mcp.run(transport='stdio')

```

This server demonstrates elicitation by requesting structured booking details from the client when the `book_table` tool is called. Here's how to create a client that handles these elicitation requests:
[With Pydantic AI Gateway](https://ai.pydantic.dev/mcp/client/#__tabbed_9_1)[Directly to Provider API](https://ai.pydantic.dev/mcp/client/#__tabbed_9_2)
[Learn about Gateway](https://ai.pydantic.dev/gateway) client_example.py```
import asyncio
from typing import Any

from mcp.client.session import ClientSession
from mcp.shared.context import RequestContext
from mcp.types import ElicitRequestParams, ElicitResult

from pydantic_ai import Agent
from pydantic_ai.mcp import MCPServerStdio


async def handle_elicitation(
    context: RequestContext[ClientSession, Any, Any],
    params: ElicitRequestParams,
) -> ElicitResult:
    """Handle elicitation requests from MCP server."""
    print(f'\n{params.message}')

    if not params.requestedSchema:
        response = input('Response: ')
        return ElicitResult(action='accept', content={'response': response})

    # Collect data for each field
    properties = params.requestedSchema['properties']
    data = {}

    for field, info in properties.items():
        description = info.get('description', field)

        value = input(f'{description}: ')

        # Convert to proper type based on JSON schema
        if info.get('type') == 'integer':
            data[field] = int(value)
        else:
            data[field] = value

    # Confirm
    confirm = input('\nConfirm booking? (y/n/c): ').lower()

    if confirm == 'y':
        print('Booking details:', data)
        return ElicitResult(action='accept', content=data)
    elif confirm == 'n':
        return ElicitResult(action='decline')
    else:
        return ElicitResult(action='cancel')


# Set up MCP server connection
restaurant_server = MCPServerStdio(
    'python', args=['restaurant_server.py'], elicitation_callback=handle_elicitation
)

# Create agent
agent = Agent('gateway/openai:gpt-5.2', toolsets=[restaurant_server])


async def main():
    """Run the agent to book a restaurant table."""
    result = await agent.run('Book me a table')
    print(f'\nResult: {result.output}')


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

```

client_example.py```
import asyncio
from typing import Any

from mcp.client.session import ClientSession
from mcp.shared.context import RequestContext
from mcp.types import ElicitRequestParams, ElicitResult

from pydantic_ai import Agent
from pydantic_ai.mcp import MCPServerStdio


async def handle_elicitation(
    context: RequestContext[ClientSession, Any, Any],
    params: ElicitRequestParams,
) -> ElicitResult:
    """Handle elicitation requests from MCP server."""
    print(f'\n{params.message}')

    if not params.requestedSchema:
        response = input('Response: ')
        return ElicitResult(action='accept', content={'response': response})

    # Collect data for each field
    properties = params.requestedSchema['properties']
    data = {}

    for field, info in properties.items():
        description = info.get('description', field)

        value = input(f'{description}: ')

        # Convert to proper type based on JSON schema
        if info.get('type') == 'integer':
            data[field] = int(value)
        else:
            data[field] = value

    # Confirm
    confirm = input('\nConfirm booking? (y/n/c): ').lower()

    if confirm == 'y':
        print('Booking details:', data)
        return ElicitResult(action='accept', content=data)
    elif confirm == 'n':
        return ElicitResult(action='decline')
    else:
        return ElicitResult(action='cancel')


# Set up MCP server connection
restaurant_server = MCPServerStdio(
    'python', args=['restaurant_server.py'], elicitation_callback=handle_elicitation
)

# Create agent
agent = Agent('openai:gpt-5.2', toolsets=[restaurant_server])


async def main():
    """Run the agent to book a restaurant table."""
    result = await agent.run('Book me a table')
    print(f'\nResult: {result.output}')


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

```

### Supported Schema Types
MCP elicitation supports string, number, boolean, and enum types with flat object structures only. These limitations ensure reliable cross-client compatibility. See [supported schema types](https://modelcontextprotocol.io/specification/2025-06-18/client/elicitation#supported-schema-types) for details.
### Security
MCP Elicitation requires careful handling - servers must not request sensitive information, and clients must implement user approval controls with clear explanations. See [security considerations](https://modelcontextprotocol.io/specification/2025-06-18/client/elicitation#security-considerations) for details.
© Pydantic Services Inc. 2024 to present
