乐此乐谱
109.49M · 2026-04-02
你有没有遇到过这种情况:
你想让 AI 帮你查 GitHub 上的 issue,它说:“我没有访问 GitHub 的权限。”
你想让 AI 帮你操作数据库,它说:“我无法连接到您的数据库。”
你想让 AI 读取本地文件,它说:“我没有文件系统访问权限。”
每次都要自己写一堆胶水代码,把 AI 和各种工具连起来。写完这个项目,下个项目又得重新写一遍。
这就是 MCP 要解决的问题。
MCP 全称是 Model Context Protocol(模型上下文协议),是 Anthropic 在 2024 年底推出的一个开放标准。
一句话解释:
在 MCP 出现之前,每个 AI 应用想接入外部工具,都要自己造轮子,写各种适配代码。就像早年电脑接外设,每个厂商接口都不一样,乱得一塌糊涂。
MCP 就是那个统一的 USB 标准——工具只需要实现一次,所有支持 MCP 的 AI 应用都能直接用。
理解 MCP,先搞清楚三个角色:
┌─────────────────────────────────────────┐
│ MCP Host(宿主) │
│ Claude Desktop / Cursor / 你的应用 │
└──────────────────┬──────────────────────┘
│ MCP 协议
┌──────────┴──────────┐
↓ ↓
┌──────────────┐ ┌──────────────────┐
│ MCP Server │ │ MCP Server │
│ (文件系统) │ │ (数据库) │
└──────────────┘ └──────────────────┘
你只需要写一个 MCP Server,所有支持 MCP 的 Host 都能直接用你的工具。
MCP Server 可以暴露三种东西:
AI 可以调用的函数,类似 Function Calling。
查询数据库、发送邮件、操作文件、调用 API...
AI 可以读取的数据,类似文件系统。
本地文件、数据库记录、API 返回的数据...
预定义的提示词,可以复用。
代码审查模板、文档生成模板、分析报告模板...
用 @modelcontextprotocol/sdk 写一个最简单的 MCP Server:
npm install @modelcontextprotocol/sdk
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import { z } from 'zod';
// 创建 MCP Server
const server = new McpServer({
name: 'weather-server',
version: '1.0.0',
});
// 注册一个 Tool:查询天气
server.tool(
'get_weather',
'获取指定城市的实时天气信息',
{
city: z.string().describe('城市名称,例如:北京、上海、广州'),
},
async ({ city }) => {
// 实际项目里这里调用真实天气 API
const weatherData = {
北京: { temperature: 22, condition: '晴', humidity: 40 },
上海: { temperature: 25, condition: '多云', humidity: 65 },
广州: { temperature: 30, condition: '阵雨', humidity: 80 },
};
const weather = weatherData[city];
if (!weather) {
return {
content: [{ type: 'text', text: `未找到城市:${city}` }],
};
}
return {
content: [
{
type: 'text',
text: `${city}天气:${weather.condition},气温 ${weather.temperature}°C,湿度 ${weather.humidity}%`,
},
],
};
}
);
// 注册一个 Resource:读取本地文件
server.resource(
'local-file',
'file://{path}',
async (uri) => {
const path = uri.pathname;
const fs = await import('fs/promises');
try {
const content = await fs.readFile(path, 'utf-8');
return {
contents: [{ uri: uri.href, mimeType: 'text/plain', text: content }],
};
} catch (error) {
throw new Error(`读取文件失败:${error.message}`);
}
}
);
// 启动 Server(通过标准输入输出通信)
const transport = new StdioServerTransport();
await server.connect(transport);
console.error('Weather MCP Server 已启动');
就这么几十行,一个 MCP Server 就写好了。
在 Claude Desktop 的配置文件里加上你的 Server:
// ~/Library/Application Support/Claude/claude_desktop_config.json
{
"mcpServers": {
"weather": {
"command": "node",
"args": ["/path/to/your/weather-server.js"]
}
}
}
重启 Claude Desktop,它就能直接调用你的天气查询工具了。
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import { z } from 'zod';
import Database from 'better-sqlite3';
const server = new McpServer({
name: 'sqlite-server',
version: '1.0.0',
});
const db = new Database('./mydata.db');
// Tool:执行 SQL 查询
server.tool(
'query_database',
'执行 SQL 查询并返回结果,只支持 SELECT 语句',
{
sql: z.string().describe('要执行的 SQL 查询语句,只允许 SELECT'),
},
async ({ sql }) => {
// 安全检查:只允许 SELECT
if (!sql.trim().toUpperCase().startsWith('SELECT')) {
return {
content: [{ type: 'text', text: '错误:只允许执行 SELECT 查询' }],
isError: true,
};
}
try {
const rows = db.prepare(sql).all();
return {
content: [
{
type: 'text',
text: JSON.stringify(rows, null, 2),
},
],
};
} catch (error) {
return {
content: [{ type: 'text', text: `查询失败:${error.message}` }],
isError: true,
};
}
}
);
// Tool:获取表结构
server.tool(
'get_table_schema',
'获取数据库中指定表的结构信息',
{
table_name: z.string().describe('表名'),
},
async ({ table_name }) => {
try {
const schema = db
.prepare(`PRAGMA table_info(${table_name})`)
.all();
return {
content: [
{
type: 'text',
text: JSON.stringify(schema, null, 2),
},
],
};
} catch (error) {
return {
content: [{ type: 'text', text: `获取表结构失败:${error.message}` }],
isError: true,
};
}
}
);
// Resource:暴露数据库表列表
server.resource(
'database-tables',
'db://tables',
async () => {
const tables = db
.prepare(`SELECT name FROM sqlite_master WHERE type='table'`)
.all();
return {
contents: [
{
uri: 'db://tables',
mimeType: 'application/json',
text: JSON.stringify(tables, null, 2),
},
],
};
}
);
const transport = new StdioServerTransport();
await server.connect(transport);
配置好之后,你就可以直接对 Claude 说:“帮我查一下 users 表里上个月注册的用户有多少”,它会自己去查数据库,给你真实的结果。
MCP Server 和 Host 之间有两种通信方式:
适合本地工具,Server 作为子进程运行:
const transport = new StdioServerTransport();
await server.connect(transport);
适合远程服务,Server 作为独立的 HTTP 服务运行:
import { SSEServerTransport } from '@modelcontextprotocol/sdk/server/sse.js';
import express from 'express';
const app = express();
const transport = new SSEServerTransport('/messages', res);
await server.connect(transport);
app.listen(3000);
很多人会问:MCP 和 Function Calling 有什么区别?
| 对比项 | MCP | Function Calling |
|---|---|---|
| 定位 | 标准协议,跨应用复用 | 单次对话内的工具调用 |
| 复用性 | 写一次,所有 Host 都能用 | 每个应用自己实现 |
| 适合场景 | 工具生态、持久化服务 | 单个应用内的功能扩展 |
| 部署方式 | 独立进程 / 远程服务 | 内嵌在应用代码里 |
| 标准化 | 有统一协议规范 | 各家 API 格式不同 |
简单来说:
两者不是替代关系,很多 MCP Server 内部就是用 Function Calling 的思路实现的。
不想自己写?社区已经有大量现成的 MCP Server:
| Server | 功能 | 地址 |
|---|---|---|
@modelcontextprotocol/server-filesystem | 本地文件读写 | 官方 |
@modelcontextprotocol/server-github | GitHub 操作 | 官方 |
@modelcontextprotocol/server-postgres | PostgreSQL 查询 | 官方 |
@modelcontextprotocol/server-brave-search | Brave 搜索 | 官方 |
@modelcontextprotocol/server-slack | Slack 消息 | 官方 |
mcp-server-sqlite | SQLite 查询 | 社区 |
安装官方文件系统 Server:
npx @modelcontextprotocol/server-filesystem /your/directory
加到 Claude Desktop 配置:
{
"mcpServers": {
"filesystem": {
"command": "npx",
"args": [
"@modelcontextprotocol/server-filesystem",
"/Users/yourname/Documents"
]
}
}
}
配置完重启,Claude 就能直接读写你的文档目录了。
MCP Tool 的返回里有个 isError 字段,出错时要设置为 true,这样 AI 才知道调用失败了,会尝试其他方式。
// 错误做法:直接 throw
throw new Error('查询失败');
// 正确做法:返回错误信息
return {
content: [{ type: 'text', text: `查询失败:${error.message}` }],
isError: true,
};
和 Function Calling 一样,Tool 的描述越详细,AI 越知道什么时候该用它。
MCP Server 有访问本地资源的能力,一定要做好权限控制,别让 AI 随便删文件、执行危险 SQL。
// 数据库操作只允许 SELECT
if (!sql.trim().toUpperCase().startsWith('SELECT')) {
return { content: [{ type: 'text', text: '只允许 SELECT 查询' }], isError: true };
}
// 文件操作限制在指定目录
if (!filePath.startsWith(ALLOWED_DIR)) {
return { content: [{ type: 'text', text: '不允许访问该目录' }], isError: true };
}
Stdio 模式下,标准输出是用来和 Host 通信的,你的日志要用 console.error 输出到标准错误,否则会破坏通信协议。
// 会破坏通信
console.log('Server 启动了');
// 正确做法
console.error('Server 启动了');
不只是 Claude Desktop,你自己写的 AI 应用也可以作为 MCP Host:
import { Client } from '@modelcontextprotocol/sdk/client/index.js';
import { StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js';
// 连接到 MCP Server
const transport = new StdioClientTransport({
command: 'node',
args: ['./weather-server.js'],
});
const client = new Client({ name: 'my-app', version: '1.0.0' }, {});
await client.connect(transport);
// 列出所有可用工具
const { tools } = await client.listTools();
console.log('可用工具:', tools.map(t => t.name));
// 调用工具
const result = await client.callTool({
name: 'get_weather',
arguments: { city: '北京' },
});
console.log('结果:', result.content[0].text);
把这些工具列表传给 OpenAI 或其他大模型,就实现了完整的 MCP Host。
MCP 的核心思想就一句话:
它解决的是 AI 生态里的"碎片化"问题,让工具开发者和 AI 应用开发者都能专注自己的事。
对于开发者来说,现在是入场 MCP 生态的好时机:
推荐学习路径:
跑通了,你就真正理解 MCP 了。