Solo Unicorn Club logoSolo Unicorn
2,700

MCP Server 完整搭建指南

AI AgentMCPModel Context Protocol工具集成服务器搭建实战
MCP Server 完整搭建指南

MCP Server 完整搭建指南

开场

MCP(Model Context Protocol)是 Anthropic 在 2024 年 11 月推出的开放协议,到 2026 年 3 月,Python 和 TypeScript SDK 的月下载量已经超过 9700 万次。它解决了一个核心问题:让 AI 模型用标准化的方式连接外部工具和数据。我搭建了 4 个生产级 MCP Server,这篇文章把完整的搭建流程——从环境配置到工具定义到安全加固——全部交代清楚。

问题背景

在 MCP 出现之前,每个 AI 应用和每个外部工具之间都需要写一套专门的集成代码。3 个 AI 应用 + 5 个工具 = 15 套集成代码。MCP 把这个 M x N 问题变成了 M + N:每个工具写一个 MCP Server,每个 AI 应用实现一个 MCP Client,通过标准协议通信。

2025 年 12 月,Anthropic 把 MCP 捐给了 Linux 基金会下的 Agentic AI Foundation(AAIF),OpenAI、Google、Microsoft、AWS 都加入了。这意味着 MCP 不再是 Anthropic 一家的协议,而是行业标准。

做 Agent 开发,你现在不学 MCP 说不过去。

核心架构

MCP 的三个基本概念

MCP Client (AI 应用)  ←→  MCP Server (工具提供方)
                  ↕
           标准化协议层
         ┌───────────────┐
         │   Tools       │  ← Agent 可以调用的函数
         │   Resources   │  ← Agent 可以读取的数据
         │   Prompts     │  ← 预定义的 prompt 模板
         └───────────────┘

Tools:Agent 可以调用的函数,有输入参数和输出结果。类似于 function calling,但通过标准协议暴露。

Resources:Agent 可以读取的数据源。比如数据库表、文件内容、API 返回的数据。只读,Agent 不能修改。

Prompts:预定义的 prompt 模板。比如 "分析这份报告" 的 prompt,Server 可以提供标准化的分析流程。

传输方式

MCP 支持两种传输方式:

传输方式 适用场景 特点
stdio 本地工具 通过标准输入输出通信,简单直接
HTTP + SSE 远程服务 通过 HTTP 请求 + Server-Sent Events,支持网络部署

本地开发用 stdio 就够了。生产部署一般用 HTTP + SSE(2025 年 11 月的新规范也支持 Streamable HTTP)。

实现细节

Step 1: 搭建 Python MCP Server

用官方 Python SDK:

# 安装 MCP Python SDK
pip install mcp

一个最小的 MCP Server 只需要几十行代码:

from mcp.server.fastmcp import FastMCP
import httpx
import json

# 创建 MCP Server 实例
mcp = FastMCP(
    name="company-data-server",
    version="1.0.0"
)

# 定义 Tool:查询客户信息
@mcp.tool()
async def get_customer(customer_id: str) -> str:
    """根据客户 ID 查询客户详细信息。

    Args:
        customer_id: 客户的唯一标识符
    """
    # 实际查询数据库
    async with httpx.AsyncClient() as client:
        resp = await client.get(
            f"https://api.internal.com/customers/{customer_id}",
            headers={"Authorization": f"Bearer {API_TOKEN}"}
        )
        data = resp.json()

    return json.dumps({
        "name": data["name"],
        "email": data["email"],
        "plan": data["subscription_plan"],
        "mrr": data["monthly_revenue"]
    }, ensure_ascii=False)

# 定义 Tool:创建工单
@mcp.tool()
async def create_ticket(
    title: str,
    description: str,
    priority: str = "medium"
) -> str:
    """在工单系统中创建新工单。

    Args:
        title: 工单标题
        description: 问题详细描述
        priority: 优先级,可选 low/medium/high
    """
    if priority not in ("low", "medium", "high"):
        return json.dumps({"error": "priority 只能是 low/medium/high"})

    async with httpx.AsyncClient() as client:
        resp = await client.post(
            "https://api.internal.com/tickets",
            headers={"Authorization": f"Bearer {API_TOKEN}"},
            json={
                "title": title,
                "description": description,
                "priority": priority
            }
        )
        ticket = resp.json()

    return json.dumps({
        "ticket_id": ticket["id"],
        "status": "created",
        "url": f"https://tickets.internal.com/{ticket['id']}"
    })

# 定义 Resource:产品目录
@mcp.resource("products://catalog")
async def get_product_catalog() -> str:
    """获取完整的产品目录数据"""
    async with httpx.AsyncClient() as client:
        resp = await client.get("https://api.internal.com/products")
    return resp.text

# 定义 Prompt:客户分析模板
@mcp.prompt()
async def analyze_customer(customer_id: str) -> str:
    """生成客户分析的标准 prompt"""
    return f"""请分析客户 {customer_id} 的数据,包括:
1. 当前订阅状态和 MRR
2. 过去 30 天的使用趋势
3. 流失风险评估
4. 建议的跟进策略

使用 get_customer 工具获取客户数据后进行分析。"""

if __name__ == "__main__":
    mcp.run()  # 默认使用 stdio 传输

Step 2: TypeScript 版本

如果你的团队用 TypeScript:

import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { z } from "zod";

// 创建 Server
const server = new McpServer({
  name: "company-data-server",
  version: "1.0.0",
});

// 定义 Tool
server.tool(
  "get_customer",
  "根据客户 ID 查询客户详细信息",
  {
    customer_id: z.string().describe("客户的唯一标识符"),
  },
  async ({ customer_id }) => {
    // 查询逻辑
    const resp = await fetch(
      `https://api.internal.com/customers/${customer_id}`,
      { headers: { Authorization: `Bearer ${process.env.API_TOKEN}` } }
    );
    const data = await resp.json();

    return {
      content: [
        {
          type: "text",
          text: JSON.stringify({
            name: data.name,
            email: data.email,
            plan: data.subscription_plan,
          }),
        },
      ],
    };
  }
);

// 启动 Server(stdio 传输)
const transport = new StdioServerTransport();
await server.connect(transport);

Step 3: 在 Claude Desktop 中配置

MCP Server 搭好后,需要在客户端配置。以 Claude Desktop 为例:

// ~/Library/Application Support/Claude/claude_desktop_config.json
{
  "mcpServers": {
    "company-data": {
      "command": "python",
      "args": ["/path/to/company_data_server.py"],
      "env": {
        "API_TOKEN": "your-api-token-here"
      }
    }
  }
}

在 Claude Code 中:

// .claude/settings.json
{
  "mcpServers": {
    "company-data": {
      "command": "python",
      "args": ["/path/to/company_data_server.py"],
      "env": {
        "API_TOKEN": "your-api-token-here"
      }
    }
  }
}

Step 4: 安全加固

生产环境的 MCP Server 必须考虑安全性:

from functools import wraps
import time
import logging

logger = logging.getLogger("mcp-server")

# 速率限制装饰器
class RateLimiter:
    def __init__(self, max_calls: int, window_seconds: int):
        self.max_calls = max_calls
        self.window = window_seconds
        self.calls: list[float] = []

    def check(self) -> bool:
        now = time.time()
        self.calls = [t for t in self.calls if now - t < self.window]
        if len(self.calls) >= self.max_calls:
            return False
        self.calls.append(now)
        return True

rate_limiter = RateLimiter(max_calls=100, window_seconds=60)

# 输入验证 + 日志 + 速率限制
@mcp.tool()
async def get_customer_secure(customer_id: str) -> str:
    """查询客户信息(带安全校验)"""

    # 速率限制
    if not rate_limiter.check():
        return json.dumps({"error": "请求频率过高,请稍后重试"})

    # 输入验证(防注入)
    if not customer_id.isalnum() or len(customer_id) > 20:
        return json.dumps({"error": "无效的客户 ID 格式"})

    # 审计日志
    logger.info(f"Tool called: get_customer, id={customer_id}")

    # 执行查询
    try:
        result = await _query_customer(customer_id)
        # 数据脱敏(隐藏敏感字段)
        result.pop("ssn", None)
        result.pop("credit_card", None)
        return json.dumps(result, ensure_ascii=False)
    except Exception as e:
        logger.error(f"查询失败: {e}")
        return json.dumps({"error": "查询失败,请重试"})

安全清单:

  • 输入验证:所有参数做格式和长度检查
  • 速率限制:防止 Agent 失控时大量调用
  • 数据脱敏:敏感字段在返回前移除
  • 审计日志:记录每次 Tool 调用的参数和结果
  • 最小权限:MCP Server 使用的 API Token 只授予必要权限

实战经验

生产数据

我为一个 SaaS 产品搭建的 MCP Server 运行了 3 个月:

指标 数值
注册的 Tools 12 个
注册的 Resources 8 个
日均调用量 2,300 次
平均响应延迟 180ms(本地 stdio) / 320ms(HTTP)
错误率 0.3%
因速率限制被拒绝 1.2%

踩过的坑

坑 1:Tool 描述决定调用质量。 MCP Tool 的 description 不只是给人看的——LLM 根据它来决定何时调用这个 Tool。最初我的 description 写得很模糊("获取数据"),导致 Claude 经常调错 Tool。改成精确描述后("根据客户 ID 查询客户的订阅计划、月收入和联系方式"),调用准确率从 78% 提升到 96%。

坑 2:stderr 被吞掉。 stdio 传输模式下,Python 的 print 输出到 stdout 会干扰 MCP 协议通信。所有日志必须走 stderr 或写文件,不能用 print。这个问题排查了两小时。

坑 3:大数据量返回。 有一次 Resource 返回了完整产品目录(50MB JSON),直接把 Claude 的 context window 撑爆了。解决方案:对大数据量的 Resource 做分页,或者改成 Tool 支持条件查询。

适用范围

适合 MCP 的场景:

  • 你有多个 AI 应用需要访问同一组工具
  • 你想让工具对多个 LLM(Claude, GPT, Gemini)通用
  • 你需要标准化的工具发现和描述机制

不适合 MCP 的场景:

  • 只有一个 AI 应用和一两个工具——直接用 Tool Use API 更简单
  • 需要极低延迟(< 50ms)——MCP 协议层有额外开销
  • 工具逻辑频繁变更——每次改 Tool 定义需要重启 Server

总结

三条核心 takeaway:

  1. MCP 是 Agent 工具集成的标准答案——不管你用 Claude、GPT 还是 Gemini,一套 MCP Server 可以服务所有模型。在 AAIF 的推动下,2026 年不支持 MCP 的 Agent 框架会越来越边缘化。

  2. Tool 描述的质量比 Tool 实现的质量更重要——LLM 根据描述决定是否调用、怎么调用。把 description 当成用户文档来写,清晰说明输入、输出和适用场景。

  3. 安全加固不是可选项——MCP Server 本质上是给 AI 开放了一个 API 接口。速率限制、输入验证、数据脱敏、审计日志,一个都不能少。

如果你还没有写过 MCP Server,建议从一个只有一个 Tool 的最小 Server 开始,配合 Claude Desktop 跑通后,再逐步加 Tool 和 Resource。整个过程不到一小时。

你对 MCP 有什么疑问?或者已经搭建了自己的 MCP Server?欢迎交流。