本文基于实战项目,手把手教你用 Model Context Protocol (MCP) 构建一个天气查询服务,让大模型能够调用你的工具。
什么是 MCP?
MCP (Model Context Protocol) 是一个开放协议,用于标准化 AI 应用与外部工具的连接方式。
简单理解:MCP 就是 AI 调用工具的"USB 接口"。
┌─────────────┐ MCP 协议 ┌─────────────┐
│ 大模型 │ ←───────────────→ │ 你的工具 │
│ (GPT/GLM) │ │ (天气/搜索) │
└─────────────┘ └─────────────┘
MCP 的三大能力
| 能力 | 说明 | 示例 |
|---|---|---|
| Resources | 提供数据资源 | 天气数据、文档内容 |
| Tools | 提供可调用的工具 | 查询天气、发送邮件 |
| Prompts | 提供提示模板 | 分析报告模板 |
项目结构
MCP_LLM/
├── WeatherMCPServer/
│ ├── index.js # MCP 服务核心(定义能力)
│ └── mock.js # 模拟天气数据
├── MCPServer/
│ └── http.js # HTTP 传输层(暴露接口)
├── MCPClient/
│ └── http_client.js # 客户端(连接服务)
└── chat.js # 与大模型集成
第一步:创建 MCP 服务
1.1 定义服务能力
// WeatherMCPServer/index.js
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
import {
ListToolsRequestSchema,
CallToolRequestSchema,
} from "@modelcontextprotocol/sdk/types.js";
class WeatherServer {
constructor() {
// 创建 MCP 服务器,声明支持的能力
this.server = new Server(
{ name: "mcp-weather-server", version: "0.0.1" },
{ capabilities: { tools: {} } }, // 声明支持 tools 能力
);
this.setupToolHandlers();
}
setupToolHandlers() {
// 1. 告诉客户端"我有哪些工具"
this.server.setRequestHandler(ListToolsRequestSchema, async () => ({
tools: [
{
name: "query_weather",
description: "查询特定地点的天气信息",
inputSchema: {
type: "object",
properties: {
location: {
type: "string",
description: "地点(如:beijing, shanghai)",
},
},
required: ["location"],
},
},
],
}));
// 2. 处理工具调用
this.server.setRequestHandler(CallToolRequestSchema, async (request) => {
if (request.params.name === "query_weather") {
const location = request.params.arguments?.location;
// 这里可以调用真实的天气 API
return {
content: [
{
type: "text",
text: `${location}:晴,25°C,湿度 45%`,
},
],
};
}
throw new Error("未知工具");
});
}
getServer() {
return this.server;
}
}
export default WeatherServer;
关键点:
Server构造时声明capabilities,表示支持哪些能力setRequestHandler注册处理器,响应客户端请求ListToolsRequestSchema→ 返回工具列表CallToolRequestSchema→ 执行工具调用
1.2 暴露 HTTP 接口
// MCPServer/http.js
import express from "express";
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
import WeatherServer from "../WeatherMCPServer/index.js";
const app = express();
app.use(express.json());
const weatherServer = new WeatherServer();
// 核心:POST /mcp 处理所有 MCP 请求
app.post("/mcp", async (req, res) => {
try {
const server = weatherServer.getServer();
// 每个请求创建独立的传输实例(无状态模式)
const transport = new StreamableHTTPServerTransport({
sessionIdGenerator: undefined, // 不维护会话
});
// 请求结束时清理资源
res.on("close", () => {
transport.close();
server.close();
});
// 连接 MCP 服务器与传输层
await server.connect(transport);
// 处理请求并返回响应
await transport.handleRequest(req, res, req.body);
} catch (error) {
res.status(500).json({
jsonrpc: "2.0",
error: { code: -32603, message: "服务器错误" },
id: null,
});
}
});
app.listen(3200, () => {
console.log("MCP HTTP 服务已启动: http://localhost:3200/mcp");
});
为什么每次请求都创建新的 transport?
- 隔离性:避免并发请求的 ID 冲突
- 无状态:适合负载均衡和水平扩展
- 资源清理:请求结束自动释放
第二步:创建客户端
// MCPClient/http_client.js
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
// 创建客户端
const client = new Client({
name: "weather-http-client",
version: "1.0.0",
});
// 连接到 MCP 服务
const transport = new StreamableHTTPClientTransport(
new URL("http://localhost:3200/mcp"),
);
await client.connect(transport);
// 现在可以调用 MCP 能力了!
const tools = await client.listTools();
console.log("可用工具:", tools);
const result = await client.callTool({
name: "query_weather",
arguments: { location: "beijing" },
});
console.log("查询结果:", result);
export default client;
第三步:与大模型集成
// chat.js
import ZhipuAI from "zhipu-sdk-js";
async function runConversation(mcpClient) {
const userMessage = "北京今天天气怎么样?";
// 1. 从 MCP 获取可用工具
const mcpTools = await mcpClient.listTools();
const tools = mcpTools.tools.map((tool) => ({
type: "function",
function: {
name: tool.name,
description: tool.description,
parameters: tool.inputSchema,
},
}));
// 2. 发送给大模型,让它决定是否调用工具
const response = await zhipuClient.createCompletions({
model: "glm-4.5-flash",
messages: [{ role: "user", content: userMessage }],
tools: tools,
});
const message = response.choices[0].message;
// 3. 如果大模型决定调用工具
if (message.tool_calls?.length > 0) {
const toolCall = message.tool_calls[0];
// 4. 执行 MCP 工具调用
const toolResult = await mcpClient.callTool({
name: toolCall.function.name,
arguments: JSON.parse(toolCall.function.arguments),
});
// 5. 把结果返回给大模型生成最终回答
const finalResponse = await zhipuClient.createCompletions({
model: "glm-4.5-flash",
messages: [
{ role: "user", content: userMessage },
message,
{
role: "tool",
tool_call_id: toolCall.id,
content: toolResult.content[0].text,
},
],
});
console.log(finalResponse.choices[0].message.content);
// 输出:北京今天天气晴朗,气温25°C,湿度45%,非常适合出行。
}
}
完整流程图
用户: "北京天气怎么样?"
│
▼
┌───────────────────────────────────────────────────────────┐
│ chat.js │
│ 1. client.listTools() ──→ 获取 MCP 工具列表 │
│ 2. 发送给大模型 ──→ 大模型决定调用 query_weather │
│ 3. client.callTool({name, arguments}) ──→ 执行工具 │
│ 4. 把结果发回大模型 ──→ 生成自然语言回答 │
└───────────────────────────────────────────────────────────┘
│ ▲
│ HTTP POST │ 工具结果
▼ │
┌───────────────────────────────────────────────────────────┐
│ MCPServer/http.js │
│ POST /mcp ──→ StreamableHTTPServerTransport │
│ │ │
│ ▼ │
│ WeatherMCPServer/index.js │
│ - ListToolsRequestSchema │
│ - CallToolRequestSchema │
└───────────────────────────────────────────────────────────┘
运行项目
1. 安装依赖
pnpm install
2. 配置环境变量
# .env
ZHIPUAI_API_KEY=your_api_key_here
3. 启动服务
# 终端 1:启动 MCP HTTP 服务
node MCPServer/http.js
# 终端 2:运行对话
node main/http.js
核心概念总结
| 概念 | 服务端 | 客户端 |
|---|---|---|
| 入口类 | Server | Client |
| 传输层 | StreamableHTTPServerTransport | StreamableHTTPClientTransport |
| 声明能力 | capabilities: { tools: {} } | - |
| 注册处理 | setRequestHandler(Schema, handler) | - |
| 调用能力 | - | listTools() / callTool() |
| 建立连接 | server.connect(transport) | client.connect(transport) |
扩展:添加新工具
只需两步:
1. 在 ListToolsRequestSchema 中声明:
tools: [
{ name: "query_weather", ... },
{
name: "send_email", // 新工具
description: "发送邮件",
inputSchema: {
type: "object",
properties: {
to: { type: "string" },
subject: { type: "string" },
body: { type: "string" }
},
required: ["to", "subject", "body"]
}
}
]
2. 在 CallToolRequestSchema 中实现:
case "send_email": {
const { to, subject, body } = request.params.arguments;
// 调用邮件服务...
return { content: [{ type: "text", text: `邮件已发送至 ${to}` }] };
}
参考资料
💡 小贴士:MCP 还支持 STDIO 和 SSE 两种传输方式,适合不同场景。HTTP 最适合生产环境和 API 集成。