MCP(Model Context Protocol)
问题
什么是 MCP(Model Context Protocol)?它如何标准化 AI 应用的工具调用和上下文管理?前端工程师如何开发和使用 MCP Server?
答案
MCP 是 Anthropic 于 2024 年底推出的开放协议,旨在标准化 LLM 应用(Host)与外部数据源/工具(Server)之间的通信方式。它解决了当前 AI 生态中每个工具都需要定制集成的碎片化问题,类比为 AI 领域的 USB 接口——工具开发者实现一次 MCP Server,就能被所有支持 MCP 的 Host(Claude Desktop、Cursor、VS Code、自定义应用)直接使用。
一、为什么需要 MCP
在 MCP 之前,AI 工具集成面临严重的M×N 问题:
| 维度 | MCP 之前 | MCP 之后 |
|---|---|---|
| 集成量 | M 个应用 × N 个工具 | M + N |
| 工具开发 | 每个平台单独适配 | 实现一次,到处可用 |
| 协议格式 | 各厂商自定义 | 统一 JSON-RPC 2.0 |
| 服务发现 | 手动配置 | 协议内置 capabilities |
二、MCP 核心架构
三个角色
| 角色 | 职责 | 关系 | 示例 |
|---|---|---|---|
| Host | 提供 AI 交互界面的用户端应用 | 包含多个 Client | Claude Desktop、Cursor、自定义应用 |
| Client | 维护与 Server 的 1:1 连接 | 内嵌在 Host 中 | 每个 Client 连一个 Server |
| Server | 暴露工具、资源和提示词的服务 | 独立进程 | GitHub Server、数据库 Server |
一个 Host 可以创建多个 Client,每个 Client 只连接一个 Server(1:1 关系)。这样做是为了安全隔离——每个 Server 只能看到自己被授权的数据,不同 Server 之间无法互相访问。
三、MCP 五大核心能力
MCP 协议定义了 Server 可以向 Client 暴露的五种原语(Primitives):
| 原语 | 控制方 | 说明 | 类比 |
|---|---|---|---|
| Tools | LLM 决定调用 | 可执行的函数(有副作用) | Function Calling |
| Resources | 用户/应用选择 | 结构化数据暴露 | GET 接口 |
| Prompts | 用户选择 | 预定义的提示词模板 | 快捷命令 |
| Sampling | Server 发起 | Server 请求 LLM 生成内容 | 反向调用 LLM |
| Roots | Client 提供 | 告知 Server 可操作的文件系统范围 | 工作目录 |
1. Tools(工具)— 最核心
LLM 可以决定何时调用的函数,类似 Function Calling 但标准化:
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
import { z } from 'zod';
const server = new McpServer({
name: 'frontend-toolkit',
version: '1.0.0',
});
// 注册工具:分析 Bundle 大小
server.tool(
'analyze_bundle',
'分析前端项目的打包产物大小,返回各模块体积',
{
directory: z.string().describe('项目根目录路径'),
format: z.enum(['summary', 'detailed']).default('summary'),
},
async ({ directory, format }) => {
const stats = await analyzeBundleSize(directory);
if (format === 'summary') {
return {
content: [{
type: 'text',
text: `总大小: ${stats.totalSize}\n最大模块: ${stats.largest.name} (${stats.largest.size})`,
}],
};
}
return {
content: [{
type: 'text',
text: JSON.stringify(stats, null, 2),
}],
};
}
);
// 注册工具:创建 GitHub Issue(有副作用)
server.tool(
'create_github_issue',
'在 GitHub 仓库创建 Issue',
{
repo: z.string().describe('仓库名,格式:owner/repo'),
title: z.string().describe('Issue 标题'),
body: z.string().describe('Issue 内容(支持 Markdown)'),
labels: z.array(z.string()).optional().describe('标签列表'),
},
async ({ repo, title, body, labels }) => {
const issue = await githubAPI.createIssue(repo, { title, body, labels });
return {
content: [{
type: 'text',
text: `Issue 创建成功:${issue.html_url}`,
}],
};
}
);
// 注册工具:运行 Lighthouse 审计
server.tool(
'lighthouse_audit',
'对指定 URL 运行 Lighthouse 性能审计',
{
url: z.string().url().describe('要审计的页面 URL'),
categories: z.array(
z.enum(['performance', 'accessibility', 'best-practices', 'seo'])
).default(['performance']),
},
async ({ url, categories }) => {
const report = await runLighthouse(url, categories);
return {
content: [{
type: 'text',
text: `Lighthouse 审计结果:\n${categories.map(
cat => `- ${cat}: ${report.scores[cat]}/100`
).join('\n')}`,
}],
};
}
);
2. Resources(资源)
向 LLM 暴露结构化的数据上下文,由用户/应用决定何时加载:
// 静态资源:项目配置
server.resource(
'project-config',
'config://project',
async (uri) => ({
contents: [{
uri: uri.href,
mimeType: 'application/json',
text: JSON.stringify(await loadProjectConfig()),
}],
})
);
// 动态资源模板:按路径读取文件
server.resourceTemplate(
'file://{path}',
'读取项目中的文件内容',
async (uri, { path }) => ({
contents: [{
uri: uri.href,
mimeType: getMimeType(path),
text: await readFile(path, 'utf-8'),
}],
})
);
// 动态资源模板:数据库表结构
server.resourceTemplate(
'db://tables/{tableName}/schema',
'获取数据库表的字段结构信息',
async (uri, { tableName }) => {
const schema = await getTableSchema(tableName);
return {
contents: [{
uri: uri.href,
mimeType: 'application/json',
text: JSON.stringify(schema, null, 2),
}],
};
}
);
- Resources:只读数据暴露,类似 REST 的 GET。用户选择加载哪些资源作为 LLM 上下文
- Tools:可执行操作,可能有副作用。LLM 决定是否调用
类比:Resources 是"图书馆的书",Tools 是"可以使用的工具"。
3. Prompts(提示词模板)
预定义的可复用 Prompt 模板,用户可以选择使用:
// 代码审查模板
server.prompt(
'code-review',
'代码审查提示词,支持选择审查重点',
{
code: z.string().describe('要审查的代码'),
language: z.string().default('typescript'),
focus: z.enum(['security', 'performance', 'readability', 'all']).default('all'),
},
({ code, language, focus }) => ({
messages: [{
role: 'user',
content: {
type: 'text',
text: `请审查以下 ${language} 代码,重点关注${
focus === 'all' ? '代码质量、安全性、性能和可读性' : focus
}:
\`\`\`${language}
${code}
\`\`\`
请按以下分类给出反馈:
- 🔴 Must Fix:必须修复
- 🟡 Should Fix:建议修复
- 🟢 Nice to Have:优化建议`,
},
}],
})
);
// 组件生成模板
server.prompt(
'generate-component',
'生成 React 组件的提示词模板',
{
name: z.string().describe('组件名(PascalCase)'),
description: z.string().describe('组件功能描述'),
framework: z.enum(['next', 'vite']).default('next'),
},
({ name, description, framework }) => ({
messages: [{
role: 'user',
content: {
type: 'text',
text: `创建一个名为 ${name} 的 React 组件。
功能描述:${description}
技术要求:
- ${framework === 'next' ? 'Next.js App Router,优先 Server Component' : 'Vite + React'}
- TypeScript strict 模式
- Tailwind CSS 样式
- 使用 shadcn/ui 基础组件
- 包含 Props 接口定义
- 处理加载和错误状态`,
},
}],
})
);
4. Sampling(反向 LLM 调用)
Server 可以请求 Client 调用 LLM 生成内容,实现嵌套的 AI 调用:
// 场景:Server 在执行工具时需要 LLM 帮助分析
server.tool(
'smart_refactor',
'智能重构代码(Server 端调用 LLM 分析)',
{
filePath: z.string(),
instruction: z.string(),
},
async ({ filePath, instruction }, { sampling }) => {
const code = await readFile(filePath, 'utf-8');
// Server 反向请求 Client 的 LLM 生成重构方案
const analysis = await sampling.createMessage({
messages: [{
role: 'user',
content: {
type: 'text',
text: `分析以下代码并给出重构建议:\n\n${code}\n\n指令:${instruction}`,
},
}],
maxTokens: 2000,
});
// 基于 LLM 分析结果执行重构
const refactoredCode = applyRefactoring(code, analysis.content);
await writeFile(filePath, refactoredCode);
return {
content: [{
type: 'text',
text: `重构完成。LLM 分析:\n${analysis.content}\n\n已更新文件:${filePath}`,
}],
};
}
);
四、通信机制
MCP 基于 JSON-RPC 2.0 协议,支持三种传输方式:
| 传输方式 | 适用场景 | 特点 | 状态 |
|---|---|---|---|
| stdio | 本地工具 | Host 以子进程启动 Server,通过 stdin/stdout 通信。简单、低延迟 | ✅ 推荐 |
| Streamable HTTP | 远程/本地 | 基于 HTTP POST + SSE,支持无状态和有状态两种模式 | ✅ 推荐(新) |
| SSE | 远程服务 | 旧版远程传输,已被 Streamable HTTP 取代 | ⚠️ 已废弃 |
JSON-RPC 消息格式
// Client → Server:请求列出可用工具
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/list"
}
// Server → Client:返回工具列表
{
"jsonrpc": "2.0",
"id": 1,
"result": {
"tools": [
{
"name": "analyze_bundle",
"description": "分析前端项目的打包产物大小",
"inputSchema": {
"type": "object",
"properties": {
"directory": { "type": "string" }
},
"required": ["directory"]
}
}
]
}
}
// Client → Server:调用工具
{
"jsonrpc": "2.0",
"id": 2,
"method": "tools/call",
"params": {
"name": "analyze_bundle",
"arguments": { "directory": "/path/to/project" }
}
}
传输层实现
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js';
import express from 'express';
const server = new McpServer({ name: 'my-server', version: '1.0.0' });
// --- 方式1:stdio(本地)---
async function startStdio() {
const transport = new StdioServerTransport();
await server.connect(transport);
console.error('Server running on stdio');
}
// --- 方式2:Streamable HTTP(远程)---
async function startHTTP() {
const app = express();
app.use(express.json());
// 无状态模式:每个请求创建新的 transport
app.post('/mcp', async (req, res) => {
const transport = new StreamableHTTPServerTransport({
sessionIdGenerator: undefined, // 无状态
});
await server.connect(transport);
await transport.handleRequest(req, res);
});
// 有状态模式:带 session
const sessions = new Map<string, StreamableHTTPServerTransport>();
app.post('/mcp-stateful', async (req, res) => {
const sessionId = req.headers['mcp-session-id'] as string;
if (sessionId && sessions.has(sessionId)) {
// 复用已有 session
const transport = sessions.get(sessionId)!;
await transport.handleRequest(req, res);
} else {
// 创建新 session
const transport = new StreamableHTTPServerTransport({
sessionIdGenerator: () => crypto.randomUUID(),
});
sessions.set(transport.sessionId!, transport);
await server.connect(transport);
await transport.handleRequest(req, res);
}
});
app.listen(3001, () => console.log('MCP HTTP Server on :3001'));
}
五、完整 MCP Server 实战
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import { z } from 'zod';
import { execSync } from 'child_process';
import { readFile, writeFile, readdir } from 'fs/promises';
import { join } from 'path';
const server = new McpServer({
name: 'frontend-project-toolkit',
version: '1.0.0',
});
// ==================== Tools ====================
// 工具1:分析项目依赖
server.tool(
'analyze_dependencies',
'分析项目的 npm 依赖,检测过时和有安全漏洞的包',
{
projectDir: z.string().describe('项目目录路径'),
checkSecurity: z.boolean().default(true),
},
async ({ projectDir, checkSecurity }) => {
const pkg = JSON.parse(
await readFile(join(projectDir, 'package.json'), 'utf-8')
);
const outdated = execSync('npm outdated --json', {
cwd: projectDir,
encoding: 'utf-8',
}).toString();
let securityReport = '';
if (checkSecurity) {
try {
securityReport = execSync('npm audit --json', {
cwd: projectDir,
encoding: 'utf-8',
}).toString();
} catch (e: unknown) {
securityReport = (e as { stdout: string }).stdout;
}
}
return {
content: [{
type: 'text',
text: `## 依赖分析
总依赖数: ${Object.keys(pkg.dependencies || {}).length}
开发依赖: ${Object.keys(pkg.devDependencies || {}).length}
### 过时的包
${outdated || '全部最新'}
${checkSecurity ? `### 安全审计\n${securityReport}` : ''}`,
}],
};
}
);
// 工具2:生成组件脚手架
server.tool(
'scaffold_component',
'根据模板生成 React 组件文件(含测试和 Story)',
{
name: z.string().describe('组件名称(PascalCase)'),
type: z.enum(['page', 'component', 'hook']).default('component'),
directory: z.string().describe('目标目录'),
withTests: z.boolean().default(true),
withStory: z.boolean().default(false),
},
async ({ name, type, directory, withTests, withStory }) => {
const files: Array<{ path: string; content: string }> = [];
const baseDir = join(directory, name);
if (type === 'component') {
files.push({
path: join(baseDir, `${name}.tsx`),
content: `interface ${name}Props {
// TODO: define props
}
export function ${name}({ ...props }: ${name}Props) {
return <div>TODO: ${name}</div>;
}`,
});
files.push({
path: join(baseDir, 'index.ts'),
content: `export { ${name} } from './${name}';`,
});
}
if (type === 'hook') {
files.push({
path: join(baseDir, `${name}.ts`),
content: `export function ${name}() {
// TODO: implement
}`,
});
}
if (withTests) {
files.push({
path: join(baseDir, `${name}.test.tsx`),
content: `import { render, screen } from '@testing-library/react';
import { ${name} } from './${name}';
describe('${name}', () => {
it('should render', () => {
render(<${name} />);
// TODO: add assertions
});
});`,
});
}
// 写入文件
for (const file of files) {
await writeFile(file.path, file.content, 'utf-8');
}
return {
content: [{
type: 'text',
text: `已创建 ${files.length} 个文件:\n${files.map(f => `- ${f.path}`).join('\n')}`,
}],
};
}
);
// 工具3:执行 Git 操作
server.tool(
'git_status',
'获取 Git 仓库的当前状态(modified/staged/untracked)',
{
directory: z.string().describe('仓库目录'),
},
async ({ directory }) => {
const status = execSync('git status --porcelain', {
cwd: directory,
encoding: 'utf-8',
});
const branch = execSync('git branch --show-current', {
cwd: directory,
encoding: 'utf-8',
}).trim();
const log = execSync('git log --oneline -5', {
cwd: directory,
encoding: 'utf-8',
});
return {
content: [{
type: 'text',
text: `当前分支: ${branch}\n\n状态:\n${status || '(无改动)'}\n\n最近提交:\n${log}`,
}],
};
}
);
// ==================== Resources ====================
server.resource(
'project-structure',
'project://structure',
async () => {
const tree = execSync('find . -type f -name "*.ts" -o -name "*.tsx" | head -50', {
encoding: 'utf-8',
});
return {
contents: [{
uri: 'project://structure',
mimeType: 'text/plain',
text: tree,
}],
};
}
);
// ==================== Prompts ====================
server.prompt(
'review-pr',
'PR 代码审查模板',
{ diff: z.string().describe('Git diff 内容') },
({ diff }) => ({
messages: [{
role: 'user',
content: {
type: 'text',
text: `请审查以下 PR 代码变更:
\`\`\`diff
${diff}
\`\`\`
请按以下分类反馈:
🔴 Must Fix | 🟡 Should Fix | 🟢 Nice to Have`,
},
}],
})
);
// ==================== 启动 ====================
async function main() {
const transport = new StdioServerTransport();
await server.connect(transport);
console.error('Frontend Toolkit MCP Server is running');
}
main().catch(console.error);
六、MCP Server 配置
Claude Desktop
{
"mcpServers": {
"frontend-toolkit": {
"command": "npx",
"args": ["tsx", "/path/to/mcp-server/index.ts"],
"env": {
"GITHUB_TOKEN": "ghp_xxx"
}
},
"postgres": {
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-postgres"],
"env": {
"DATABASE_URL": "postgresql://localhost:5432/mydb"
}
},
"filesystem": {
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-filesystem", "/Users/me/projects"]
}
}
}
Cursor
{
"mcpServers": {
"frontend-toolkit": {
"command": "npx",
"args": ["tsx", "./mcp-server/index.ts"],
"env": {
"NODE_ENV": "development"
}
}
}
}
Claude Code
{
"mcpServers": {
"frontend-toolkit": {
"command": "npx",
"args": ["tsx", "./mcp-server/index.ts"]
}
}
}
七、MCP Client 实现
如果你在开发自定义 AI 应用(而非使用 Claude Desktop),需要实现 MCP Client:
import { Client } from '@modelcontextprotocol/sdk/client/index.js';
import { StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js';
// 创建 MCP Client 连接到 Server
async function createMCPClient(
serverCommand: string,
serverArgs: string[]
): Promise<Client> {
const client = new Client({
name: 'my-ai-app',
version: '1.0.0',
});
const transport = new StdioClientTransport({
command: serverCommand,
args: serverArgs,
});
await client.connect(transport);
return client;
}
// 使用示例:获取工具列表并调用
async function useMCPTools() {
const client = await createMCPClient('npx', [
'tsx', './mcp-server/index.ts',
]);
// 1. 列出可用工具
const { tools } = await client.listTools();
console.log('可用工具:', tools.map(t => t.name));
// 2. 调用工具
const result = await client.callTool({
name: 'analyze_bundle',
arguments: { directory: '/path/to/project' },
});
console.log('结果:', result.content);
// 3. 读取资源
const { contents } = await client.readResource({
uri: 'project://structure',
});
console.log('项目结构:', contents[0].text);
// 4. 列出 Prompts
const { prompts } = await client.listPrompts();
// 5. 使用 Prompt 模板
const prompt = await client.getPrompt({
name: 'code-review',
arguments: { code: 'const x = 1;', focus: 'security' },
});
console.log('Prompt:', prompt.messages);
await client.close();
}
将 MCP Tools 集成到 LLM 调用
import { Client } from '@modelcontextprotocol/sdk/client/index.js';
import { streamText, tool } from 'ai';
import { openai } from '@ai-sdk/openai';
import { z } from 'zod';
// 将 MCP Server 的工具转换为 Vercel AI SDK 的工具格式
async function mcpToolsToAISDK(
client: Client
): Promise<Record<string, ReturnType<typeof tool>>> {
const { tools: mcpTools } = await client.listTools();
const aiTools: Record<string, ReturnType<typeof tool>> = {};
for (const mcpTool of mcpTools) {
aiTools[mcpTool.name] = tool({
description: mcpTool.description || '',
// 从 JSON Schema 转换参数
parameters: z.object(
jsonSchemaToZod(mcpTool.inputSchema)
),
execute: async (args) => {
const result = await client.callTool({
name: mcpTool.name,
arguments: args,
});
// 提取文本内容
return result.content
.filter((c): c is { type: 'text'; text: string } => c.type === 'text')
.map(c => c.text)
.join('\n');
},
});
}
return aiTools;
}
// 在 API Route 中使用
export async function POST(req: Request) {
const { messages } = await req.json();
// 连接 MCP Server 并获取工具
const client = await createMCPClient('npx', ['tsx', './mcp-server/index.ts']);
const tools = await mcpToolsToAISDK(client);
const result = streamText({
model: openai('gpt-4o'),
messages,
tools,
maxSteps: 5, // 允许多轮工具调用
});
return result.toDataStreamResponse();
}
八、MCP 安全模型
| 安全要点 | 说明 | 实践 |
|---|---|---|
| 传输加密 | HTTP 传输必须使用 TLS | 生产环境配置 HTTPS |
| 认证 | Server 应验证 Client 身份 | 使用 OAuth 2.0 或 API Key |
| 工具确认 | 高危工具应要求用户确认 | 写操作前弹出确认对话框 |
| 最小权限 | Server 只暴露必要的工具和资源 | 不要暴露 rm -rf 之类的危险命令 |
| 输入验证 | Server 必须验证工具参数 | 使用 Zod Schema 严格校验 |
| 沙箱隔离 | 不同 Server 之间不能互相访问 | Client-Server 1:1 连接保证隔离 |
九、MCP vs Function Calling vs ChatGPT Plugin
| 维度 | Function Calling | MCP | ChatGPT Plugin(已废弃) |
|---|---|---|---|
| 协议 | 嵌入 LLM API 请求 | 独立 JSON-RPC 协议 | OpenAPI/HTTP |
| 标准化 | 各厂商格式不同 | 统一开放协议 | OpenAI 专有 |
| 跨平台 | 绑定特定 LLM | 任何 Host 都可用 | 仅 ChatGPT |
| 通信 | 同进程 | 独立传输层(stdio/HTTP) | HTTP API |
| 能力 | 只有 Tools | Tools + Resources + Prompts + Sampling | Tools |
| 部署 | 后端代码 | 独立进程/服务 | 独立 Web 服务 |
| 安全 | 应用自行处理 | 协议内置安全模型 | OAuth |
| 生态 | 依赖各平台 | 开放生态,社区驱动 | 已废弃 |
MCP 不是 Function Calling 的替代品,而是更高层的抽象。MCP Server 暴露的 Tools 最终还是通过 Function Calling 让 LLM 调用的。MCP 解决的是工具如何注册、发现和跨平台复用的问题,Function Calling 解决的是LLM 如何决定调用哪个工具的问题。
十、MCP 社区生态
常用的官方和社区 MCP Server:
| Server | 来源 | 功能 |
|---|---|---|
@modelcontextprotocol/server-filesystem | 官方 | 文件系统读写 |
@modelcontextprotocol/server-postgres | 官方 | PostgreSQL 查询 |
@modelcontextprotocol/server-github | 官方 | GitHub API 操作 |
@modelcontextprotocol/server-slack | 官方 | Slack 消息收发 |
@modelcontextprotocol/server-puppeteer | 官方 | 浏览器自动化 |
server-sentry | 社区 | Sentry 错误查看 |
server-notion | 社区 | Notion 文档管理 |
server-linear | 社区 | Linear Issue 管理 |
十一、调试 MCP Server
# 使用 MCP Inspector 可视化调试工具
npx @modelcontextprotocol/inspector npx tsx ./mcp-server/index.ts
# Inspector 会启动 Web UI,可以:
# - 查看 Server 暴露的 Tools/Resources/Prompts
# - 手动调用工具并查看结果
# - 查看 JSON-RPC 通信日志
// 在 Server 中添加日志(注意:stdio 模式下必须用 stderr,stdout 留给协议通信)
const server = new McpServer({
name: 'my-server',
version: '1.0.0',
});
// ✅ 正确:使用 stderr 输出日志
console.error('[MCP] Server starting...');
console.error('[MCP] Tool called:', toolName, args);
// ❌ 错误:不要用 stdout(会干扰 JSON-RPC 通信)
// console.log('...');
常见面试问题
Q1: MCP 解决了什么问题?为什么需要它?
答案:
MCP 解决了 AI 工具集成的M×N 碎片化问题。在 MCP 之前,M 个 AI 应用对接 N 个外部工具需要 M×N 种定制集成代码。MCP 提供统一协议后,只需 M+N 次实现——每个应用实现一次 MCP Client,每个工具实现一次 MCP Server,即可互相连通。
类比 USB 协议:USB 出现之前,每种外设(键盘、鼠标、打印机)都需要专用接口。USB 统一后,任何设备只需实现 USB 协议就能被所有电脑使用。MCP 对 AI 工具做了同样的事情。
Q2: MCP 的 Tools、Resources、Prompts、Sampling 分别是什么?
答案:
| 能力 | 说明 | 控制方 | 类比 |
|---|---|---|---|
| Tools | LLM 可调用的函数,可能有副作用 | LLM 决定调用 | Function Calling |
| Resources | 向 LLM 暴露只读数据 | 用户/应用选择加载 | REST GET |
| Prompts | 预定义的 Prompt 模板 | 用户选择使用 | 快捷命令/Slash Commands |
| Sampling | Server 请求 Client 调用 LLM | Server 发起 | 反向 LLM 调用 |
最常用的是 Tools(占 90% 的使用场景),Resources 适合提供上下文信息,Prompts 适合标准化常用操作。Sampling 是高级特性,用于 Server 需要 AI 辅助分析时。
Q3: stdio 和 Streamable HTTP 传输方式的区别和选择?
答案:
| 维度 | stdio | Streamable HTTP |
|---|---|---|
| 通信方式 | Host 以子进程启动 Server,stdin/stdout | HTTP POST + SSE |
| 部署位置 | 本地 | 本地或远程 |
| 生命周期 | 绑定 Host 进程 | 独立运行 |
| 多客户端 | 1:1 | 支持多客户端 |
| 网络穿透 | 不需要 | 需要网络可达 |
| 认证 | 进程隔离 | 需要显式认证(OAuth/API Key) |
| 适用场景 | 开发工具、本地文件操作 | SaaS 服务、团队共享、生产部署 |
选择原则:本地开发工具首选 stdio(简单可靠),需要远程访问或多人共享时用 Streamable HTTP。
Q4: 如何开发一个自定义 MCP Server?
答案:
开发步骤:
- 安装 SDK:
npm install @modelcontextprotocol/sdk zod - 创建 Server 实例:
new McpServer({ name, version }) - 注册工具:
server.tool(name, description, schema, handler)— 使用 Zod 定义参数 Schema - 注册资源(可选):
server.resource()或server.resourceTemplate() - 选择传输方式:本地用
StdioServerTransport,远程用StreamableHTTPServerTransport - 启动连接:
server.connect(transport) - 在 Host 中配置:在
claude_desktop_config.json或.cursor/mcp.json中注册 - 调试:使用 MCP Inspector(
npx @modelcontextprotocol/inspector)
关键注意:stdio 模式下日志必须输出到 stderr(console.error),因为 stdout 被 JSON-RPC 通信占用。
Q5: MCP 和 Function Calling 是什么关系?
答案:
MCP 和 Function Calling 解决的是不同层次的问题,是互补而非替代关系:
- Function Calling 解决的是:LLM 如何理解工具描述并决定调用哪个工具(模型层面)
- MCP 解决的是:工具如何注册、发现、跨平台复用(协议层面)
工作流程:MCP Server 暴露 Tools → MCP Client 获取工具列表 → 将工具描述转为 LLM 的 Function Calling 格式 → LLM 决定调用 → Client 通过 MCP 协议将调用转发给 Server 执行。
简单说:MCP 是"工具的 USB 接口",Function Calling 是"LLM 的工具使用能力"。
Q6: MCP Server 如何处理安全问题?
答案:
MCP 安全模型基于以下原则:
- 最小权限:Server 只暴露必要的工具和资源,不要给予过大权限
- 用户确认:高危操作(写文件、删除、发消息)应要求人工确认(Host 负责实现确认 UI)
- 输入验证:使用 Zod Schema 严格校验工具参数,防止注入
- 传输加密:HTTP 传输必须使用 TLS
- 认证授权:远程 Server 使用 OAuth 2.0 或 API Key 验证 Client 身份
- 沙箱隔离:每个 Client-Server 连接 1:1,不同 Server 之间无法互相访问
- 日志审计:记录所有工具调用的参数和结果
// 安全实践:验证参数 + 限制操作范围
server.tool('read_file', '读取文件', {
path: z.string()
.refine(p => !p.includes('..'), '不允许路径遍历')
.refine(p => p.startsWith('/allowed/dir'), '只能读取指定目录'),
}, async ({ path }) => {
const content = await readFile(path, 'utf-8');
return { content: [{ type: 'text', text: content }] };
});
Q7: 如何将 MCP Tools 集成到自定义 AI 应用中?
答案:
如果不使用 Claude Desktop 等现成 Host,需要在自定义应用中实现 MCP Client:
- 创建 Client:
new Client({ name, version }) - 连接 Server:通过
StdioClientTransport或 HTTP Transport 连接 - 获取工具列表:
client.listTools()获取 Server 暴露的所有工具 - 格式转换:将 MCP Tool 的 JSON Schema 转换为 LLM SDK 的工具格式(如 Vercel AI SDK 的
tool()或 OpenAI 的tools参数) - 调用转发:LLM 决定调用某个工具时,通过
client.callTool()转发给 MCP Server - 结果回传:将 Server 返回的结果传回给 LLM
核心代码:mcpToolsToAISDK(client) 将 MCP 工具转为 Vercel AI SDK 格式(见上文实现)。
Q8: MCP 对前端开发有什么影响?
答案:
MCP 对前端开发的影响体现在三个层面:
-
开发体验提升:
- 在 Cursor/VS Code 中通过 MCP 连接数据库、文档、API,AI 可以访问完整项目上下文
- 连接 Notion/Linear 后 AI 可以直接读取需求文档编写代码
-
产品开发简化:
- 构建 AI 应用时可以复用社区 MCP Server(GitHub、Slack、数据库),减少定制开发
- 一个工具接入所有 AI 应用,维护成本低
-
新的开发方向:
- 前端工程师可以开发 MCP Server(如 Lighthouse 审计、组件脚手架工具)
- MCP Server 成为一种新的"微服务"形态,前端需要理解其通信机制
Q9: MCP 的 Sampling 能力是什么?什么场景下使用?
答案:
Sampling 是 MCP 中一个独特的能力:Server 可以反向请求 Client 调用 LLM 生成内容。
通常的流程是 LLM → Tool → Server,但 Sampling 允许 Server 在执行工具时反过来调用 LLM。
使用场景:
- 智能重构:Server 执行代码重构时,调用 LLM 分析代码结构
- 内容生成:数据库 Server 在创建报告时,调用 LLM 生成摘要
- 异常分析:监控 Server 检测到错误时,调用 LLM 分析错误原因
注意:Sampling 需要 Host 支持(不是所有 Host 都实现了),且应由 Host 展示用户确认界面,防止 Server 滥用 LLM 调用。
Q10: 如何调试 MCP Server?
答案:
-
MCP Inspector(推荐):
npx @modelcontextprotocol/inspector npx tsx ./server.ts提供 Web UI,可以查看 Tools/Resources 列表、手动调用工具、查看 JSON-RPC 通信日志
-
stderr 日志:stdio 模式下用
console.error输出日志(stdout 被协议占用) -
Claude Desktop 开发者日志:查看
~/Library/Logs/Claude/mcp*.log -
单元测试:直接对 Server 的 handler 函数编写测试,不需要经过 MCP 协议层
-
常见问题排查:
- Server 启动失败 → 检查 command/args 路径是否正确
- 工具不出现 → 检查
tools/list响应是否正确 - stdout 乱码 → 日志误用了
console.log而非console.error
Q11: MCP 未来发展方向是什么?
答案:
-
协议演进:
- Streamable HTTP 取代 SSE 成为远程传输标准
- 支持更多原语(如 Elicitation——Server 向用户提问获取信息)
- 更完善的认证授权标准
-
生态扩展:
- 更多 AI IDE 支持 MCP(VS Code Copilot、Windsurf 等)
- 企业级 MCP Server Registry(工具市场)
- 标准化的 Server 包管理和版本管理
-
前端机会:
- 浏览器内 MCP Client(Web 应用直接连接 MCP Server)
- MCP over WebSocket(低延迟实时通信)
- 可视化 MCP Server 管理面板
相关链接
- MCP 官方文档
- MCP TypeScript SDK
- MCP Server 列表
- MCP Inspector
- Function Calling 与 AI Agent - 工具调用基础
- AI 辅助开发 - MCP 在开发工具中的应用
- AI SDK 与框架 - 将 MCP 工具集成到 AI SDK