关于 MCP Proxy
MCP Proxy 是用 TypeScript 编写的代理,可将基于 stdio 的 MCP 服务通过流式 HTTP 和 SSE 对外提供。它面向需要通过 HTTP 客户端部署或共享此类服务的场景,流式 HTTP 端点支持 2025 年代协议修订版及 2026-07-28 修订版。
主要功能
- 01stdio 转 HTTP 与 SSE 代理
- 02单一端点支持多个协议修订版
- 03可选 API 密钥认证
- 04无状态 HTTP 模式
- 05断线后重放事件
- 06可配置 CORS
适用场景
- 01让 HTTP 客户端访问 stdio 服务
- 02在同一网址服务新旧协议客户端
- 03在无服务器部署中独立处理请求
- 04通过公共隧道共享本地服务
MCP 相关
它通过 stdio 启动 MCP 服务,并经流式 HTTP 和 SSE 向 MCP 客户端提供其能力。HTTP 端点支持 2025 年代修订版和 2026-07-28;stdio 服务支持 2025 年代客户端。
配置
本地服务的配置在各客户端中通用,复制后粘贴到客户端的 MCP 配置文件即可。
工具
本地服务需要在你的电脑上运行后才能读取工具,维护者暂未提供工具列表。
详细介绍
MCP 代理
面向使用 stdio 传输的 MCP 服务的 TypeScript 流式 HTTP 和 SSE 代理。
说明
默认启用 CORS,并提供可配置选项。详情请参阅 CORS 配置。
说明
如需反向操作——让仅支持 stdio 的客户端访问远程 MCP 服务,包括交互式 OAuth 流程——请参阅 mcp-remote。
说明
FastMCP 使用 MCP Proxy 来启用流式 HTTP 和 SSE。
协议修订版本
MCP Proxy 在同一端点提供协议修订版本 2026-07-28 和 2025 年时期的修订版本
(2024-10-07 至 2025-11-25)。每个发往流式 HTTP 端点的 POST 请求都会根据其自身内容分类——
2026-07-28 修订版本会在每个请求上添加 _meta 封装,而不含该封装的请求则由 2025 年时期的会话机制处理——因此新旧客户端可以共用一个 URL,
彼此无需知道对方的存在。
| 2025 年时期的客户端 | 2026-07-28 客户端 | |
|---|---|---|
/mcp(流式) | 是,有会话 | 是,按请求处理 |
/sse(HTTP+SSE) | 是 | 不适用——不存在 SSE 传输 |
stdio(startStdioServer) | 是 | 尚未支持——见下文 |
| 更改通知 | 主动推送 | subscriptions/listen 流 |
两个时期的进度通知和取消操作都能穿过代理:转发的调用会携带调用方的中止信号,中继的进度通知会重新标记为调用方发送的令牌。
使用 --no-modern 仅为 2025 年时期的客户端提供服务;此时,2026-07-28 客户端会由 2025 年时期的机制处理,而该机制无法识别其请求。
现代协议端使用流式 HTTP 端点,因此禁用该端点时也不会提供现代协议支持(--server sse 或 streamEndpoint: null)。
上游连接(即 MCP Proxy 启动的服务)会独立协商自身的修订版本,因此任意组合均可使用。默认使用 2025 年的 initialize
握手;传入 --upstreamProtocol auto 可先通过 server/discover 探测(在代理 2026-07-28 服务时需要),或传入
--upstreamProtocol modern 以要求使用现代协议。
# front a 2026-07-28 stdio server, serving both eras downstream
npx mcp-proxy --upstreamProtocol auto -- my-modern-mcp-server
不会通过代理传递的内容
这些都是同一设计决策带来的后果——代理将所有下游连接复用到单个上游连接上——因此在此列出,而不是只解决一半。
- 任何需要客户端提供输入的功能(elicitation、sampling、roots)。2025 年的服务会通过带外方式发出请求,且不携带任何标识来说明请求属于哪个调用方,因此中继请求就意味着要猜测应将提示词显示在哪个用户的屏幕上。2026-07-28 上游没有这种歧义——请求会作为代理转发的调用的
input_required结果返回——因此,是否支持这一方向取决于作用范围,而非关联方式。尚未实现。 - **2026-07-28 连接两端的日志消息。**该修订版按请求传递日志:客户端在请求的
_meta中发起请求,服务端在该请求的流中响应。代理无法处理这两部分——在下游,它没有按请求设置的日志回调,传入通知处理程序的内容也无法将日志归属于某个请求;在上游,它根本无法请求日志,因此 2026-07-28 服务的按请求日志记录永远不会触发。2025 年的客户端仍能收到已弃用的非请求路径中的日志:包括来自 2025 年上游的所有日志,以及来自 2026-07-28 上游的、在按请求通道之外发送的日志;符合规范的服务不会使用该路径。 - **上游的
logging/setLevel。**代理会针对每个下游连接响应并遵循其设置——低于该连接所请求级别的消息不会转发给它——但不会将其传递到上游:共享的上游只有一个日志级别,因此转发会让一个客户端屏蔽另一个客户端的日志,而且 2026-07-28 上游根本不支持此方法。 - **客户端身份。**上游看到的客户端是
mcp-proxy,而非下游调用方——以此为键的任何遥测也一样。 - 基于 stdio 的 2026-07-28。
startStdioServer仅服务 2025 年的客户端;HTTP 端则同时服务两者。
说明
/sse端点使用的是冻结的@modelcontextprotocol/server-legacy中的 HTTP+SSE 传输副本,该传输已从 v2 SDK 中移除。该传输自2026-07-28起弃用,并设有十二个月的迁移期限;请将客户端迁移到可流式传输的 HTTP 端点。
安装
npm install mcp-proxy
快速入门
命令行
# no mcp-proxy options
npx mcp-proxy npx -y @anthropic/mcp-server-filesystem /path
# with mcp-proxy options
npx mcp-proxy --port 8080 --shell -- tsx server.js
第二条命令会通过 stdio 启动 tsx server.js,并在端口 8080 的 /mcp(可流式传输的 HTTP)和 /sse(SSE)上提供服务。
说明
关于
--分隔符:
- 如果不需要向 mcp-proxy 传递选项,
--分隔符是可选的- 如果需要向 mcp-proxy 传递选项(例如
--port、--shell等),请使用--将这些选项与命令明确分开- 不使用
--时,第一个位置参数会被视为命令,之后的所有参数都会传递给该命令- 如果命令本身带有可能与 mcp-proxy 选项冲突的标志,
--分隔符也很有用
选项:
--server:设置为sse或stream,仅启用对应的传输方式(默认:两者都启用)--endpoint:如果server设置为sse或stream,此选项用于设置端点路径(默认:/sse或/mcp)--sseEndpoint:设置 SSE 端点路径(默认:/sse)。如果server设置为sse,则覆盖--endpoint。--streamEndpoint:设置可流式传输的 HTTP 端点路径(默认:/mcp)。如果server设置为stream,则覆盖--endpoint。--stateless:每次请求都创建一个新的服务实例,而不是维持会话。适用于 2025 年版本;2026-07-28 版本按设计采用逐请求模式。--modern:在可流式传输的 HTTP 端点上,除 2025 年版本外,同时提供 2026-07-28 协议修订版(默认:true)。使用--no-modern仅为 2025 年版本的客户端提供服务。请参阅协议修订版。--upstreamProtocol:指定与启动的服务通信时使用的协议修订版——legacy(默认)、auto或modern。请参阅协议修订版。--port:指定监听端口(默认:8080)--connectionTimeout:连接到 MCP 服务的初始连接超时时间,单位为毫秒(默认:60000)--requestTimeout:向 MCP 服务发送请求的超时时间,单位为毫秒(默认:300000)--keepAliveTimeout:有状态流式会话的 HTTP keep-alive 超时时间,单位为毫秒(默认:300000)--sessionIdleTimeout:有状态流式会话在没有连接的流或请求时,保持存活的时长,单位为毫秒;超时后会话将关闭(默认:1800000,即 30 分钟)。设置为0,可使每个会话一直保留,直到客户端发送DELETE。请参阅已放弃的会话。--eventStore:启用可恢复性事件存储,让客户端在重新连接后重放遗漏的消息(默认:true)。--no-eventStore可禁用此功能。请参阅可恢复性与内存使用。--eventStoreMaxEvents:可恢复性事件存储为每个会话保留的缓冲事件上限,达到上限后会淘汰最早的事件(默认:1000)。设置--no-eventStore时,此选项会被忽略。--maxBodySize:可流式传输的 HTTP 端点接受的最大请求正文大小,单位为字节;超过限制的请求会收到413 Payload Too Large响应(默认:10485760,即 10 MiB)。设置为0可禁用限制。请参阅请求正文大小。--debug:启用调试日志--shell:通过用户的 shell 启动服务--apiKey:用于验证请求的 API 密钥(使用 X-API-Key 标头)--sslCa:覆盖受信任 CA 证书的文件名--sslCert:PEM 格式的证书链文件名--sslKey:PEM 格式的私钥文件名--tunnel:通过公共隧道公开代理(请参阅公共隧道)--tunnelSubdomain:为隧道请求特定子域名(不保证可用)--corsAddAllowedHeader:向Access-Control-Allow-Headers添加标头名称(保留默认值)。可重复使用以添加多个标头。与--apiKey搭配运行时很有用,可确保浏览器针对X-API-Key的预检请求成功。
Python stdio 服务故障排查
如果 Python stdio 服务因 Expected server to respond to ping 超时,请以非缓冲模式运行 Python——缓冲的 stdout 可能会延迟 JSON-RPC 消息,导致代理将服务视为无响应:
npx mcp-proxy -- python -u -m your_package.mcp_server
# or: PYTHONUNBUFFERED=1 npx mcp-proxy -- python -m your_package.mcp_server
stdio 服务还应将 stdout 保留给协议消息——将诊断信息发送到 stderr,并使用 --debug 查看代理端日志。
公共隧道
将本地服务暴露到公共互联网——可用于测试 Webhook、分享开发服务或远程访问。
npx mcp-proxy --port 8080 --tunnel -- tsx server.js
# request a specific subdomain
npx mcp-proxy --port 8080 --tunnel --tunnelSubdomain myapp -- tsx server.js
隧道建立后,你会看到类似这样的消息:
tunnel established at https://abcdefghij.tunnel.gla.ma
说明
请求的子域名可能不可用。隧道建立后会显示实际 URL。
由 pipenet 提供支持,由 glama.ai 赞助——请参阅公告。
无状态模式
默认情况下,每个客户端连接都会在其会话期间持有一个服务实例。--stateless 则会在每次请求时创建一个新实例,因此请求彼此完全独立——适用于无服务器平台、负载均衡部署,以及任何不希望保留连接状态的场景。
npx mcp-proxy --port 8080 --stateless -- tsx server.js
# stream-only transport
npx mcp-proxy --port 8080 --stateless --server stream -- tsx server.js
说明
无状态模式仅影响 HTTP 可流式传输(
/mcp端点)。SSE 传输行为保持不变。
API 密钥身份验证
此功能可选,默认关闭。启用后,客户端必须在 X-API-Key 标头中发送有效密钥。
npx mcp-proxy --port 8080 --apiKey "your-secret-key" -- tsx server.js
# or via the environment
export MCP_PROXY_API_KEY="your-secret-key"
npx mcp-proxy --port 8080 -- tsx server.js
客户端通过传输标头传递密钥:
const transport = new StreamableHTTPClientTransport(
new URL("http://localhost:8080/mcp"),
{ headers: { "X-API-Key": "your-secret-key" } },
);
/ping 和 OPTIONS 预检请求不受此限制。密钥通过标头传输,因此在生产环境中请使用 HTTPS。
CORS 配置
默认行为
默认启用 CORS,配置如下:
- Origin:
*(允许所有来源) - Methods:
GET, POST, DELETE, OPTIONS - Headers:
Content-Type, Authorization, Accept, Mcp-Session-Id, Mcp-Protocol-Version, Last-Event-Id, Mcp-Method, Mcp-Name - Credentials:
true - Exposed Headers:
Mcp-Session-Id
只要允许的来源最终为 *,就会省略 Access-Control-Allow-Credentials,因为 Fetch Standard 规定,浏览器同时看到这两者时会拒绝整个 CORS 响应。因此,带凭据的浏览器请求(credentials: "include"、withCredentials)需要显式指定 origin——使用列表或函数,而不是通配符。
2026-07-28 版的可流式传输 HTTP POST 请求需要 Mcp-Method 和 Mcp-Name,因此使用该修订版的浏览器客户端需要将它们加入允许列表。该修订版的 Mcp-Param-* 标头采用前缀形式,Access-Control-Allow-Headers 无法表达这种形式——如果你的工具声明了 x-mcp-header 参数,请使用 --corsAddAllowedHeader 或 allowedHeaders 显式添加这些标头名称。
配置
cors: true(或省略)使用默认值,cors: false 会禁用 CORS,而 CorsOptions 对象可覆盖各个字段:
import { startHTTPServer, CorsOptions } from "mcp-proxy";
const cors: CorsOptions = {
// A list, or a function for dynamic validation
origin: ["https://app.example.com"],
methods: ["GET", "POST", "DELETE", "OPTIONS"],
// "*" allows any header — the usual fix for browser preflight failures.
// Listing headers instead REPLACES the defaults, so restate the protocol's
// own: dropping Mcp-Method/Mcp-Name breaks browser clients on 2026-07-28.
allowedHeaders: "*",
exposedHeaders: ["Mcp-Session-Id"],
credentials: true,
maxAge: 86400,
};
await startHTTPServer({ cors, createServer, port: 3000 });
从 5.5.6 或更早版本升级时,标头曾自动设为通配符?设置 allowedHeaders: "*" 即可恢复该行为。
CLI:添加允许的标头
--corsAddAllowedHeader 会将标头追加到默认的 Access-Control-Allow-Headers 列表中,同时保留默认值。重复使用该选项即可添加更多标头。常见用途是在 --apiKey 下解除自定义身份验证标头的浏览器预检限制:
npx mcp-proxy --port 8080 --apiKey secret \
--corsAddAllowedHeader X-API-Key \
-- npx -y @modelcontextprotocol/server-filesystem /srv
如需设置来源允许列表、使用通配符标头或禁用 CORS,请使用上文的程序化 cors 选项。
Node.js SDK
proxyServer
在服务端和客户端之间建立代理。
const transport = new StdioClientTransport();
const client = new Client();
await client.connect(transport);
const serverCapabilities = client.getServerCapabilities();
const server = new Server(client.getServerVersion(), {
capabilities: serverCapabilities,
});
await proxyServer({
client,
server,
serverCapabilities,
});
在此示例中,服务端会将所有请求代理给客户端,反之亦然。处理程序根据 serverCapabilities 注册,因此,对于上游从未声明的方法,本地会直接返回“method not found”,而不是发起往返请求进行确认。
选项:
client:已连接的上游客户端server:要接入的下游服务端serverCapabilities:上游声明的能力;用于决定注册哪些处理程序requestTimeout:应用于每个转发请求的超时时间,以毫秒为单位(可选)
说明
一次
proxyServer调用对应一个下游连接,但这些连接共用一个上游客户端,因此每条上游通知只会接收一次,然后转发给所有已连接的客户端。有两类通知会在转发前进行筛选:notifications/resources/updated只会发送给订阅了相应 URI 的连接;notifications/message只会发送给logging/setLevel允许接收该消息的连接——从未设置级别的连接仍会收到所有消息。list_changed通知不会筛选——它们描述的是所有连接都在查看的同一个上游。如果你在createServer中派生每个请求的身份信息,以便通过一个代理为多个租户提供服务,就需要检查这一点是否符合你的模型。
startHTTPServer
启动一个监听 port 的代理,并通过 StreamableHTTPServerTransport 和 SSEServerTransport 向已连接的服务发送消息。
import { Server } from "@modelcontextprotocol/server";
import { startHTTPServer } from "mcp-proxy";
const { close, notify } = await startHTTPServer({
createServer: async () => {
return new Server();
},
port: 8080,
stateless: false, // Optional: enable stateless mode for streamable HTTP transport
});
close();
选项:
createServer:为每个连接创建新服务实例的函数eventStore:用于支持可恢复性的流式 HTTP 传输事件存储(可选)。传入false可完全禁用可恢复性;省略此项则为每个会话创建一个全新的、有容量限制的InMemoryEventStore(参见eventStoreMaxEvents);传入EventStore实例则使用你自己的存储(例如持久化存储或跨进程存储),并在所有会话间共享。参见可恢复性与内存使用。eventStoreMaxEvents:限制自动创建的每会话InMemoryEventStore在淘汰最旧事件前保留的事件数(默认值:1000)。仅在未显式提供eventStore时适用。port:要监听的端口号host:要绑定的主机(默认值:"::")keepAliveTimeout:有状态流会话的 HTTP keep-alive 超时时间,单位为毫秒(默认值:300000)sessionIdleTimeout:有状态流会话在没有连接流且没有请求的情况下,持续多久后关闭,单位为毫秒(默认值:1800000,即 30 分钟)。传入0可使所有会话一直保持,直到客户端发送DELETE。参见已放弃的会话。maxBodySize:限制流式 HTTP 端点缓冲的请求体字节数;更大的请求将收到413 Payload Too Large(默认值:10485760,即 10 MiB)。传入false可禁用限制。参见请求体大小。sseEndpoint:SSE 端点路径(默认值:"/sse",设为 null 可禁用)streamEndpoint:流式 HTTP 端点路径(默认值:"/mcp",设为 null 可禁用)stateless:为 HTTP 流式传输启用无状态模式(默认值:false)。适用于 2025 年版本的协议;协议修订版 2026-07-28 按设计采用逐请求模式。modern:在streamEndpoint上与 2025 年版本的协议修订版一同提供协议修订版 2026-07-28(默认值:true)。参见协议修订版。apiKey:用于验证请求的 API 密钥(可选)cors:CORS 配置(默认值:启用宽松设置,参见 CORS 配置部分)onConnect:创建服务实例时的回调(可选)。在 2025 年版本的协议中,每个会话触发一次;在 2026-07-28 版本的协议中,每个请求触发一次,因为每个请求都会创建一个全新实例。onClose:拆除服务实例时的回调(可选)。触发单位与onConnect按协议版本保持一致;请确保回调开销低且幂等。若回调被拒绝,系统会记录日志,但不会停止其余拆除操作。onUnhandledRequest:未处理 HTTP 请求时的回调(可选)
返回 { close, notify }。关于 notify 的用途,请参见变更通知。
变更通知
2025 年时期的客户端会在自己的连接上接收主动发送的 list_changed 和 resources/updated 通知,而 proxyServer 会为你将它们转发到该连接。
2026-07-28 协议修订版没有这样的连接:它只会在客户端打开的 subscriptions/listen 流上发送这些事件,而为其提供服务的各个请求级服务实例都不拥有流。这些事件会改为发布到处理程序的事件总线上,这也是 startHTTPServer 作为 notify 返回的对象。将上游变更事件接入该对象:
import { getUpstreamBridge, startHTTPServer } from "mcp-proxy";
const { notify } = await startHTTPServer({ createServer, port: 8080 });
getUpstreamBridge({ client }).subscribe({
promptsListChanged: () => notify.promptsChanged(),
resourcesListChanged: () => notify.resourcesChanged(),
resourceUpdated: ({ uri }) => notify.resourceUpdated(uri),
toolsListChanged: () => notify.toolsChanged(),
});
当 modern: false 时,notify 不会执行任何操作,因此始终保留这段接线逻辑是安全的。mcp-proxy CLI 正是这么做的。
getUpstreamBridge(client) 会为每个客户端注册一次上游通知处理程序,并将通知分发给多个接收方,因此多个下游连接可以共享一个上游服务。subscribe(sink) 会返回一个租约,包含 addResourceSubscription / removeResourceSubscription / owns / release。资源订阅按租约归属,因此一个连接取消订阅,不会取消另一个连接仍需要的 URI;调用 release() 则会释放断开连接的连接持有的所有订阅。
2026-07-28 协议修订版的客户端通过 subscriptions/listen 过滤器的 resourceSubscriptions 字段请求资源更新,而不是使用 resources/subscribe。传入 onListenSubscriptions 来处理这些 URI;CLI 使用的实现是 acquireListenSubscriptions,它会以全有或全无的方式获取所有订阅,避免过滤器仅被上游部分接受时,剩余订阅无人处理:
import { acquireListenSubscriptions } from "mcp-proxy";
await startHTTPServer({
createServer,
onListenSubscriptions: (uris) => acquireListenSubscriptions({ client, uris }),
port: 8080,
});
可恢复性与内存用量
可流式 HTTP 传输可以保留服务端→客户端的消息,以便客户端携带 Last-Event-ID 重新连接时从中断处继续。每个会话都有独立的 InMemoryEventStore,最多保存 1000 个事件(通过 --eventStoreMaxEvents / eventStoreMaxEvents 设置上限;优先淘汰最早的事件),因此长期运行的会话内存用量始终受限,不会在进程整个生命周期内持续增长。
不需要在重新连接后恢复吗?使用 --no-eventStore(CLI)或 eventStore: false(库)即可完全关闭相关记录。若要保留更长的重放窗口,或在进程之间共享存储(例如使用 Redis),请传入自己的 EventStore——此时需要由你负责限制其大小。
已放弃的会话
2025 年时期的会话会在创建它的连接结束后继续存在——这正是重新连接后能够重放消息的原因——并且原本应通过 DELETE 结束。实际上,大多数客户端都不会发送该请求:它们会关闭笔记本电脑、被终止或失去网络连接,此后便再无消息。每个遗留会话都会持有一个服务实例、一个传输和一个事件存储,并继续缓冲已无法送达的通知。
当会话没有任何连接,且 30 分钟内没有任何活动时,mcp-proxy 会关闭该会话(通过 --sessionIdleTimeout / sessionIdleTimeout 设置)。只要会话仍有流连接,无论多安静都不会被关闭,因此停留在通知流上的客户端不受影响;倒计时从流断开时开始,而不是从最后一次请求时开始。关闭会执行与 DELETE 相同的流程,因此 onClose 会像以往一样触发。
代价是无法在超时之后恢复会话:客户端稍后携带 Last-Event-ID 返回时,会获得一个新会话,而不是重放旧消息。默认的内存存储最多保存 1000 个事件,到那时通常也没什么可重放的了。将超时时间设为 0,即可保留所有会话,直到客户端发送 DELETE。
/sse 端点上的会话会随连接结束,不受此影响;--stateless 模式和 2026-07-28 协议修订版也一样,因为它们都不会在请求之间保留任何内容。
请求正文大小
流式 HTTP 端点会在解析请求正文之前将其缓冲,因此慢速分块发送的客户端可能会让进程内存持续增长,直到发送结束。mcp-proxy 会将该缓冲区限制为 10 MiB(--maxBodySize / maxBodySize)。如果 Content-Length 超出限制,会在读取任何正文之前拒绝请求;如果正文采用分块传输,则会在超过限制时立即截断。无论哪种情况,都会返回 413 Payload Too Large,并附带说明限制值的 JSON-RPC 错误正文,然后关闭连接。
默认限制刻意设置得比较宽松——Base64 图片、文档和较长的粘贴文本经常会让单次工具调用超过 1 MiB。如果客户端确实需要发送更多内容,可以调高限制;也可以设置为 0(CLI)/ false(库)来禁用限制。这样会恢复不受限制的缓冲,因此只有在已有网关限制正文大小的情况下才安全。此限制不适用于 SSE 端点,其 POST /messages 正文由 MCP SDK 而非 mcp-proxy 读取。
startStdioServer
启动一个监听 stdio 的代理,并将消息发送到连接的 sse 或 streamable 服务。
import { ServerType, startStdioServer } from "mcp-proxy";
await startStdioServer({
serverType: ServerType.SSE,
url: "http://127.0.0.1:8080/sse",
});
选项:
serverType:ServerType.SSE或ServerType.HTTPStreamurl:上游 MCP 服务的 URLtransportOptions:传递给客户端传输层的选项(可选)upstreamProtocol:上游使用的协议版本——"legacy"(默认,即 2025 年的initialize握手)、"auto"(使用server/discover探测,失败时回退到握手),或"modern"(要求使用 2026-07-28)。请参阅协议版本。请注意,此函数提供的 stdio 连接仍属于 2025 年版本。initStdioServer/initStreamClient:使用自定义的Server/Client实例(可选)
关闭返回的服务也会关闭 startStdioServer 创建的上游客户端。在正常关闭期间,通过 initStreamClient 提供的客户端仍由调用方负责管理。
tapTransport
接入传输层并记录事件。
import { tapTransport } from "mcp-proxy";
const transport = tapTransport(new StdioClientTransport(), (event) => {
console.log(event);
});
开发
使用本地服务运行 MCP Proxy
tsx src/bin/mcp-proxy.ts --debug -- tsx src/fixtures/simple-stdio-server.ts
评论
一个开放协议,规定了 AI 应用如何以统一方式连接外部工具与数据源。客户端负责与模型对话,MCP 服务负责提供工具、资源和提示模板,两者通过标准消息通信,同一个服务可以被不同客户端复用。