跳到主要内容

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 交互界面的用户端应用包含多个 ClientClaude 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):

原语控制方说明类比
ToolsLLM 决定调用可执行的函数(有副作用)Function Calling
Resources用户/应用选择结构化数据暴露GET 接口
Prompts用户选择预定义的提示词模板快捷命令
SamplingServer 发起Server 请求 LLM 生成内容反向调用 LLM
RootsClient 提供告知 Server 可操作的文件系统范围工作目录

1. Tools(工具)— 最核心

LLM 可以决定何时调用的函数,类似 Function Calling 但标准化

mcp-server/tools.ts
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 暴露结构化的数据上下文,由用户/应用决定何时加载:

mcp-server/resources.ts
// 静态资源:项目配置
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 vs Tools
  • Resources:只读数据暴露,类似 REST 的 GET。用户选择加载哪些资源作为 LLM 上下文
  • Tools:可执行操作,可能有副作用。LLM 决定是否调用

类比:Resources 是"图书馆的书",Tools 是"可以使用的工具"。

3. Prompts(提示词模板)

预定义的可复用 Prompt 模板,用户可以选择使用:

mcp-server/prompts.ts
// 代码审查模板
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 调用

mcp-server/sampling.ts
// 场景: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 消息格式

MCP 通信示例
// 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" }
}
}

传输层实现

mcp-server/transport.ts
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 实战

mcp-server/index.ts
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

~/Library/Application Support/Claude/claude_desktop_config.json
{
"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

.cursor/mcp.json
{
"mcpServers": {
"frontend-toolkit": {
"command": "npx",
"args": ["tsx", "./mcp-server/index.ts"],
"env": {
"NODE_ENV": "development"
}
}
}
}

Claude Code

.claude/settings.json
{
"mcpServers": {
"frontend-toolkit": {
"command": "npx",
"args": ["tsx", "./mcp-server/index.ts"]
}
}
}

七、MCP Client 实现

如果你在开发自定义 AI 应用(而非使用 Claude Desktop),需要实现 MCP Client:

lib/mcp-client.ts
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 调用

lib/mcp-llm-integration.ts
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 CallingMCPChatGPT Plugin(已废弃)
协议嵌入 LLM API 请求独立 JSON-RPC 协议OpenAPI/HTTP
标准化各厂商格式不同统一开放协议OpenAI 专有
跨平台绑定特定 LLM任何 Host 都可用仅 ChatGPT
通信同进程独立传输层(stdio/HTTP)HTTP API
能力只有 ToolsTools + Resources + Prompts + SamplingTools
部署后端代码独立进程/服务独立 Web 服务
安全应用自行处理协议内置安全模型OAuth
生态依赖各平台开放生态,社区驱动已废弃
Function Calling 和 MCP 的关系

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 分别是什么?

答案

能力说明控制方类比
ToolsLLM 可调用的函数,可能有副作用LLM 决定调用Function Calling
Resources向 LLM 暴露只读数据用户/应用选择加载REST GET
Prompts预定义的 Prompt 模板用户选择使用快捷命令/Slash Commands
SamplingServer 请求 Client 调用 LLMServer 发起反向 LLM 调用

最常用的是 Tools(占 90% 的使用场景),Resources 适合提供上下文信息,Prompts 适合标准化常用操作。Sampling 是高级特性,用于 Server 需要 AI 辅助分析时。

Q3: stdio 和 Streamable HTTP 传输方式的区别和选择?

答案

维度stdioStreamable HTTP
通信方式Host 以子进程启动 Server,stdin/stdoutHTTP POST + SSE
部署位置本地本地或远程
生命周期绑定 Host 进程独立运行
多客户端1:1支持多客户端
网络穿透不需要需要网络可达
认证进程隔离需要显式认证(OAuth/API Key)
适用场景开发工具、本地文件操作SaaS 服务、团队共享、生产部署

选择原则:本地开发工具首选 stdio(简单可靠),需要远程访问或多人共享时用 Streamable HTTP。

Q4: 如何开发一个自定义 MCP Server?

答案

开发步骤:

  1. 安装 SDKnpm install @modelcontextprotocol/sdk zod
  2. 创建 Server 实例new McpServer({ name, version })
  3. 注册工具server.tool(name, description, schema, handler) — 使用 Zod 定义参数 Schema
  4. 注册资源(可选):server.resource()server.resourceTemplate()
  5. 选择传输方式:本地用 StdioServerTransport,远程用 StreamableHTTPServerTransport
  6. 启动连接server.connect(transport)
  7. 在 Host 中配置:在 claude_desktop_config.json.cursor/mcp.json 中注册
  8. 调试:使用 MCP Inspector(npx @modelcontextprotocol/inspector

关键注意:stdio 模式下日志必须输出到 stderrconsole.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 安全模型基于以下原则:

  1. 最小权限:Server 只暴露必要的工具和资源,不要给予过大权限
  2. 用户确认:高危操作(写文件、删除、发消息)应要求人工确认(Host 负责实现确认 UI)
  3. 输入验证:使用 Zod Schema 严格校验工具参数,防止注入
  4. 传输加密:HTTP 传输必须使用 TLS
  5. 认证授权:远程 Server 使用 OAuth 2.0 或 API Key 验证 Client 身份
  6. 沙箱隔离:每个 Client-Server 连接 1:1,不同 Server 之间无法互相访问
  7. 日志审计:记录所有工具调用的参数和结果
// 安全实践:验证参数 + 限制操作范围
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:

  1. 创建 Clientnew Client({ name, version })
  2. 连接 Server:通过 StdioClientTransport 或 HTTP Transport 连接
  3. 获取工具列表client.listTools() 获取 Server 暴露的所有工具
  4. 格式转换:将 MCP Tool 的 JSON Schema 转换为 LLM SDK 的工具格式(如 Vercel AI SDK 的 tool() 或 OpenAI 的 tools 参数)
  5. 调用转发:LLM 决定调用某个工具时,通过 client.callTool() 转发给 MCP Server
  6. 结果回传:将 Server 返回的结果传回给 LLM

核心代码:mcpToolsToAISDK(client) 将 MCP 工具转为 Vercel AI SDK 格式(见上文实现)。

Q8: MCP 对前端开发有什么影响?

答案

MCP 对前端开发的影响体现在三个层面:

  1. 开发体验提升

    • 在 Cursor/VS Code 中通过 MCP 连接数据库、文档、API,AI 可以访问完整项目上下文
    • 连接 Notion/Linear 后 AI 可以直接读取需求文档编写代码
  2. 产品开发简化

    • 构建 AI 应用时可以复用社区 MCP Server(GitHub、Slack、数据库),减少定制开发
    • 一个工具接入所有 AI 应用,维护成本低
  3. 新的开发方向

    • 前端工程师可以开发 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?

答案

  1. MCP Inspector(推荐):

    npx @modelcontextprotocol/inspector npx tsx ./server.ts

    提供 Web UI,可以查看 Tools/Resources 列表、手动调用工具、查看 JSON-RPC 通信日志

  2. stderr 日志:stdio 模式下用 console.error 输出日志(stdout 被协议占用)

  3. Claude Desktop 开发者日志:查看 ~/Library/Logs/Claude/mcp*.log

  4. 单元测试:直接对 Server 的 handler 函数编写测试,不需要经过 MCP 协议层

  5. 常见问题排查

    • Server 启动失败 → 检查 command/args 路径是否正确
    • 工具不出现 → 检查 tools/list 响应是否正确
    • stdout 乱码 → 日志误用了 console.log 而非 console.error

Q11: MCP 未来发展方向是什么?

答案

  1. 协议演进

    • Streamable HTTP 取代 SSE 成为远程传输标准
    • 支持更多原语(如 Elicitation——Server 向用户提问获取信息)
    • 更完善的认证授权标准
  2. 生态扩展

    • 更多 AI IDE 支持 MCP(VS Code Copilot、Windsurf 等)
    • 企业级 MCP Server Registry(工具市场)
    • 标准化的 Server 包管理和版本管理
  3. 前端机会

    • 浏览器内 MCP Client(Web 应用直接连接 MCP Server)
    • MCP over WebSocket(低延迟实时通信)
    • 可视化 MCP Server 管理面板

相关链接