Essay
MCP:能力边界、结构、以及一个实践
MCP 与 Skills 是构建 Agent 流程中不可或缺的两类工具:前者侧重于外部上下文信息的接入,后者则专注于经验的封装与沉淀。 那么MCP 的能力边界到底在哪里?除了作为外部接口信息和工具调用的桥梁,它还具备哪些深层价值? 本文记录了我学习 MCP 的过程,希望通过拆解 MCP 的组成部分,来探索它的能力上限,
MCP 与 Skills 是构建 Agent 流程中不可或缺的两类工具:前者侧重于外部上下文信息的接入,后者则专注于经验的封装与沉淀。
那么MCP 的能力边界到底在哪里?除了作为外部接口信息和工具调用的桥梁,它还具备哪些深层价值?
本文记录了我学习 MCP 的过程,希望通过拆解 MCP 的组成部分,来探索它的能力上限,厘清其真正的价值所在。
1. MCP 的能力边界
MCP是一个标准化的通用接口,而不是模型大脑。
MCP 可以做到的:
- 统一通信语言:把杂乱无章的外部 API、本地文件系统、数据库查询,全部翻译成大模型(LLM)能听懂的标准 JSON-格式。
- 暴露操作能力:让大模型知道“我手头有哪些工具可以按,需要传什么参数”。例如,大模型不知道怎么查 GitHub,但 MCP Server 告诉它:“我有一个叫 search_issues 的工具,你需要给我传 repo_name 和 keyword”。
- 控制访问权限:隔离敏感数据。API Key 存在 MCP Server 里,大模型只负责发指令,不直接接触密码。
MCP 不能做的:
- 自主思考与规划: MCP 本身没有任何逻辑推理能力,它就是个听喝的“手脚”。如果大模型不去主动调用它,MCP Server 就静静地躺在那里,什么也不会发生。
- 绕过物理/网络限制: 如果你的 MCP Server 没有权限访问某个内网数据库,它也无法把数据变出来给大模型。它获取信息的深度和广度,完全取决于你编写的 Server 具备什么权限。
2. MCP 的核心结构
MCP 的设计简洁,可以概括为“三层网络架构”以及“三大核心原语”。
2.1. 三层架构模型(Client-Server Architecture)
MCP 采用 C/S 架构,包含三个实体:
- MCP Host:用户直接交互的界面,通常是具备大模型能力的 IDE 或客户端例如 claude code、 cursor、codebuddy 等。不直接与 Server 通信,而通过内部集成的 MCP Client 发起操作。
- MCP Client:Host 内部的通信模块,负责和 Server 建立一对一的独立连接。从 Host 接受指令,并转为协议请求发给 Server。
- MCP Server:开发者唯一需要编写或配置的内容。通常是独立的轻量级程序(常用 node.js或 python)通过标准 I/O 或 HTTP(SSE)与 Client 通信。暴露三大原语(Recourse、Tools、Prompts)。Server 连接本地文件系统、、数据库、第三方 API 等,将结果返回给 Client。

2.2. 三大核心原语
大模型通过 MCP 获取信息,只能通过这三种标准形式:
- 资源 (Resources): 静态、只读的数据。比如一整份 API 文档、一个本地 Log 文件。大模型可以直接“阅读”它们来获取上下文。
- 工具 (Tools): 动态、可执行的函数(带副作用,可以通过函数来进行执行操作)。大模型可以传递参数来“调用”它们。比如:执行一段 SQL 获取查询结果、发送一封邮件、通过 Puppeteer 抓取某个网页的动态内容并返回给大模型。
- 提示词 (Prompts): 预设的指令模板。帮助用户或大模型快速进入特定任务的状态。

3. MCP 的信息如何流转?
MCP 的核心价值就在于它的通信机制。LLM 不能直接访问外部 API,只能通过 MCP 或者工具调用来访问本地文件或者外部远端信息。
MCP 定义了两种官方通道,本地通信和远程通信:
stdio 模式(本地通信):
很直观的含义。当使用 Host 和本地文件系统等通信的时候,走的就是 stdio 通道。
- 进程关系:MCP Client充当父进程,启动应用时拉起 MCP Server 作为子进程。
- 通信管道:不走网络端口,通过操作系统底层的标准输入输出进行对话。
SSE(sever-sent events)模式(远程通信):
通过 SSE 来进行远程的信息通信。
分离的双向通道:SSE 采用单向数据流的 http 机制:
- 下行通道(sever 到 Client):MCP Sever 通过 SSE建立一个持久的单向连接。只要有数据返回或状态更新,sever 就通过这条管道推给 Client
- 上行通道(Client 到 sever):当大模型需要调用工具获取信息的时候,Client 向 Sever 发送一个标准 HTTP POST 请求,发送指令。
MCP 信息如何流转?
通道建好了,里面流淌的数据是什么样子的?MCP 在通道内使用的是 JSON-RPC 2.0 协议。
这里以"在 cursor 中,大模型通过本地 stdio 通道读取 log.txt 文件"为例,还原一次完整的信息流转过程。先约定术语,避免后面混淆:
- Host:驱动会话的应用,例如 Cursor、Claude Desktop。
- MCP Client:Host 内部为每个 Server 维护的一条连接实例(一对一)。
- MCP Server:被 Host 作为子进程拉起的能力提供方,例如 filesystem server。
- stdio 通道:Host 向子进程的 stdin 写入请求,从子进程的 stdout 读取响应;stderr 留给 Server 输出日志。
整体包括握手与能力发现、触发意图、协议转换与下发、本地执行与响应、回传上下文与最终生成五个步骤。
1. 握手与能力发现(Handshake & Discovery)
打开 Cursor 时,Host 拉起 MCP Server 子进程,Client 与 Server 之间走完一组三步握手:
- Client → Server:initialize 请求,声明协议版本与 client capabilities;
- Server → Client:返回 server capabilities(声明它支持 tools / resources / prompts 等);
- Client → Server:notifications/initialized 通知,握手完成。
握手之后,Client 再主动发起 tools/list,把 Server 的工具列表(含 name、description、inputSchema)拉回来。
Host 会把这些工具的 schema 翻译成对应 LLM API 所需的格式(OpenAI function calling、Anthropic tool use 等),随首次请求一起发往云端模型,等于告诉模型:"你现在有 read_file(path) 这把工具可用。"
2. 触发意图(Intent Generation)
用户在聊天框输入:"帮我总结一下本地的 log.txt。"
云端模型推理后判断自己拿不到文件内容,但记得 Host 注册过 read_file 工具,于是返回一条 tool call(OpenAI 叫 tool_calls,Anthropic 叫 tool_use),而不是直接出文字。
注意边界:tool call 是 LLM API 的协议,不是 MCP 的一部分。Host 的核心职责,就是在 LLM 协议和 MCP 协议这两套协议之间做翻译。
3. 协议转换与下发(RPC Request)
Host 拦截到这条 tool call,把它映射成 MCP 标准的 tools/call 请求,通过 Server 子进程的 stdin 写入:
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "read_file",
"arguments": { "path": "/abs/path/to/log.txt" }
}
}
实际工程中建议传绝对路径——相对路径会落到 Server 进程的 CWD 上,不同 Host 启动方式下行为不一致,是常见坑。
4. 本地执行与响应(Execution & RPC Response)
Server 从 stdin 收到这串 JSON,解析 arguments.path,调用本地 OS 的文件系统 API(如 Node.js 的 fs.readFileSync),把文件内容打包成 JSON-RPC 响应,从 stdout 写回:
{
"jsonrpc": "2.0",
"id": 1,
"result": {
"content": [
{ "type": "text", "text": "[Error] 内存溢出..." }
],
"isError": false
}
}
两个细节值得记住:
- content 是数组,元素可以是 text / image / resource 等多种类型,不止文本。
- 业务错误(文件不存在、权限不足)走 result.isError = true;协议错误(方法名拼错、参数不合法)走 JSON-RPC 顶层的 error 字段。
5. 回传上下文与最终生成(Tool Result Injection)
Host 拿到 result 后,按 LLM API 的规范把它封装成一条 tool result 消息(OpenAI 的 role: "tool"、Anthropic 的 tool_result content block),追加到对话历史里,连同完整历史再发一次请求给云端模型。
这一步发生在 Host ↔ LLM 之间,和 MCP 无关。它不是塞进 system prompt,而是作为一条结构化的对话消息。
模型这次看到了 log.txt 的内容,开始真正生成回答:"根据日志显示,你的程序发生了内存溢出……"
可以用下面这个图来展示整个过程:

在 LLM 视角,只能看见一组带 schema 的工具,并不清楚这个工具来自 MCP 还是写死的tools,从模型层面看,tools 和 MCP 没有区别。而从 Host 和生态视角,MCP 是一种获取 tool 的「供给协议」,两个层次需要分开来看。
所以 MCP 解决了什么问题?
生态问题。如果只能用 tool 或者 function calling,每个 Host 想要接入新能力,就只能在自己代码里硬编码。10 个 Host 想要接入同一个工具,就得各自实现 10 次。MCP 把这件事标准化了:能力提供方只用写一次 Server 就能被任何兼容 MCP 的 Host 接入。
同时,MCP 不止承载 tools,还规范了 resources、prompts等。MCP Server 还可以和模型建立双向连接,反向让Host 调用一次模型,让 Server 可以“思考”。

4. 练习一个简单的 MCP-whth AI
本来想着学习阶段,照着网页 AI 写一个,但是现在 时代变了,直接让cursor 和我一起完成我的第一个 MCP 开发。
先让 Gemini 生成一个 MCP 的开发方案,然后让 cursor 对方案进行评价。
我正在学习 MCP 。学习了 MCP 的架构、机制、三大原语等。但是没有实践。
这里有一个 MCP 的开发方案。我想以这个MCP 为例子加强对 MCP 的可操作性和记忆。
本来想手敲,但是一想,感觉 AI 时代没必要,我觉得和你一起搞就行。
和我一起开发这个 MCP。
首先先审核评价一下这个方案
这个方案很适合作为第一个 MCP 实践项目:小、可见、有真实副作用,能把 Resources / Tools / Prompts / stdio transport / 宿主配置 串起来。但如果目的是“形成正确 MCP 操作记忆”,它需要稍微修一下,否则会记住几个不准确的点。
总体评价
我会给它 7/10,适合入门,但不建议原样照做。
优点是路径短:一个本地 MCP Server,暴露一个用户偏好资源、一个改写备忘录工具、一个每日回顾 Prompt,最后挂到 Claude Desktop 里验证。这个设计对初学者很好,因为你能亲眼看到“大模型通过 MCP 调用本地能力”。
主要问题有三个:
-
**Prompt 里写“请读取 ** file:///profile.json”不等于模型一定会触发 Resource 读取。 MCP Resource 是客户端可发现、可读取的上下文原语,但是否自动根据文本 URI 去读,取决于宿主客户端行为。更稳的做法是:要么把 profile 内容直接嵌进 Prompt,要么把“测试 Resource”设计成单独步骤,不把它伪装成自动链路。
-
rewrite_memo 是全量覆写文件,学习效果强,但风险模型不够好。 作为 demo 可以,但它会让模型一次性覆盖整个 memo.md。更适合学习的设计是同时提供:
-
read_memo
- append_memo
- rewrite_memo
这样你能更清楚地区分“读上下文”和“产生副作用”。
- 代码偏底层,适合理解协议,不一定适合记忆现代开发方式。 它用 Server + setRequestHandler 手写 ListTools / CallTool / ListResources / ReadResource,这很适合看 MCP 底层消息流。但真正开发时,官方 SDK 通常也有更高层的封装方式,写起来更少、更类型安全。我们可以先做底层版,再重构成高层版,这样学习效果最好。
我建议的学习路线
不要直接“一步到位做完整 daily review”。我们可以把它拆成 4 个小实验,每一步都能验证一个 MCP 概念:
- 先做最小 MCP Server:只启动 stdio,确认宿主能发现它。
- 加一个 Resource:暴露 profile 和 memo,验证客户端能读。
- 加一个 Tool:先做 append_memo,再做 rewrite_memo。
- 加一个 Prompt:让 Prompt 组织一次“读取偏好、整理 memo、调用工具保存”的工作流。
这样比原方案更适合学习,因为每一步都对应一个 MCP 原语和一次真实验证。
结论
这个方案方向是对的,尤其适合作为“第一个有副作用的 MCP”。但我建议我们不要完全照抄,而是以它为蓝本,做一个更准确的版本:保留 geek-memo-mcp 这个主题,同时把 Resource 的行为讲清楚,把 Tool 的权限和副作用设计得更合理。
下一步我们可以直接开始开发。我建议先在当前 workspace 下创建一个 geek-memo-mcp 项目,做第一版“最小可运行 MCP Server”。
我的反馈:
ok。做出如下优化:我是mac os 电脑。
希望在指定目录下创建这个文件夹,但是我还没想好具体在哪。
同时我的 Host 是 cursor,不是 claude desktop。
其他的听你的。
cursor 哐哐修改了方案,然后实现了第一版 MCP。
# Geek Memo MCP 开发方案
目标:用一个足够小、但能真实运行的 MCP Server,把 MCP 的三大原语 `Resources`、`Tools`、`Prompts` 串起来。Host 使用 Cursor,运行环境是 macOS。
这个项目不追求功能复杂,而是追求可操作、可观察、可复盘:
- `Resources`:让 Host / 模型读取本地用户偏好和备忘录内容。
- `Tools`:让模型通过受控工具修改本地备忘录。
- `Prompts`:提供一个固定工作流,帮助理解 Prompt 如何组织一次 MCP 调用。
- `Transport`:使用最常见的 `stdio` 通道,由 Cursor 启动本地 Node.js 进程。
---
## 阶段 0:先决定项目目录
你还没决定项目放在哪里,所以先保留一个占位变量:
```bash
PROJECT_PARENT="/你选择的父目录"
PROJECT_DIR="$PROJECT_PARENT/geek-memo-mcp"
我建议优先考虑这几个位置:
/Users/townboats/code/MCP-self/geek-memo-mcp
/Users/townboats/code/playground/geek-memo-mcp
/Users/townboats/code/mcp-practice/geek-memo-mcp
如果只是学习和实验,推荐直接放在当前 workspace:
/Users/townboats/code/MCP-self/geek-memo-mcp
理由:当前目录本来就是 MCP 学习项目,后续 Cursor 配置、代码、笔记都在一个地方,复盘成本最低。
阶段 1:创建项目脚手架
在选定目录后执行:
mkdir -p "$PROJECT_PARENT"
cd "$PROJECT_PARENT"
mkdir geek-memo-mcp
cd geek-memo-mcp
npm init -y
npm install @modelcontextprotocol/sdk
然后修改 package.json,开启 ESM,并增加一个启动脚本:
{
"name": "geek-memo-mcp",
"version": "1.0.0",
"type": "module",
"main": "index.js",
"scripts": {
"start": "node index.js"
},
"dependencies": {
"@modelcontextprotocol/sdk": "latest"
}
}
准备两个本地数据文件:
profile.json:
{
"name": "townboats",
"language": "Node.js",
"tone": "清晰、直接、稍微幽默",
"learning_goal": "通过实践理解 MCP 的 Resources、Tools、Prompts 和 stdio 通信"
}
memo.md:
# 我的 MCP 学习备忘录
- 今天开始实践开发一个本地 MCP Server。
阶段 2:实现第一版 MCP Server
第一版先用偏底层的写法:Server + setRequestHandler。
这样虽然代码稍多,但适合学习 MCP 的真实协议流转:
- Host 先调用
resources/list,发现有哪些资源。 - Host 再调用
resources/read,读取指定资源。 - Host 调用
tools/list,发现有哪些工具。 - 模型需要执行动作时,Host 调用
tools/call。 - Host 调用
prompts/list和prompts/get,获取预设工作流。
建议暴露这些能力:
Resources
memo://profile
读取用户偏好,对应本地 profile.json。
memo://memo
读取当前备忘录,对应本地 memo.md。
注意:不要把 Resource 理解成“模型看到 URI 就一定会自动读取”。Resource 是 MCP Server 暴露给 Host 的上下文能力,具体是否自动读取、何时读取,取决于 Host 的产品行为。
Tools
read_memo
显式读取当前备忘录。这个工具和 Resource 有点重复,但适合学习:Resource 偏“上下文暴露”,Tool 偏“模型主动调用的动作”。
append_memo
向备忘录追加一条内容。它比全量覆写更安全,适合第一步验证真实副作用。
rewrite_memo
完全覆写备忘录。这个工具保留,但描述里要明确风险:只有当模型已经生成完整新版本时才调用。
Prompts
daily_review
生成一个每日回顾工作流:读取偏好、读取备忘录、润色内容,最后根据用户确认调用工具保存。
这里建议不要让 Prompt 直接假设模型会自动读取 memo://profile。更稳的方式是让 Prompt 明确告诉模型:“如需了解用户偏好,请读取 profile 资源或调用相关工具。”
阶段 3:挂载到 Cursor
Host 是 Cursor,不是 Claude Desktop。
Cursor 支持通过 MCP 配置启动本地 Server。我们要配置的是一个 stdio MCP Server:Cursor 作为 Host,启动本地 node index.js,然后通过标准输入输出和 MCP Server 通信。
配置方式通常有两类:
- 项目级配置:适合只在当前 workspace 使用。
- 用户级配置:适合在所有项目里都可用。
本项目建议用项目级配置,避免污染全局环境。
在当前 workspace 创建或编辑:
.cursor/mcp.json
示例配置:
{
"mcpServers": {
"geek-memo": {
"command": "node",
"args": [
"/Users/townboats/code/MCP-self/geek-memo-mcp/index.js"
]
}
}
}
如果项目最后放在别的目录,只需要把 args 里的路径替换成真实的 index.js 绝对路径。
关键点:
command是 Cursor 要启动的命令。args是传给命令的参数。- MCP Server 的日志必须写到
stderr,不要写到stdout,因为stdout用于 MCP 协议通信。
阶段 4:分步验证
不要一上来就测完整工作流。按原语逐步验证,记忆会更牢。
测试 1:验证 Server 能启动
目标:Cursor 能发现 geek-memo 这个 MCP Server。
观察点:
- Cursor MCP 配置没有 JSON 语法错误。
node index.js能被 Cursor 正常启动。- Server 日志出现在
stderr,不会污染协议输出。
测试 2:验证 Resources
目标:看到并读取:
memo://profilememo://memo
你要记住的是:
Resource = Server 暴露给 Host 的“可读上下文”。
测试 3:验证 Tools
目标:让模型调用:
read_memoappend_memorewrite_memo
推荐先测 append_memo:
请调用 geek-memo 的 append_memo 工具,向备忘录追加一条:今天学习了 MCP 的 Tool 调用机制。
然后打开 memo.md,确认本地文件真的被修改。
你要记住的是:
Tool = 模型通过 Host 请求 Server 执行的“动作”,可以产生真实副作用。
测试 4:验证 Prompts
目标:执行 daily_review。
观察点:
- Prompt 本身不是工具调用。
- Prompt 是 Server 提供给 Host 的一段可复用对话模板。
- Prompt 可以引导模型读取资源、调用工具,但不是强制执行器。
你要记住的是:
Prompt = 可复用的工作流入口。
阶段 5:学习复盘
完成后建议复盘这几件事:
- Cursor 作为 Host 做了什么?
- MCP Server 做了什么?
stdiotransport 解决了什么问题?- Resource 和 Tool 的边界是什么?
- Prompt 为什么不是“自动化脚本”?
- 哪些行为是 MCP 协议保证的,哪些行为是 Cursor 这个 Host 的产品行为?
这 6 个问题回答清楚,MCP 的操作性记忆基本就建立起来了。
推荐下一步
先确认项目目录。如果没有强偏好,就使用:
/Users/townboats/code/MCP-self/geek-memo-mcp
然后我会和你一起创建项目,先做最小可运行版本,再逐步加 Resources、Tools、Prompts。
但是 cursor 生成的 MCP 没有指引,同时并没有一步步指引,而是一次性生成了所有代码,耗时 3min 左右。如果我自己写,估计得一两小时。但是代价就是我自己的理解并不深刻,如果不自己探索的话会获得「虚假」的掌控感。因此需要自己探索代码,向 AI 发问。
我的反馈:
```plain
如何运行、启动 MCP?我是新手,缺乏一个操作手册。
同时不知道文件目录中各个文件的作用。
MCP 的启动方式:
{
"mcpServers": {
"geek-memo": {
"command": "node",
"args": [
"/Users/townboats/code/MCP-self/geek-memo-mcp/index.js"
]
}
}
}
其中,command 字段为 设置为node,表示这个 MCP 是运行在 node 上的。在Host中进行配置以后,就会自己识别然后启动,识别到MCP Server 的 index.js 暴露出来的 tools 和 resource、prompt。
MCP 的调用流程:
Host(Cursor)配置 MCP → 启动 Node 进程 → index.js 创建 McpServer → 通过 registerResource / registerTool / registerPrompt 注册能力 → McpServer 内部自动整理 capabilities 和 request handlers → 连接 stdio transport → Cursor 发现并调用 Resources、Tools、Prompts
下面是这个练手的 MCP 的详细信息
MCP 名称
geek-memo
这是一个本地备忘录 MCP Server,用来学习 MCP 的三大原语:
- Resources
- Tools
- Prompts
它由 Cursor 作为 Host 启动,通过 stdio 和本地 Node.js 进程通信。
启动入口
/Users/townboats/code/MCP-self/geek-memo-mcp/index.js
Cursor 配置位置
/Users/townboats/code/MCP-self/.cursor/mcp.json
Resources
memo://profile
读取本地用户偏好文件:
geek-memo-mcp/profile.json
用途:让模型了解你的学习目标、偏好语言、表达风格等。
memo://memo
读取当前备忘录文件:
geek-memo-mcp/memo.md
用途:让模型看到当前备忘录内容,作为后续整理、追加、改写的上下文。
Tools
read_memo
读取当前 memo.md 内容。
用途:测试模型如何通过 Tool 主动读取本地数据。
append_memo
向 memo.md 追加一条备忘录。
参数:
entry: string
用途:适合小修改,风险较低,推荐作为新手测试工具。
rewrite_memo
完整覆写 memo.md。
参数:
new_content: string
用途:适合模型已经生成完整新版 Markdown 后保存。
注意:这个工具有真实副作用,风险比 append_memo 高。
Prompts
daily_review
每日回顾工作流。
它会读取:
- profile.json
- memo.md
然后生成一段提示词,让模型根据你的偏好润色备忘录,并在你确认后调用 rewrite_memo 保存。
核心理解
Resources 负责给模型看资料
Tools 负责让模型做动作
Prompts 负责提供可复用工作流