如何将MCP工具与PydanticAI代理结合使用

2025年03月17日 由 alex 发表 4184 0

模型上下文协议(MCP)使得AI代理能够连接到工具和资源,然而目前PydanticAI框架并不原生支持这一协议。在这里,我将展示如何克服这一限制,以便你能够构建强大的AI代理!(剧透:过程并不那么美观。)


什么是MCP?

模型上下文协议(MCP)是由Anthropic提出的一项倡议,旨在定义一种标准化方式,使AI代理能够连接到外部上下文,如提供信息的资源或执行操作的工具。总体思路是设立一个MCP服务器,提供一组工具或资源,以及一个MCP客户端,该客户端可以连接到一个或多个服务器,以访问它们的功能并将其提供给AI代理。


这旨在消除提供上下文或工具的服务与需要访问这些服务的AI框架或模型之间的耦合。


PydanticAI

PydanticAI是一个Python代理框架,旨在减轻使用生成式AI构建生产级应用程序的痛苦。


与LangChain等笨重的替代方案相比,该框架在开发AI代理时通常更加直观和灵活。


然而,它目前还不原生支持MCP,因此创建能够利用通过该协议交付的所有酷炫功能的AI代理并不那么直接。我决定借此机会深入探索MCP,并找出如何“从零开始”将其集成!


准备MCP服务器

我想专注于将PydanticAI代理与现有的MCP服务器实现连接起来,而不是创建自己的服务器(因为这正是协议的目的!)。幸运的是,有一个存储库提供了基本MCP服务器的参考实现,这些服务器可以访问Slack、Google Drive、Github以及各种数据库等服务。为了简单起见,我决定使用文件系统服务器,以帮助创建一个能够协助软件开发的代理,该代理能够读取和编辑文件。


它作为一个简单的Node包/工具实现,可以在本地运行,并通过标准输入输出(STDIO)进行通信。


npx -y @modelcontextprotocol/server-filesystem "/path/to/working/dir""/path/to/working/dir"


经过一些测试后,我很快发现文件搜索和目录树工具存在一些问题,并且在代码仓库中使用起来并不实际,因为它们会被node_modules或Python虚拟环境中的大量内容,或其他通常会被Git或集成开发环境(IDE)忽略的文件所淹没。因此,我复制了一份服务器实现,并添加了一些自定义增强功能,使相关工具能够遵循任何.gitignore规则,从而使它们的输出更加可用。(我还提交了一个拉取请求来修复search_files工具的行为。)


为PydanticAI代理提供工具

下一个挑战是如何将MCP服务器提供的功能注册为PydanticAI代理的工具。使用MCP Python SDK启动一个MCP服务器,用客户端连接到它,并查看它提供了哪些工具,过程如下:


from mcp import ClientSession, StdioServerParameters
from mcp.client.stdio import stdio_client
# Define server configuration
server_params = StdioServerParameters(
      command="npx",
      args=[
          "tsx",
          "server/index.ts",
          "path/to/working/directory",
      ],
  )
# Start server and connect client
async with stdio_client(server_params) as (read, write):
    async with ClientSession(read, write) as session:
        # Initialize the connection
        await session.initialize()
        
        # Get details of available tools
        tools_result = await session.list_tools()
        tools = tools_result.tools


在这里,tools是一个工具Pydantic模型的列表,它们以大多数大型语言模型(LLM)API所要求的工具规范格式,定义了每个可用工具的名称、描述和输入模式。然而,不幸的是,PydanticAI目前不支持直接提供函数工具的输入模式;它只能通过检查函数签名来构建输入模式。


因此,为了绕过这一限制,我需要构建一种能力,将MCP工具规范转换为动态函数定义,仅仅是为了让PydanticAI能够再次将其转换回JSON工具规范 。大致上,它的工作原理是这样的:


async def get_tools(session: ClientSession) -> list[Tool[AgentDeps]]:
    """
    Get all tools from the MCP session and convert them to Pydantic AI tools.
    """
    tools_result = await session.list_tools()
    return [pydantic_tool_from_mcp_tool(session, tool) for tool in tools_result.tools]

def pydantic_tool_from_mcp_tool(session: ClientSession, tool: MCPTool) -> Tool[AgentDeps]:
    """
    Convert a MCP tool to a Pydantic AI tool.
    """
    tool_function = create_function_from_schema(session=session, name=tool.name, schema=tool.inputSchema)
    return Tool(name=tool.name, description=tool.description, function=tool_function, takes_ctx=True)


对于create_function_from_schema()的实现,关键部分是,我们需要为每个工具定义一个函数,该函数向MCP服务器发出实际请求,服务器将执行实际操作,然后返回响应。这看起来像这样:


def create_function_from_schema(session: ClientSession, name: str, schema: Dict[str, Any]) -> types.FunctionType:create_function_from_schema(session: ClientSession, name: str, schema: Dict[str, Any]) -> types.FunctionType:
    """
    Create a function with a signature based on a JSON schema. This is necessary because PydanticAI does not yet
    support providing a tool JSON schema directly.
    """
    # Create parameter list from tool schema
    parameters = convert_schema_to_params(schema)
    # Create the signature
    sig = inspect.Signature(parameters=parameters)
    # Create function body
    async def function_body(ctx: RunContext[AgentDeps], **kwargs) -> str:
        # Call the MCP tool with provided arguments
        result = await session.call_tool(name, arguments=kwargs)
        # Assume response is always TextContent
        if isinstance(result.content[0], TextContent):
            return result.content[0].text
        else:
            raise ValueError("Expected TextContent, got ", type(result.content[0]))
    # Create the function with the correct signature
    dynamic_function = types.FunctionType(
        function_body.__code__,
        function_body.__globals__,
        name=name,
        argdefs=function_body.__defaults__,
        closure=function_body.__closure__,
    )
    # Add signature and annotations
    dynamic_function.__signature__ = sig  # type: ignore
    dynamic_function.__annotations__ = {param.name: param.annotation for param in parameters}
    return dynamic_function


整合所有内容

现在我们已经有了一组标准的PydanticAI工具,它们可以直接提供给代理,像平常一样使用,例如:


from pydantic_ai import Agent
from rich.console import Console
from rich.prompt import Prompt
...
def run():  
    # Configure Model and Agent dependencies
    ...
    # Initialise & connect MCP server, construct tools
    ...
    agent = Agent(
        model=model,
        deps_type=type(deps),
        system_prompt=SYSTEM_PROMPT,
        tools=tools,
    )
    message_history: list[ModelMessage] = []
    while True:
        prompt = Prompt.ask("[cyan]>[/cyan] ").strip()
        if not prompt:
            continue
        # Handle special commands
        if prompt.lower() in EXIT_COMMANDS:
            break
        # Process normal input through the agent
        result = await agent.run(prompt, deps=deps, message_history=message_history)
        response = result.data
        console.print(f"[bold green]Agent:[/bold green] {response}")
        message_history = result.all_messages()


自己尝试一下使用文件系统代理,看看它能做什么!以下是一个示例:


Welcome to MCP Demo CLI! Type /quit to exit.exit.
[DEBUG] Starting MCP server with working directory: /Users/finn.andersen/projects/mcp_demo
[DEBUG] Secure MCP Filesystem Server running on stdio
[DEBUG] Allowed directories: [ '/Users/finn.andersen/projects/mcp_demo' ]
> : Create a file that contains a directory tree structure of the project in a hierarchical format which shows depth of each item using indentation, using a similar format as the result of the "tree" tool. Exclude hidden files and folders
[DEBUG] Calling tool directory_tree with arguments: {'path': '.'}
[DEBUG] Calling tool write_file with arguments: {'path': 'directory_tree.txt', 'content': '...'}
Agent: The directory tree has been successfully written to a file named `directory_tree.txt`. If you need anything 
else, feel free to ask!
> :


它成功创建了一个名为directory_tree.txt的文件,内容如下:


Makefile
README.md
client
    __init__.py
    mcp_agent
        __init__.py
        agent.py
        cli.py
        deps.py
        llm.py
        run.py
        tools.py
        util
            __init__.py
            schema_to_params.py
pyproject.toml
requirements-dev.txt
requirements.txt
server
    Dockerfile
    README.md
    index.ts
    package-lock.jsonlock.json
    package.json
    test.ts
    tsconfig.json
uv.lock


总结

我希望这有助于大家理解MCP的工作原理,以及如何利用它将AI代理与新功能集成。快去用那些免费可用的MCP服务器构建一些很酷的东西吧!

文章来源:https://medium.com/@finndersen/how-to-use-mcp-tools-with-a-pydanticai-agent-0d3a09c93a51
欢迎关注ATYUN官方公众号
商务合作及内容投稿请联系邮箱:bd@atyun.com
评论 登录
热门职位
Maluuba
20000~40000/月
Cisco
25000~30000/月 深圳市
PilotAILabs
30000~60000/年 深圳市
写评论取消
回复取消