a13nDocs

MCP 工具

连接 MCP server 提供的工具,并发送根据当前身份或 Run 生成的请求头。

使用 MCP 连接其他进程或服务的工具。MCP 管理连接配置;ContextualMCP 用于随 Run 变化的请求头。

需求选择
静态 server、stdio 进程、进程内 server 或预先构建的 Toolset原生 MCP
根据当前 Harness 身份或 Run 生成 URL server 的请求头ContextualMCP
Harness UI 中的命令或 JSON server 配置Harness UI MCP 配置

SDK 代码通过 MCP Capability 接入;Harness UI 使用 MCP 资源配置。

离线运行 MCP 工具

在源码环境中,将以下代码保存为 mcp_demo.py,执行 uv run python mcp_demo.py。示例启动进程内 server,两次调用 add 工具,无需 provider 凭据。

mcp_demo.py
import asyncio
from collections.abc import AsyncIterator

from fastmcp import Client, FastMCP
from pydantic_ai.capabilities import MCP
from pydantic_ai.messages import ModelMessage, ModelRequest, ToolReturnPart
from pydantic_ai.mcp import MCPToolset
from pydantic_ai.models.function import AgentInfo, DeltaToolCall, DeltaToolCalls, FunctionModel

from a13n_harness import AgentSpec, HarnessBuilder, RunBindings

server = FastMCP("counter")


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


async def respond(messages: list[ModelMessage], info: AgentInfo) -> AsyncIterator[str | DeltaToolCalls]:
    if isinstance(messages[-1], ModelRequest):
        for part in messages[-1].parts:
            if isinstance(part, ToolReturnPart):
                yield f"Result: {part.content}"
                return
    yield {0: DeltaToolCall(name=info.function_tools[0].name, json_args='{"a": 3, "b": 5}')}


async def main() -> None:
    executable = HarnessBuilder().build(AgentSpec(), output_type=str, model=FunctionModel(stream_function=respond))
    async with Client(server, mode="auto") as client:
        for prompt in ("Add 3 and 5", "Add them again"):
            projection = MCPToolset(client, id="calculator", cache_tools=False)
            bindings = RunBindings.embedded(capabilities=(MCP(id="calculator", local=projection),))
            result = await executable.run(prompt, bindings=bindings)
            print(result.output_or_raise())


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

两次 Run 都输出 Result: 8。Host 保持已进入的客户端,每次 Run 使用新的 MCPToolset 投影。

原生 MCP

将原生 MCP 加入 AgentSpec.capabilities 或 builder 的 capabilities= 参数。

可以直接从 AgentSpec 文档重建本地执行(local: True)的 URL server:

Python
from a13n_harness import AgentSpec

agent_spec = AgentSpec.from_dict(
    {
        "capabilities": [
            {
                "MCP": {
                    "url": "https://mcp.example.com/mcp",
                    "id": "knowledge",
                    "local": True,
                    "native": False,
                    "allowed_tools": ["search"],
                }
            }
        ]
    }
)

基础安装已包含本地 URL 和 stdio MCP 运行时。进程内 server、传输对象、脚本路径或预构建的 MCPToolset 需要在 Python 中构造 MCP。设置 defer_loading=True,可通过 load_capability 发现该 Capability;设置 native=True, local=False,由选定的模型 provider 执行 URL server。

Host 持有的客户端

多个 Run 需要使用同一服务器状态时,可信 Host 代码可以持有已进入的 FastMCP 客户端,为每个 Run 创建新的上游投影:

Python
from fastmcp import Client
from pydantic_ai.capabilities import MCP
from pydantic_ai.mcp import MCPToolset

from a13n_harness import RunBindings


async def use_host_client(executable):
    async with Client("https://mcp.example.com/mcp", mode="auto") as client:
        results = []
        for prompt in ("Create a workspace", "Inspect that workspace"):
            projection = MCPToolset(client, id="workspace", cache_tools=False)
            bindings = RunBindings.embedded(
                capabilities=(MCP(id="workspace", local=projection),)
            )
            results.append(await executable.run(prompt, bindings=bindings))
        return results

进入客户端前配置认证和输入处理器。mode="auto" 选择现代发现或旧版协商,也可显式选择 legacy 和 2026-07-28 模式。FastMCP 管理多轮输入和请求状态。客户端仅在相同身份与请求头下共享,每次 Run 创建新投影。业务调用丢失响应时,先检查 server 上的结果再重复调用。

用 ContextualMCP 生成当前 Run 的请求头

URL server 的请求头需要包含当前用户、Thread 或 Run 时,使用 ContextualMCP。Harness 在连接工具前,每次 Run 解析一次请求头。

常见的身份、关联关系、Run 和元数据值,可使用声明式解析器:

Python
from a13n_harness import (
    AgentIdentityRef,
    HarnessBuilder,
    RunBindings,
)
from a13n_harness.mcp import (
    ContextualMCP,
    MCPContextHeaderBinding,
    MCPContextHeaders,
    MCPContextHeadersConfig,
)

mcp = ContextualMCP(
    "https://mcp.example.com/mcp",
    id="knowledge",
    native=True,
    local=None,
    headers={"X-Application": "support"},
    headers_factory=MCPContextHeaders(
        MCPContextHeadersConfig(
            headers={
                "X-Run-ID": MCPContextHeaderBinding("context.run_id"),
                "X-Thread-ID": MCPContextHeaderBinding("context.thread_id"),
                "X-User-ID": MCPContextHeaderBinding("identity.user_id"),
                "X-Request-Context": MCPContextHeaderBinding(
                    "context.metadata.request_context",
                    required=False,
                ),
            }
        )
    ),
)

executable = HarnessBuilder().build(
    agent_spec,
    output_type=str,
    model=model,
    capabilities=(mcp,),
)

bindings = RunBindings.embedded(
    identity=AgentIdentityRef(
        issuer="my-host",
        subject="support-agent",
        user_id="user-123",
        agent_id="agent-support",
    ),
    metadata={
        "request_context": {
            "region": "us-east",
            "labels": ["interactive", "priority"],
        }
    },
)
result = await executable.run("Find the account record", bindings=bindings)

将附加 JSON 值放入 RunBindings.metadata;context.metadata.<key> 选择一个准确的顶层键。

声明式解析器只支持以下准确的来源:

来源解析值
identity.issuer工作负载身份的签发方
identity.subject工作负载身份的主体
identity.<claim>一个准确的身份 claim,例如 user_id
instance.agent_instance_id当前由 Host 管理的 Agent 实例 ID
instance.parent_agent_instance_id可选的父 Agent 实例 ID
instance.delegation_id可选的委派关联标识
instance.actor可选的 actor 字符串
context.run_id当前逻辑 Harness Run ID
context.thread_id当前 Thread 的 ID
context.metadata.<top-level-key>不可变 RunBindings.metadata 中的一个准确值

选中的字符串原样发送。JSON 数字、布尔值、对象和数组使用只含有限数值、键排序的紧凑 JSON。例如,{"region": "us-east", "labels": ["interactive"]} 会变成 {"labels":["interactive"],"region":"us-east"}。值缺失或为 None 时,必需绑定会失败,可选绑定则省略。

headers= 与 factory 解析结果中的请求头名称,不区分大小写时也不得重叠。authorization_token、allowed_tools、description 和 defer_loading 保留上游 MCP 行为。

自定义请求头 Factory

声明式解析器的来源不足时,可以使用自定义同步或异步 factory。它接收逻辑 Run 的完整可信 AgentContext,返回准确的字符串到字符串映射:

Python
from collections.abc import Mapping

from a13n_harness import AgentContext
from a13n_harness.mcp import ContextualMCP


def resolve_mcp_headers(context: AgentContext) -> Mapping[str, str]:
    return {
        "X-Run-ID": context.run_id,
        "X-Agent-Instance-ID": context.instance.agent_instance_id,
        "X-Tenant-ID": context.instance.identity.require_claim("tenant_id"),
    }


mcp = ContextualMCP(
    "https://mcp.example.com/mcp",
    id="tenant-tools",
    headers_factory=resolve_mcp_headers,
    native=True,
    local=None,
)

异步 factory 使用相同的输入输出约定:

Python
async def resolve_mcp_headers(context: AgentContext) -> Mapping[str, str]:
    route = await route_store.resolve(context.instance.identity)
    return {"X-Route": route}

Factory 每次 Run 运行一次,内部模型恢复复用该请求头快照。新 Run 会重新解析请求头。

本地与 Provider 原生执行

ContextualMCP 只接受基于 URL 的上游执行:

选择参数行为
本地默认native=False, local=None使用上游基于 URL 的本地 MCP 执行
自动native=True, local=None优先使用 provider 原生 MCP,并采用上游的本地回退
仅本地native=False, local=True要求本地 URL MCP 执行
仅原生native=True, local=False要求 provider 原生 MCP 执行

预构建客户端、传输对象、进程内 server、脚本和预构建 Toolset 已自行管理连接设置。这些值直接使用原生 MCP,不要与 ContextualMCP 组合。

ContextualMCP 要求应用配置 HTTP(S) URL。

Host 提供的配置

Host 可以通过自身的可信配置模型暴露同一条 URL 接入路径。保留 ContextualMCP 字段及准确的执行选择,不要再实现另一套 MCP 运行时。只持久化不含凭据的期望配置;构建 Capability 时,通过进程内 factory 解析请求头、短期凭据和当前路由。

每个 server 的 id 唯一时,一个 Agent 可以选择多个 MCP server。如果配置需要 callable factory、当前身份、静态请求头或独立的秘密解析器,应通过代码构建 ContextualMCP。

将大型本地 MCP server 的工具分组

在 ToolProxyCapability(groups=...) 中,将本地 MCP 或 ContextualMCP Capability 作为 ToolProxyGroup 来源,可以暴露分组发现接口,而不是所有工具 schema。选择 native=False, local=True;provider 原生工具和延后加载来源不能作为代理目标。原生组合保留新的 Run 绑定和上下文请求头,调用仍使用原 MCP Toolset 和传输。这减少的是模型上下文,不减少 MCP 初始化或工具列表读取工作。

结果边界

本地执行的 MCP 工具是普通的动态发现函数工具。它们返回的文本和 JSON 经过 Harness 必需的结果边界,过大时默认明确截断,而非写入外部文件。限制针对进入模型历史的值,不限制 MCP 客户端接收结果之前的传输体或进程内存。Provider 原生 MCP 执行仍走 provider 路径,不经过本地函数工具边界。

本页目录