连接 MCP 工具#

使用McpExtension连接由 Model Context Protocol 服务器提供的工具。模型看到的是普通的工具定义;当模型请求某个工具时,扩展会调用该服务器并返回结果。如果你已经有一套 MCP 集成,可以选择它,而不必把这些工具改写成本地 Python 函数。

连接 Streamable HTTP 服务器#

import os

from zett_agent.client import create_agent
from zett_agent.extensions.mcp import McpExtension, McpHttpServer
from zett_agent.extensions.memory import InMemoryMessageAccumulator
from zett_agent.extensions.tool_guidelines import ToolGuidelinesExtension

mcp = McpExtension(
    servers=[
        McpHttpServer(
            name="catalog",
            url=os.environ["MCP_URL"],
            headers={"Authorization": f"Bearer {os.environ['MCP_TOKEN']}"},
        ),
    ],
)
client = await create_agent(
    model,
    extensions=[InMemoryMessageAccumulator(), ToolGuidelinesExtension(), mcp],
)
reply = await client.run("Find the product matching this description.")

这是一个需要主动启用的网络示例:用连接模型提供商配置 model,并为受信任的服务器设置 URL 和令牌。如果服务器不要求 headers,可以省略它。请求头会在传输的整个生命周期中发送,但不会出现在服务器对象的repr中;你的应用仍然必须避免凭据出现在日志和版本控制里。

启动本地 stdio 服务器#

import sys
from pathlib import Path

from zett_agent.extensions.mcp import McpStdioServer

mcp = McpExtension(
    servers=[
        McpStdioServer(
            name="local",
            command=sys.executable,
            args=("server.py",),
            cwd=Path("trusted-server").resolve(),
        ),
    ],
)

请把路径和参数替换成你已经安装的服务器。命令和参数是分开传递的,不会被拼接进 shell 字符串。进程仍然以你应用的权限运行。只启动受信任的服务器,并在进程或容器边界上限制它们的文件系统、环境和网络访问。

使用配置文件#

mcp.json#
{
  "mcpServers": {
    "catalog": {
      "type": "streamable-http",
      "url": "https://your-server.example/mcp"
    },
    "local": {
      "command": "python",
      "args": ["/absolute/path/to/server.py"]
    }
  }
}

可以用McpExtension(config_path="mcp.json")加载它。servers和mcpServers都是可接受的根键,但一个文件只能使用其中一个。HTTP 条目可以包含headers;stdio 条目可以包含env和cwd。文件中的路径和命令由你负责;不要假定它们相对于配置文件解析。密钥不会有自动的${VAR}替换;当取值来自环境变量时,请在 Python 中构造服务器定义。

McpExtension()会查找~/.zett/mcp.json。传入显式的servers列表可以避免这种隐式查找。自定义的config_path可以和服务器列表一起使用;重复的服务器名称会被拒绝。配置文件不存在时不会加载任何服务器,而配置格式错误时会在构造扩展时抛出错误。

默认情况下(strict_config=True),只要有一个无效的服务器条目,整个文件就会失效,因此拼写错误绝不会被静默忽略。如果希望改为跳过无效条目,可以传入McpExtension(strict_config=False)或McpConfiguration.from_file(path, strict=False):每个被跳过的条目都会写入日志并列入McpConfiguration.skipped,有效的服务器仍然会加载。如果文件不是有效的 JSON 或者没有服务器映射,仍然会抛出错误。

每个请求开始时都会检查该文件,只有在修改时间或大小发生变化时才会重新读取,因此修改会在下一个请求中生效,无需重启应用。如果文件在应用运行期间变得无效,会写入日志,并继续使用最后一次有效的服务器列表。

工具名称和结果#

名为catalog的服务器中有一个名为search的工具,默认会以catalog__search的形式暴露。使用命名空间可以避免与本地工具以及其他服务器冲突。namespace_tools=False会保留原始名称,但此时你必须确保每个已注册的名称都唯一。

服务器提供的输入模式描述了这些工具。远程校验和你应用的策略仍然很重要;MCP 工具不会经过@tool函数校验器。如果结果带有structured_content,就会以该结构化值返回;否则内容块会被序列化成 JSON 数据。图像块不会被自动转成带类型的视觉消息。远程的is_error结果会变成模型可以看到的普通工具失败。

连接与失败#

每个请求开始时,都会并发发现所有服务器的工具列表,因此总延迟取决于最慢的服务器,而不是各服务器之和。工具列表会按服务器配置缓存tools_cache_ttl秒(默认 300;设为0表示禁用缓存)。缓存仍然有效时,请求不会启动任何进程,也不会打开任何套接字:服务器只有在模型第一次调用它的工具时才延迟连接,而且只有那个服务器会连接。请求无论是成功、失败、取消,还是执行compact(),都会关闭该请求打开的所有连接。

如果某个服务器无法在connect_timeout秒(默认 30)内连接或列出工具,它就会被跳过。模型会被告知哪个服务器不可用,其他服务器则继续正常工作。超过call_timeout秒(默认 300)的工具调用会失败,并抛出模型可以看到的TimeoutError;把任一超时设为None即可禁用它。工具调用过程中的普通失败会产生TOOL_FAILED,并允许模型继续。你可以选择哪些远程错误文本可以被显示。该扩展不会给远程服务器授予额外权限。

发送工具模式时不会包含title和$schema注解;服务器生成器会给每个属性都附上这些注解,而它们只是重复属性名。每个工具的 guidelines 也会被省略:系统消息已经说明<server>__<tool>形式的名称属于对应的服务器,因此为每个工具都重复一遍只会消耗提示词 token。属性的名称、类型、描述和约束都保持不变。

暴露的工具名称只能包含字母、数字、_和-,并且最多 64 个字符,这是各模型提供商接受范围的交集;更长的名称会用稳定哈希缩短。如果某个工具的暴露名称已经被注册,它会被跳过并给出警告,而不会让请求失败。如果服务器重复返回同一个分页游标,或者返回超过 100 页,就会被视为不可用。

离线验证集成#

async def main() -> None:
    local = LocalMcpClient()

    @asynccontextmanager
    async def connect(server: McpServer) -> AsyncGenerator[McpClient]:
        assert server.name == "demo"
        try:
            yield local
        finally:
            local.closed = True

    extension = McpExtension(
        servers=[McpHttpServer(name="demo", url="https://unused.example/mcp")],
        client_factory=connect,
    )
    client = await create_agent(McpModel(), extensions=[extension])
    reply = await client.run("Get the answer.")
    assert reply.content == "MCP tool result: 42"
    assert local.closed
    print(reply.content)
    print("MCP connection closed.")

在源码检出目录中运行uv run python docs/_examples/mcp_tools.py:

MCP tool result: 42
MCP connection closed.

完整示例通过client_factory提供了一个内存中的 MCP 客户端。它无需启动服务器,就能验证注册、分发、结构化结果和传输清理。

下一步:定义并注册工具介绍本地工具的行为,或者看注册服务商托管的工具,了解由模型提供商而非 MCP 服务器执行的工具。