前端转型 Agent 开发 04:MCP 与 Skill,赋予 Agent 更广泛的工作能力
英文标题:前端转型 Agent 开发 04 之 MCP 与 Skill(赋予 Agent 更广工作能力)
本文介绍了 MCP(Model Context Protocol)的 Client/Server 架构,以及 Tools、Resources、Prompts 三类核心能力。文章通过 LangChain 与文件系统 MCP Server 的示例,说明 Agent 如何发现、加载并统一调度远程工具,同时覆盖资源读取、提示模板、认证、错误处理与断线重连。文中强调,MCP 将跨进程、跨网络的工具调用标准化,使 Agent 可以在不大幅修改应用代码的情况下扩展能力。针对 Skill 插件系统,文章进一步说明其作为面向 LLM 的指令文档,通过分类与渐进式披露机制降低上下文负担并提升可管理性。
24.1 KB
6.7 KB
中文
一、MCP 连接
1.1、核心概念
MCP 是一种用于标准化暴露工具(Tools)、资源(Resources)和提示(Prompts)的开放协议,使不同系统能够以统一方式接入。
从上述描述可以看出,MCP 可以理解为一组与特定业务类型相关的 Tools 的集合;其底层本质上调用的仍是 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 架构
┌────────────────┐ ┌────────────────┐
│ 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:
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';
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);
const { tools: mcpTools } = await mcpClient.listTools();
console.log('MCP Server 暴露的工具:', mcpTools.map(t => t.name));
import { loadMcpTools } from '@langchain/mcp-adapters';
const tools = await loadMcpTools(mcpClient);
const agent = createAgent({
llm: new ChatAnthropic({ model: 'claude-sonnet-4-20250514' }),
tools,
prompt: '你可以通过 MCP 访问 /workspace 目录下的文件。',
});
const result = await agent.invoke({
messages: [
{ role: 'user', content: '列出 /workspace 下所有文件,然后读 README.md 给我看' },
],
});
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 响应,并将其作为上下文。
import { Client } from '@modelcontextprotocol/sdk/client/index.js';
const mcpClient = new Client();
const { resources } = await mcpClient.listResources();
console.log(resources.map(r => ({
uri: r.uri,
name: r.name,
mimeType: r.mimeType,
})));
const content = await mcpClient.readResource({
uri: 'file:///docs/api.md',
});
console.log(content.contents[0].text);
Resources vs Tools:Resources 不会改变 Server 状态,只提供只读数据;Tools 则可能改变状态,例如写入文件或发送请求。Agent 使用 Tool 主动执行操作,使用 Resource 拉取上下文。
1.5、MCP Prompts 实际用法
Prompts 是 MCP 协议中的预定义提示模板。Server 维护一组可复用的 Prompt,Client 可以按名称和参数调用,从而避免每次都手动拼接 Prompt:
const { prompts } = await mcpClient.listPrompts();
console.log(prompts.map(p => ({ name: p.name, description: p.description })));
const rendered = await mcpClient.getPrompt('code_review', {
language: 'typescript',
snippet: `function add(a: number, b: number) { return a + b }`,
});
for (const msg of rendered.messages) {
console.log(`[${msg.role}] ${msg.content}`);
}
const result = await model.invoke(rendered.messages);
1.6、MCP 错误处理与重连
MCP 连接可能因网络问题、Server 崩溃或超时而中断。生产环境代码必须实现重连机制与错误分类:
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) {
const retryable =
err.code === 'ECONNREFUSED' ||
err.code === 'ETIMEDOUT' ||
err.code === 'ECONNRESET';
if (!retryable) throw err;
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 参数:
import { Client } from '@modelcontextprotocol/sdk/client/index.js';
const client1 = new Client(
{ name: 'agent-mcp-client', version: '1.0.0' },
{
capabilities: {},
auth: {
type: 'bearer',
token: process.env.MCP_API_TOKEN,
},
},
);
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'],
},
},
);
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 加载到内存;后续每次新会话都从内存对象出发,按需将不同层级的内容注入模型上下文。
┌─────────────────────────────────────────────────────────────────┐
│ ① 加载:SKILL.md (磁盘) → Skill[] 内存对象 │
│ loadSkills() [engine/skills.ts:117] │
│ 合并 builtin(BUILTIN_SKILLS 常量) + userData/skills
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 业务划分方式是使用 SKILL.md frontmatter 中的 tags 字段进行分类,例如 tags: [frontend, react]。Skill 加载器可按 tag 聚合,从而便于在 UI 中分组展示以及按需筛选。
渐进式披露处理
| 层级 | 内容 | 进入上下文的时机 |
|---|---|---|
| 1 | name + description |
始终位于 System Prompt 中 |
| 2 | 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…
English / 原文
一、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 架构
┌────────────────┐ ┌────────────────┐
│ 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:
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';
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);
const { tools: mcpTools } = await mcpClient.listTools();
console.log('MCP Server 暴露的工具:', mcpTools.map(t => t.name));
import { loadMcpTools } from '@langchain/mcp-adapters';
const tools = await loadMcpTools(mcpClient);
const agent = createAgent({
llm: new ChatAnthropic({ model: 'claude-sonnet-4-20250514' }),
tools,
prompt: '你可以通过 MCP 访问 /workspace 目录下的文件。',
});
const result = await agent.invoke({
messages: [
{ role: 'user', content: '列出 /workspace 下所有文件,然后读 README.md 给我看' },
],
});
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 响应)作为上下文。
import { Client } from '@modelcontextprotocol/sdk/client/index.js';
const mcpClient = new Client();
const { resources } = await mcpClient.listResources();
console.log(resources.map(r => ({
uri: r.uri,
name: r.name,
mimeType: r.mimeType,
})));
const content = await mcpClient.readResource({
uri: 'file:///docs/api.md',
});
console.log(content.contents[0].text);
Resources vs Tools :Resources 不会改变 server 状态(只读),Tools 会改变(写文件、发请求)。Agent 用 Tool 主动操作,用 Resource 拉取上下文。
1.5、MCP Prompts 实际用法
Prompts 是 MCP 协议中的预定义提示模板 ——server 维护一组可复用的 prompt,client 按名称和参数调用,避免每次都拼 prompt:
const { prompts } = await mcpClient.listPrompts();
console.log(prompts.map(p => ({ name: p.name, description: p.description })));
const rendered = await mcpClient.getPrompt('code_review', {
language: 'typescript',
snippet: `function add(a: number, b: number) { return a + b }`,
});
for (const msg of rendered.messages) {
console.log(`[${msg.role}] ${msg.content}`);
}
const result = await model.invoke(rendered.messages);
1.6、MCP 错误处理 + 重连
MCP 连接可能因网络、server 崩溃、超时而中断。生产代码必须做重连 + 错误分类 :
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) {
const retryable =
err.code === 'ECONNREFUSED' ||
err.code === 'ETIMEDOUT' ||
err.code === 'ECONNRESET';
if (!retryable) throw err;
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 参数:
import { Client } from '@modelcontextprotocol/sdk/client/index.js';
const client1 = new Client(
{ name: 'agent-mcp-client', version: '1.0.0' },
{
capabilities: {},
auth: {
type: 'bearer',
token: process.env.MCP_API_TOKEN,
},
},
);
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'],
},
},
);
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 加载到内存,后续每次新会话都从内存对象出发,按需把不同层级的内容注入到模型的上下文中。
┌─────────────────────────────────────────────────────────────────┐
│ ① 加载:SKILL.md (磁盘) → Skill[] 内存对象 │
│ loadSkills() [engine/skills.ts:117] │
│ 合并 builtin(BUILTIN_SKILLS 常量) + userData/skills
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…