MCPModel Context ProtocolAI AgentTool CallingJSON-RPCAPI EngineeringNbility

MCP 到底是什么?用一次真实工具调用讲清 Host、Client 和 Server

MCP 不是模型 API,也不只是插件协议。本文通过一个真实订单查询工具,拆解 MCP Host、Client、Server、JSON-RPC、stdio 与 Streamable HTTP 的完整调用链。

MCP 到底是什么?用一次真实工具调用讲清 Host、Client 和 Server

如果只记一句话:

MCP 是 AI 应用连接外部数据、工具和工作流的标准协议。

它不是一个模型,不负责让模型“更聪明”;它也不是 OpenAI 兼容 API,不负责生成答案。它解决的是另一件事:让 AI 应用用统一方式发现和调用文件系统、数据库、浏览器、GitHub 或你自己的业务服务。

但“AI 的 USB-C”这个比喻仍然太抽象。本文不继续堆名词,而是运行一个真实的 get_order 工具,把 Host、Client、Server 和一次 JSON-RPC 调用逐步拆开。

先看答案:三个角色分别做什么

假设用户在桌面 AI 助手里问:

帮我查订单 NB-1001 的状态。

这时至少有三个不同角色:

角色在例子里是谁负责什么
Host桌面 AI 助手或 Coding Agent管理用户界面、模型、权限和多个 MCP 连接
ClientHost 内部的一条 MCP 会话与一个 Server 初始化、协商能力、发送请求和接收结果
Serverorder-tools 本地进程暴露 get_order 工具并真正查询订单数据

一个 Host 可以连接多个 Server,但通常会为每个 Server 创建独立 Client。这里的 Client 不是最终用户,也不是整个桌面应用,而是 Host 内负责一条 MCP 连接的协议组件。

Host 内的 Client 分别连接不同 MCP Server

数据流可以简化为:

用户
  ↓
Host(应用与模型编排)
  ↓
Client(MCP 会话)
  ↓ JSON-RPC
Server(工具实现)
  ↓
订单数据 / 外部 API / 数据库

MCP 的两层:协议消息和传输方式

理解 MCP 时,最好把它拆成两层。

数据层

数据层定义:

  • JSON-RPC 请求、响应和通知;
  • 初始化与能力协商;
  • tools/listtools/call 等方法;
  • Resources、Prompts、Tools 等协议原语;
  • 错误、进度和变更通知。

传输层

传输层负责把这些消息送到另一端。当前规范定义两种标准传输:

  • stdio:Host 启动本地 Server 子进程,通过标准输入和标准输出通信;
  • Streamable HTTP:Client 通过 HTTP POST/GET 与远程 Server 通信,服务端可选择用 SSE 流返回多条消息。

它们传递的是同一种 MCP JSON-RPC 消息。区别在进程边界、连接方式、认证和安全风险,而不是工具语义。

一次真实调用:我们实际运行了什么

为了避免把文档示例当运行结果,我用官方 Python SDK mcp 1.28.1 建了一个最小 Server,并通过 stdio 实际完成初始化、工具发现和调用。

1. Server:暴露订单查询工具

from mcp.server.fastmcp import FastMCP

mcp = FastMCP("order-tools")

ORDERS = {
    "NB-1001": {"status": "shipped", "items": 2},
    "NB-1002": {"status": "processing", "items": 1},
}


@mcp.tool()
def get_order(order_id: str) -> dict:
    """Look up an order by its public order ID."""
    return ORDERS.get(order_id, {"status": "not_found", "items": 0})


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

@mcp.tool() 会把函数名、描述和参数类型转换成工具定义。这里没有调用任何 LLM;Server 只是声明并执行业务能力。

2. Client:启动 Server 并调用工具

import asyncio
import sys

from mcp import ClientSession, StdioServerParameters
from mcp.client.stdio import stdio_client


async def main():
    params = StdioServerParameters(
        command=sys.executable,
        args=["server.py"],
    )

    async with stdio_client(params) as (read, write):
        async with ClientSession(read, write) as session:
            await session.initialize()

            tools = await session.list_tools()
            print([tool.name for tool in tools.tools])

            result = await session.call_tool(
                "get_order",
                {"order_id": "NB-1001"},
            )
            print(result)


asyncio.run(main())

SDK 隐藏了大部分协议细节,但 Wire 上实际发生的仍是下面这组 JSON-RPC 消息。

Wire 级时序:从初始化到结果

一次 MCP 工具调用的 JSON-RPC 时序

第一步:初始化连接

Client 发送:

{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "initialize",
  "params": {
    "protocolVersion": "2025-11-25",
    "capabilities": {},
    "clientInfo": {
      "name": "article-demo",
      "version": "1.0.0"
    }
  }
}

Server 返回它接受的协议版本、能力和实现信息:

{
  "jsonrpc": "2.0",
  "id": 1,
  "result": {
    "protocolVersion": "2025-11-25",
    "capabilities": {
      "prompts": { "listChanged": false },
      "resources": {
        "subscribe": false,
        "listChanged": false
      },
      "tools": { "listChanged": false }
    },
    "serverInfo": {
      "name": "order-tools",
      "version": "1.28.1"
    }
  }
}

然后 Client 发送不需要响应的通知:

{
  "jsonrpc": "2.0",
  "method": "notifications/initialized"
}

初始化必须是 Client 与 Server 的第一次协议交互。双方在这里确认版本和能力,之后才能进入正常操作阶段。

本次运行的 SDK 协商为 2025-11-25。规范版本会演进,生产代码应让 SDK 协商,而不是把本文版本写死。本文链接的稳定规范页面为 2025-06-18,角色和核心调用方法与本次演示一致。

第二步:发现工具

Client 请求:

{
  "jsonrpc": "2.0",
  "id": 2,
  "method": "tools/list",
  "params": {}
}

真实响应中的工具定义是:

{
  "jsonrpc": "2.0",
  "id": 2,
  "result": {
    "tools": [
      {
        "name": "get_order",
        "description": "Look up an order by its public order ID.",
        "inputSchema": {
          "type": "object",
          "properties": {
            "order_id": { "type": "string" }
          },
          "required": ["order_id"]
        }
      }
    ]
  }
}

这一步很重要:模型或 Host 不必提前硬编码每个工具的参数,它可以通过 Server 返回的 Schema 理解工具接受什么输入。

第三步:调用工具

Client 发送:

{
  "jsonrpc": "2.0",
  "id": 3,
  "method": "tools/call",
  "params": {
    "name": "get_order",
    "arguments": {
      "order_id": "NB-1001"
    }
  }
}

Server 真正执行 Python 函数,返回:

{
  "jsonrpc": "2.0",
  "id": 3,
  "result": {
    "content": [
      {
        "type": "text",
        "text": "{\n  \"status\": \"shipped\",\n  \"items\": 2\n}"
      }
    ],
    "isError": false
  }
}

这不是伪造的预期输出,而是本地 Server 在本文制作过程中返回的实际结果。

模型到底在调用链的哪里

前面的 Demo 完全没用模型也能运行。这说明 MCP 本身是 Client–Server 协议,不要求每次 tools/call 都由 LLM 发起。

在真实 AI Host 中,常见流程是:

  1. Host 通过 MCP Client 获取工具列表;
  2. Host 把可用工具的名称、描述和 Schema 提供给模型;
  3. 模型根据用户问题建议调用 get_order
  4. Host 根据权限策略决定是否批准;
  5. MCP Client 向 Server 发送 tools/call
  6. Host 把工具结果交回模型;
  7. 模型组织成用户看到的自然语言回答。

因此要分清两段协议:

Host ↔ 模型 Provider:OpenAI / Claude / Gemini 等模型 API
Host 内 MCP Client ↔ MCP Server:MCP JSON-RPC

MCP 不替代模型 API,模型 API 也不会自动把你的数据库变成 MCP Server。使用多家模型时,统一模型入口仍可交给 OpenAI 兼容 API 或网关处理;MCP 则负责工具和上下文连接。

Tools、Resources、Prompts 有什么区别

MCP Server 不只提供工具。

原语控制方式适合什么例子
Tools通常由模型选择调用执行动作或动态计算查订单、创建 Issue、运行查询
Resources通常由应用选择读取提供可寻址的上下文数据文件、数据库 Schema、日志
Prompts通常由用户显式选择提供可复用工作流模板代码审查、事故分析模板

“通常”很重要:协议描述了交互原语,但 Host 可以设计自己的确认界面、自动化策略和权限规则。

stdio 还是 Streamable HTTP

MCP 的 stdio 与 Streamable HTTP 传输

选 stdio,当 Server 跟着 Host 本地运行

适合:

  • 文件系统或本地 Git 工具;
  • 单用户桌面助手;
  • Host 可以安全启动的受信任进程;
  • 不需要独立网络认证的场景。

注意:stdout 只能输出合法 MCP 消息。调试日志应写到 stderr,否则一行普通日志就可能破坏协议流。

选 Streamable HTTP,当 Server 是远程共享服务

适合:

  • 多用户或多设备访问;
  • 独立部署、弹性伸缩和集中升级;
  • 需要 OAuth、审计、租户隔离的企业工具;
  • Server 不能由 Host 本地启动的场景。

不要把旧教程里的独立 “HTTP+SSE transport” 当当前标准。当前标准是 Streamable HTTP:Client 把 JSON-RPC 发送到一个 MCP Endpoint,Server 可以直接返回 JSON,也可以用 SSE 流式返回消息。

安全边界:工具能做事,也能做错事

MCP 统一了调用方式,但不会自动保证工具安全。

至少需要这些控制:

  1. 显示工具名称、参数和影响:用户知道将要发生什么;
  2. 高风险动作要求确认:删除文件、发送消息、付款不能静默执行;
  3. 最小权限:只读查询不要拿写权限,Server 不应继承不必要的环境变量;
  4. 输入验证:即使有 JSON Schema,也要做路径、ID、额度和租户校验;
  5. 输出视为不可信内容:网页和数据库文本可能包含 Prompt Injection;
  6. 日志与审计:记录谁调用了哪个工具,但不要泄露 API Key 和敏感返回;
  7. 远程 Server 做来源和认证校验:Streamable HTTP Server 应验证 Origin,本地部署应尽可能绑定 localhost,并使用正确认证;
  8. 超时和取消:Server 卡住时,Client 不能无限等待。

官方 Tools 规范特别建议保留 Human in the Loop,让用户有机会拒绝工具调用。后续我们会单独通过威胁模型拆解 MCP Server 安全,而不是把所有风险塞进一篇入门文。

常见误解

“装了 MCP,模型就知道我的数据”

不是。Server 要先暴露 Resource 或 Tool,Host 要连接它,并决定哪些信息可以进入模型上下文。

“一个 MCP Client 会同时连接所有 Server”

Host 可以管理多个 Server,但协议架构通常是一条 Client 会话对应一个 Server。这样能力协商、状态和权限边界更清楚。

“Tools 就是普通 Function Calling 的新名字”

两者有关,但层次不同。模型 Provider 的 Function Calling 描述模型如何请求函数;MCP 描述 Host 如何发现和调用外部 Server 的工具。Host 经常把 MCP Tool 转换成模型可理解的 Tool Schema,再把模型选择映射回 tools/call

“MCP Server 必须调用 LLM”

不需要。本文的订单 Server 没有任何模型依赖。Server 可以只是数据库适配器、浏览器控制器或业务 API 包装层。

“远程 MCP 就是把 stdio 暴露到公网”

不是。应使用 Streamable HTTP、认证、授权、Origin 校验、会话管理和安全部署,而不是转发裸进程管道。

什么时候值得做 MCP Server

适合:

  • 同一工具需要接入多个支持 MCP 的 Host;
  • 希望工具 Schema、错误和权限边界统一;
  • 外部系统变化频繁,不想为每个 Agent 重写适配;
  • 需要把工具运行与模型 Provider 解耦。

不一定适合:

  • 只有一个应用、一个简单内部函数;
  • 操作风险高,却没有审批和审计能力;
  • 只是想统一多家模型 API;
  • 团队还没有清楚定义工具权限和租户边界。

如果问题只是把 OpenAI、Claude 和 Gemini 统一到一个模型入口,优先处理 三家 API 格式差异 和网关适配,不要把 MCP 当成万能中间层。

总结

一次 MCP 工具调用并不神秘:

  1. Host 为 Server 建立 Client 会话;
  2. Client 用 initialize 协商版本和能力;
  3. Client 用 tools/list 发现工具与输入 Schema;
  4. Host 或模型选择工具,并经过权限决策;
  5. Client 用 tools/call 发起调用;
  6. Server 执行业务逻辑并返回内容或结构化结果;
  7. Host 决定如何展示或继续交给模型处理。

真正重要的不是记住每个 JSON 字段,而是分清边界:模型负责推理,Host 负责控制,Client 负责协议会话,Server 负责接入真实世界。

如果你的 Agent 同时要调用多家模型,Nbility 可以统一模型 API、用量和渠道;MCP Server 仍然由 Host 按工具权限独立连接。两者是互补关系,而不是替代关系。

官方资料

相关文章

结构化输出与 JSON Schema:如何让模型稳定返回 JSON
Structured OutputJSON SchemaOpenAI API

结构化输出与 JSON Schema:如何让模型稳定返回 JSON

模型返回的 JSON 经常少字段、类型错误或夹带 Markdown?本文解释 JSON Mode 与结构化输出的区别,并给出 OpenAI、Claude、Gemini 配置、Schema 设计、校验与重试方案。

用 Nbility 跑通你的 Agent 工作流

获取 API Key,统一接入 OpenAI 兼容模型和开发工具。

管理 API Key