-
Notifications
You must be signed in to change notification settings - Fork 811
Add support for MCP servers #1100
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
PR Change SummaryAdded support for MCP (Model Control Protocol) servers, enhancing integration capabilities for external services.
Added Files
How can I customize these reviews?Check out the Hyperlint AI Reviewer docs for more information on how to customize the review. If you just want to ignore it on this PR, you can add the Note specifically for link checks, we only check the first 30 links in a file and we cache the results for several hours (for instance, if you just added a page, you might experience this). Our recommendation is to add What is Hyperlint?Hyperlint is an AI agent that helps you write, edit, and maintain your documentation. Learn more about the Hyperlint AI reviewer and the checks that we can run on your documentation. |
@@ -148,6 +150,7 @@ def __init__( | |||
result_tool_description: str | None = None, | |||
result_retries: int | None = None, | |||
tools: Sequence[Tool[AgentDepsT] | ToolFuncEither[AgentDepsT, ...]] = (), | |||
mcp_servers: dict[str, MCPServer] | None = None, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not sure I use the name of the server... Maybe it can be a list...
Docs Preview
|
|
||
for server in ctx.deps.mcp_servers: | ||
tools = await server.list_tools() | ||
if tool_name in {tool.name for tool in tools}: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we need to do anything to ensure there aren’t naming conflicts between servers?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What should I do? Error? 🤔
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I guess so, or namespace the tools by server or something
docs/mcp_servers.md
Outdated
async def main(): | ||
async with MCPServer.stdio('python', ['-m', 'pydantic_ai.mcp']) as server: | ||
agent = Agent('openai:gpt-4o', mcp_servers=[server]) | ||
result = await agent.run('Can you convert 30 degrees celsius to fahrenheit?') | ||
print(result.data) | ||
#> 30 degrees Celsius is equal to 86 degrees Fahrenheit. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this using the server in any way? It would be nice if the example did do that. Or at least looked more like it did that lol.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah, I see that it is. I think you should make it more clear that this is using the example MCP server present in the pydantic_ai.mcp
module.
I think it's also worth printing out the full message history here if it shows that a tool call related to the MCP server was made.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we need to change this so we have separate types for:
- stdio servers where pydantic-ai is responsible for running the server as a subprocess
- SSE where pydantic-ai is just connecting to a server running in a different process (maybe remotely)
Here's a rough sketch of running both:
from pydantic_ai import Agent
from pydantic_ai.mcp import MCPSubprocessServer, MCPRemoteServer
agent = Agent(
'openai:gpt-4o',
mcp_servers=[
MCPSubprocessServer('python', ('-m', 'my_mcp_server')),
MCPRemoteServer('http://localhost:8000/sse')
],
)
async def main():
async for agent.run_mcp_servers():
...
The advantage of this distinction are:
- more type safe
- easier to document the difference and clearer to the user what's going
- no need to use
run_mcp_servers
forMCPRemoteServer
which can use the existing http connection fromcached_async_http_client
- I also thing "subprocess" vs "remote" is a clearer distinction for most developers than "stdio" vs "sse" although of course we should document what transport they're using under the hood
@@ -0,0 +1,72 @@ | |||
# MCP Servers |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this should be called "MCP Client" since pydantic-ai is acting as a client
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm using the same notation everybody is tho. Claude Desktop, Cursor, and Cline use the "MCP Server" terminology even on the STDIO.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
right, but we're building an MCP client, so we should call it that.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
But the chapter is about MCP Servers. It far better for SEO. No one uses the term client, even when you are configuring the client: Cursor, Claude Desktop, Cline.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe rephrase the introduction phrase as something like:
PydanticAI supports integration with MCP Servers and act as a MCP client...
maybe link with https://modelcontextprotocol.io/introduction#general-architecture.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
we should have a new section in docs:
mcp/index.md
- a general introduction saying PydanticAI can be used as an MCP client or to build servers, and comes with some serversmcp/client.md
- this page - describing how to use PydanticAI as an MCP clientmcp/run-python.md
- docs for MCP server to run Python code in a sandbox #1140- ... more
docs/mcp_servers.md
Outdated
|
||
async def main(): | ||
async with MCPServer.sse(url='http://localhost:8000/sse') as mcp_server: | ||
Agent('your-model', mcp_servers=[mcp_server]) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I thought we were providing a way to set or initialise the server on an agent created in the global scope?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
also, surely with and SSE server, we're just using an HTTP connection, so we should be able to use the existing http connection and we then don't need a context manager.
docs/mcp_servers.md
Outdated
proper initialization and cleanup of resources. You can use either an HTTP/SSE server or a | ||
stdio-based server. | ||
|
||
### HTTP/SSE Server |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
we need some explanation on what's going on here.
@samuelcolvin What happens if the user didn't use the following, and they have set a async with agent.run_mcp_servers():
... Should we error? Or... ? Creating and recreating the server can be too resource intensive. |
error. |
We have to drop python 3.9 support if using mcp official lib, while it's ok for me |
Nop. I rather support 3.9 on their side. Not much work for it. |
At this point, since the user has to do async with Agent() as agent: # This creates the HTTP client and runs the MCP servers.
await agent.run('What is the capital of France?') Honestly, just the HTTP client already justifies to make the Agent an async context manager. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
otherwise looking good.
@@ -0,0 +1,72 @@ | |||
# MCP Servers |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
right, but we're building an MCP client, so we should call it that.
docs/mcp_servers.md
Outdated
You can have a MCP server running on a remote server. In this case, you'd use the | ||
[`MCPRemoteServer`][pydantic_ai.mcp.MCPRemoteServer] class: | ||
|
||
```python {title="basic_mcp_setup.py" test="skip"} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
```python {title="basic_mcp_setup.py" test="skip"} | |
```python {title="mcp_remote_server.py" test="skip"} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
also, these examples need to be run!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you suggest how? It's a remote server. I've enabled the other one.
@@ -0,0 +1,72 @@ | |||
# MCP Servers |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe rephrase the introduction phrase as something like:
PydanticAI supports integration with MCP Servers and act as a MCP client...
maybe link with https://modelcontextprotocol.io/introduction#general-architecture.
docs/mcp_servers.md
Outdated
PydanticAI comes with two ways to connect to MCP servers: | ||
|
||
- [`MCPRemoteServer`][pydantic_ai.mcp.MCPRemoteServer] which connects to an MCP server using the [HTTP SSE](https://modelcontextprotocol.io/docs/concepts/transports#server-sent-events-sse) transport | ||
- [`MCPSubprocessServer`][pydantic_ai.mcp.MCPSubprocessServer] which runs the server as a subprocess and connects to it using the [stdio](https://modelcontextprotocol.io/docs/concepts/transports#standard-input%2Foutput-stdio) transport |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I find it a bit confusing that the MCP*Server
classes are actually clients. Can't we name them MCP*Client
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we should see the object as a representation of the server.
command: str | ||
"""The command to run.""" | ||
|
||
args: Sequence[str] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Unlikely but this will allow MCPSubprocessServer('python', '-m pydantic_ai_examples.mcp_server')
which will not work as expected.
Maybe enforce list[str]
, or list[str] | tuple[str, ...]
? Feel free to leave it as is.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should I use https://docs.python.org/3/library/shlex.html ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure how shlex
behaves on non Unix shells, and I think it's best to enforce a proper sequence of str instead of trying to parse str
inputs
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So either we leave it as is if you think passing a bare string will not be a common mistake, or change the type hint to list[str] | tuple[str, ...]
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If someone get this mistake, I'll fix it.
Co-authored-by: Samuel Colvin <s@muelcolvin.com>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I can do some work on the docs tomorrow.
@@ -0,0 +1,72 @@ | |||
# MCP Servers |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
we should have a new section in docs:
mcp/index.md
- a general introduction saying PydanticAI can be used as an MCP client or to build servers, and comes with some serversmcp/client.md
- this page - describing how to use PydanticAI as an MCP clientmcp/run-python.md
- docs for MCP server to run Python code in a sandbox #1140- ... more
pydantic_ai_slim/pydantic_ai/mcp.py
Outdated
|
||
|
||
@dataclass | ||
class MCPSubprocessServer(MCPServer): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think I was wrong about this name, we should use the names MCP users which are clearly "stdio" and "sse", my mistake ✋ .
I guess we should call this MCPServerStdio
, and the other one MCPServerSSE
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think I was wrong about this name, we should use the names MCP users which are clearly "stdio" and "sse", my mistake ✋ .
I guess we should call this
MCPServerStdio
, and the other oneMCPServerSSE
.
...
Co-authored-by: Victorien <65306057+Viicos@users.noreply.github.com>
No description provided.