MCP 入门教程:用 HTTP 方式构建 AI 工具服务

5 阅读1分钟

本文基于实战项目,手把手教你用 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

核心概念总结

概念服务端客户端
入口类ServerClient
传输层StreamableHTTPServerTransportStreamableHTTPClientTransport
声明能力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 集成。