MCP 解决了什么问题
大语言模型擅长推理,但不擅长“接系统”。过去每接一个数据源,就要写一套专属的 function calling 定义、鉴权逻辑和提示词,N 个模型 × M 个数据源 = N×M 份重复劳动。
MCP(Model Context Protocol) 把这件事标准化:工具提供方暴露一个 MCP Server,模型侧只需实现 MCP Client。协议基于 JSON-RPC 2.0,传输层支持 stdio(本地)与 Streamable HTTP(远程)。
一、协议三件套
MCP Server 可以暴露三类能力:
- Tools:可被模型调用的函数(有副作用);
- Resources:可读取的数据(类似文件,用 URI 标识);
- Prompts:预置的提示词模板。
二、用 TypeScript 实现第一个 MCP Server
npm init -y
npm i @modelcontextprotocol/sdk zod
npm i -D typescript tsx// server.ts
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { z } from "zod";
const server = new McpServer({ name: "ops-tools", version: "1.0.0" });
// Tool 1:查询订单
server.registerTool(
"get_order",
{
title: "查询订单",
description: "根据订单号查询订单状态与金额",
inputSchema: { orderId: z.string().regex(/^SO\d{6}$/, "订单号格式如 SO123456") },
outputSchema: { status: z.string(), amount: z.number() },
},
async ({ orderId }) => {
const row = await db.orders.find(orderId);
if (!row) {
return {
content: [{ type: "text", text: `未找到订单 ${orderId}` }],
isError: true,
};
}
return {
content: [{ type: "text", text: JSON.stringify(row) }],
structuredContent: { status: row.status, amount: row.amount },
};
},
);
// Tool 2:只读的报表查询(带注解,提示客户端这是只读操作)
server.registerTool(
"daily_report",
{
title: "日报",
description: "获取指定日期的销售日报",
inputSchema: { date: z.string().regex(/^\d{4}-\d{2}-\d{2}$/) },
annotations: { readOnlyHint: true, openWorldHint: false },
},
async ({ date }) => {
const r = await db.reports.daily(date);
return { content: [{ type: "text", text: JSON.stringify(r) }] };
},
);
// Resource:暴露文档
server.registerResource(
"runbook",
"docs://runbook/oncall",
{ title: "值班手册", mimeType: "text/markdown" },
async () => ({
contents: [{ uri: "docs://runbook/oncall", text: await fs.readFile("./runbook.md", "utf8") }],
}),
);
const transport = new StdioServerTransport();
await server.connect(transport);inputSchema 用 Zod 声明,SDK 会自动生成 JSON Schema 并在调用前做校验——模型传错参数不会打到你的业务代码。
三、Python 版:FastMCP
# server.py
from mcp.server.fastmcp import FastMCP
from pydantic import Field
mcp = FastMCP("data-tools")
@mcp.tool(description="执行只读 SQL 查询")
def query(sql: str = Field(description="仅支持 SELECT 语句")) -> list[dict]:
if not sql.strip().lower().startswith("select"):
raise ValueError("只允许 SELECT")
return run_readonly(sql)
@mcp.resource("schema://tables")
def tables() -> str:
return json.dumps(list_tables(), ensure_ascii=False)
if __name__ == "__main__":
mcp.run(transport="stdio")四、客户端接入(Host 侧)
// Claude Desktop / Cursor 的配置
{
"mcpServers": {
"ops-tools": {
"command": "node",
"args": ["/abs/path/server.js"],
"env": { "DB_URL": "postgres://..." }
},
"remote": {
"type": "http",
"url": "https://mcp.example.com/mcp",
"headers": { "Authorization": "Bearer xxx" }
}
}
}五、安全设计要点
- 最小权限:给 Server 专用账号,只读工具只给只读库连接。
- 参数白名单:禁止拼接原始 SQL/命令,用参数化查询或枚举约束。
- 审计日志:记录每次 tool 调用的入参与调用方,便于追溯。
- 危险操作二次确认:删除、转账类工具返回 elicitation,要求人工确认。
// 要求用户在客户端侧确认
const ok = await server.elicitInput({
message: `确认删除订单 ${orderId}?`,
requestedSchema: { type: "object", properties: { confirm: { type: "boolean" } } },
});
if (!ok?.confirm) return { content: [{ type: "text", text: "已取消" }] };六、调试
# 官方 Inspector,可视化查看 tools/resources 并直接调用
npx @modelcontextprotocol/inspector node server.js小结
MCP 把“给模型接工具”从定制开发变成了标准集成。写一个 Server 的成本不到一百行代码,却能让你的系统在所有支持 MCP 的客户端里被直接调用——这是当前 AI 工程里投入产出比最高的一块拼图。
评论 0
还没有评论,来说两句吧。