
一、MCP 连接
1.1、核心概念
标准化暴露工具(Tools)、资源(Resources)和提示(Prompts)的开放协议,使不同系统能统一接入。
从上面的描述当中,我们其实可以知道 MCP 其实就相当于是一些关于一组业务类型相关的 tools 的集合体,也就是 MCP 底层其实调用的就是 agent tool 这个功能。
1.2、Agent 接入 MCP 的实现核心
三大能力(Tools / Resources / Prompts)
| 能力 | 是 什么 | 例子 |
|---|---|---|
| Tools | 模型可以调用的函数 | read_file(path)、send_email(to, subject) |
| Resources | 模型可以读取的数据 | file:///docs/api.md、postgres://users |
| Prompts | 预定义的提示模板 | summarize_doc(uri)、code_review(snippet) |
关键区别:
- Tools 是"动作"(do something)------有副作用;
- Resources 是"数据"(read something)------只读;
- Prompts 是"模板"(render something)------生成 Prompt 文本。
Client / Server 架构
arduino
┌────────────────┐ ┌────────────────┐
│ MCP Client │ <-----> │ MCP Server │
│ (在你的 Agent │ JSON- │ (独立进程, │
│ 应用中) │ RPC │ 暴露 tools) │
└────────────────┘ └────────────────┘
- Client:你的 Agent 应用中的一个组件,负责连接 Server、发现能力、调用 tool;
- Server:独立进程,可能由你或第三方维护(如官方 GitHub MCP Server);
- 通信:用 JSON-RPC 2.0 协议(和 LLM API 用的格式相同)。
Transport(传输方式)
| Transport | 特点 | 适用 |
|---|---|---|
| stdio | 通过子进程的标准输入/输出通信 | 本地 server(同机部署) |
| HTTP(SSE) | 通过 HTTP + Server-Sent Events 通信 | 远程 server(跨机部署) |
- stdio 适合本地工具(文件系统、本地数据库);
- HTTP 适合远程服务(云上的 GitHub MCP server)。
1.3、完整简单例子
以 LangChain 提供的 Client + 一个模拟的文件系统 MCP Server 为例,演示 Agent 如何通过 MCP 协议读取远端文件。这里用 @langchain/mcp-adapters 把 MCP server 的能力桥接成标准的 LangChain tool:
javascript
import { Client } from '@modelcontextprotocol/sdk/client/index.js';
import { StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js';
import { StreamableHTTPClientTransport } from '@modelcontextprotocol/sdk/client/streamableHttp.js';
import { ChatAnthropic } from '@langchain/anthropic';
import { createAgent } from '@langchain/langgraph';
// ① 创建 MCP Client,连接一个 stdio 类型的 MCP Server
// (假设已有一个暴露 read_file / list_files 能力的 server 进程)
const transport = new StdioClientTransport({
command: 'npx',
args: ['-y', '@modelcontextprotocol/server-filesystem', '/workspace'],
});
const mcpClient = new Client(
{ name: 'agent-mcp-client', version: '1.0.0' },
{ capabilities: {} },
);
await mcpClient.connect(transport);
// ② 发现 server 暴露的所有 tools(这里手动模拟转换过程)
const { tools: mcpTools } = await mcpClient.listTools();
console.log('MCP Server 暴露的工具:', mcpTools.map(t => t.name));
// ["read_file", "list_files", "write_file", ...]
// ③ 把 MCP tools 包装成 LangChain 兼容的 tool 数组
// (实际开发中常用 @langchain/mcp-adapters 的 loadMcpTools 一行搞定)
import { loadMcpTools } from '@langchain/mcp-adapters';
const tools = await loadMcpTools(mcpClient);
// ④ 像普通 tool 一样喂给 Agent
const agent = createAgent({
llm: new ChatAnthropic({ model: 'claude-sonnet-4-20250514' }),
tools, // MCP 来的工具和本地定义的工具可以混用
prompt: '你可以通过 MCP 访问 /workspace 目录下的文件。',
});
const result = await agent.invoke({
messages: [
{ role: 'user', content: '列出 /workspace 下所有文件,然后读 README.md 给我看' },
],
});
// Agent 会自主调用 list_files → read_file,最后总结内容
console.log(result.messages.at(-1).content);
// ⑤ 用完记得关闭连接
await mcpClient.close();
关键洞察 :对 Agent 来说,MCP 工具和本地定义的 tool() 没有本质区别 ------都是"带 description 和 schema 的可调用函数"。MCP 只是提供了一种跨进程/跨网络发现和调用工具的标准协议。所以你可以混搭:本地定义几个核心工具 + 从 N 个 MCP server 加载几十个外部工具,Agent 统一调度。这正是 MCP 协议的价值------让 Agent 的能力可以无限扩展,而代码不用改。
1.4、MCP Resources 实际用法
Resources 是 MCP 协议中只读数据的能力(区别于有副作用的 Tools)。Agent 可以读取远程资源(文件、数据库行、API 响应)作为上下文。
javascript
import { Client } from '@modelcontextprotocol/sdk/client/index.js';
const mcpClient = new Client(/* ... */);
// ① 列出 server 暴露的所有 resources
const { resources } = await mcpClient.listResources();
console.log(resources.map(r => ({
uri: r.uri, // 如 "file:///docs/api.md"
name: r.name, // 如 "API 文档"
mimeType: r.mimeType,
})));
// [
// { uri: 'file:///docs/api.md', name: 'API 文档', mimeType: 'text/markdown' },
// { uri: 'postgres://users', name: '用户表', mimeType: 'application/json' },
// ]
// ② 读取指定 resource
const content = await mcpClient.readResource({
uri: 'file:///docs/api.md',
});
console.log(content.contents[0].text);
// "# API 文档\n\n## 接口列表\n..."
Resources vs Tools:Resources 不会改变 server 状态(只读),Tools 会改变(写文件、发请求)。Agent 用 Tool 主动操作,用 Resource 拉取上下文。
1.5、MCP Prompts 实际用法
Prompts 是 MCP 协议中的预定义提示模板------server 维护一组可复用的 prompt,client 按名称和参数调用,避免每次都拼 prompt:
javascript
// ① 列出所有可用 prompt 模板
const { prompts } = await mcpClient.listPrompts();
console.log(prompts.map(p => ({ name: p.name, description: p.description })));
// [
// { name: 'code_review', description: '代码审查模板' },
// { name: 'summarize_doc', description: '文档总结模板' },
// ]
// ② 用参数渲染 prompt
const rendered = await mcpClient.getPrompt('code_review', {
language: 'typescript',
snippet: `function add(a: number, b: number) { return a + b }`,
});
// rendered.messages 是可以直接发给模型的消息列表
for (const msg of rendered.messages) {
console.log(`[${msg.role}] ${msg.content}`);
}
// [user] 请审查以下 TypeScript 代码:
//
// function add(a: number, b: number) { return a + b }
//
// 关注点:类型安全、边界情况、性能。
// [user] 输出格式:优点 → 问题 → 改进建议
// ③ 直接喂给 Agent
const result = await model.invoke(rendered.messages);
1.6、MCP 错误处理 + 重连
MCP 连接可能因网络、server 崩溃、超时而中断。生产代码必须做重连 + 错误分类:
typescript
async function connectWithRetry(url: string, maxAttempts = 5) {
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
try {
const transport = new StreamableHTTPClientTransport(new URL(url));
const client = new Client(/* ... */);
await client.connect(transport);
console.log(`✅ MCP 连接成功(attempt ${attempt})`);
return client;
} catch (err: any) {
// 分类错误:可重试 vs 不可重试
const retryable =
err.code === 'ECONNREFUSED' || // 连接被拒(server 没起)
err.code === 'ETIMEDOUT' || // 超时
err.code === 'ECONNRESET'; // 连接被重置
if (!retryable) throw err; // 不可重试错误(认证失败、协议错误)
// 指数退避:1s, 2s, 4s, 8s, 16s
const delay = Math.min(1000 * Math.pow(2, attempt - 1), 16_000);
console.warn(`MCP 连接失败(attempt ${attempt}),${delay}ms 后重试:`, err.message);
await new Promise(r => setTimeout(r, delay));
}
}
throw new Error(`MCP 连接失败(${maxAttempts} 次重试后仍不成功)`);
}
// 运行时断线监测 + 自动重连
client.on('disconnect', async () => {
console.warn('MCP 连接断开,尝试重连...');
await connectWithRetry(serverUrl);
});
MCP 错误码速查:
| 错误码 | 含义 | 处理 |
|---|---|---|
-32700 |
JSON 解析错误 | 重试(可能是传输损坏) |
-32600 |
无效请求 | 不要重试(请求本身错) |
-32601 |
方法不存在 | 不要重试(client 调用了 server 不支持的方法) |
-32602 |
参数无效 | 不要重试(参数错) |
-32603 |
内部错误 | 重试(server bug) |
-32000 ~ -32099 |
服务端自定义错误 | 看具体含义 |
1.7、MCP Server 认证
远程 MCP server 通常需要认证。协议支持在连接时传 auth 参数:
arduino
import { Client } from '@modelcontextprotocol/sdk/client/index.js';
// ① Bearer Token 认证
const client1 = new Client(
{ name: 'agent-mcp-client', version: '1.0.0' },
{
capabilities: {},
auth: {
type: 'bearer',
token: process.env.MCP_API_TOKEN,
},
},
);
// ② OAuth 2.0 认证
const client2 = new Client(
{ name: 'agent-mcp-client', version: '1.0.0' },
{
capabilities: {},
auth: {
type: 'oauth2',
clientId: process.env.MCP_CLIENT_ID,
clientSecret: process.env.MCP_CLIENT_SECRET,
tokenEndpoint: 'https://auth.example.com/oauth/token',
scopes: ['read', 'write'],
},
},
);
// ③ 自定义 Header 认证(如企业内部 SSO)
const client3 = new Client(
{ name: 'agent-mcp-client', version: '1.0.0' },
{
capabilities: {},
headers: {
'X-API-Key': process.env.MCP_API_KEY,
'X-Tenant': 'tenant-123',
},
},
);
Token 安全:永远不要把 token 硬编码或写进 git。生产环境用环境变量 / 密钥管理服务(AWS Secrets Manager / HashiCorp Vault)。一种常见的封装是从一个凭证抽象层(Credential Layer)读取 token,再注入到 MCP client,避免 token 散落在各处。
二、Skill 插件系统
2.1、核心概念
Skill 不是"执行体",而是"给 LLM 的指令文档"
也就是说 Skill 并不是 LLM 直接执行的代码逻辑,而是一段让 LLM 知道要做什么,怎么做的一些提示词描述。因此 Skill 不会作为一个带执行逻辑的工具直接发给 LLM,而是通过两条通道分阶段披露给模型。
基于上面的一个概念,我们能够把 Skill 拆解为三件事:磁盘加载 → 内存对象 → 分阶段注入 。一次 loadSkills() 调用把磁盘上的所有 Skill 加载到内存,后续每次新会话都从内存对象出发,按需把不同层级的内容注入到模型的上下文中。
scss
┌─────────────────────────────────────────────────────────────────┐
│ ① 加载:SKILL.md (磁盘) → Skill[] 内存对象 │
│ loadSkills() [engine/skills.ts:117] │
│ 合并 builtin(BUILTIN_SKILLS 常量) + userData/skills/* │
│ ├─ *.md → 单文件 Skill │
│ └─ 目录(SKILL.md) → 目录式 Skill (+references/ scripts/) │
│ 解析 frontmatter → splitFrontmatter + skillFromMeta │
└───────────────────────────┬─────────────────────────────────────┘
│ Skill[]
┌─────────────┴──────────────┐
▼ ▼
② 通道A: 注入系统提示 ② 通道B: 注册为工具
(渐进披露 · 第1层) (渐进披露 · 第2/3层入口)
2.2、Agent 对 Skill 系统的优化
业务类型的划分
根据不同的业务领域和使用频率,把 Skill 划分成不同类别,有助于模型快速定位该用哪个 Skill,也便于你管理。常见的划分维度:
| 划分维度 | 例子 | 为什么这样分 |
|---|---|---|
| 按领域 | frontend / backend / devops / docs |
不同领域知识不互相污染,模型按当前任务领域激活对应 Skill |
| 按频率 | core(高频,始终可用) / optional(低频,按需加载) |
高频 Skill 常驻 system prompt,低频 Skill 走渐进披露第 2/3 层 |
| 按风险 | readonly(只读,安全) / mutating(会改文件/发请求,需审批) |
风险高的 Skill 可以挂审批 Hook,风险低的直接执行 |
常见的 Skill 业务划分做法:用 SKILL.md 的 frontmatter tags 字段做分类(如 tags: [frontend, react]),Skill 加载器按 tag 聚合,便于在 UI 上分组展示和按需筛选。
渐进式披露处理
| 层 | 内容 | 进入上下文时机 |
|---|---|---|
| 1 | name + description |
始终在 system prompt |
| 2 | [SKILL.md](https://SKILL.md) 的全文 body |
模型调 Skill 工具时 |
| 3 | references/ scripts |
模型调 Skill_Resource 时 |
第 3 层「绝不预加载」由 <font style="background-color:rgba(255, 255, 255, 0);">SKILL_TOOL</font> 的 description 强约束(<font style="background-color:rgba(255, 255, 255, 0);">skill-tool.ts:36-38</font>):正文没提的资源,模型不会主动加载,清单只是"备选目录"。
参考资料:
Agent 开发系列文章
前端转型 Agent 开发 01 之 Agent API 调用(和 Agent 的基础对话):juejin.cn/post/767744...
前端转型 Agent 开发 02 之 Provider 与 Structured Output(规范化模型输入输出):juejin.cn/post/767745...
前端转型 Agent 开发 03 之 Agent Tools(给 Agent 装上手脚)介绍 Tool Calling:juejin.cn/post/768007...