2025年Model Context Protocol完整指南

如果你一直在关注2025年的AI工具生态,你可能已经注意到Model Context Protocol (MCP)已经从"来自Anthropic的有趣规范"演变成了"每个认真的SaaS产品都需要支持的东西"。这是有充分理由的。MCP为AI代理——Claude、基于GPT的助手、自定义代理——提供了一种标准化的方式来发现和调用你的API。把它看作是代理时代的OpenAPI,但内置了真实的双向通信。

在过去的几个月里,我为多个SaaS产品构建了MCP服务器,我想分享什么真正有效,文档没有告诉你什么,以及哪些架构决策很重要。这不是对规范的重述。这是一份我开始时希望拥有的指南。

目录

什么是Model Context Protocol (MCP)?

MCP是由Anthropic最初创建的一个开放协议,定义了AI模型如何与外部工具和数据源通信。它在2024年末发布,并在2025年初达到v1.0稳定版本,目前已被Claude Desktop、Cursor、Windsurf、OpenAI Agents SDK以及数十个其他AI客户端支持。

核心思想:不是每个AI集成都是具有专有格式的自定义插件,而是MCP提供了一个单一协议,任何兼容的客户端都可以使用它来发现你的服务提供的功能并与之交互。

以下是MCP定义的内容:

  • 工具 -- AI可以调用的函数(如create_ticketsearch_usersgenerate_report
  • 资源 -- AI可以读取的数据(如文档、数据库记录、配置文件)
  • 提示 -- 你的服务器可以公开的可重用提示模板
  • 采样 -- 你的服务器能够向客户端请求LLM补全的能力

传输层使用JSON-RPC 2.0,可以是stdio(对于本地服务器)或HTTP with Server-Sent Events (SSE)(对于远程服务器)。在2025-03规范修订版中引入的更新的Streamable HTTP传输是你想要用于任何生产SaaS部署的。

为什么你的SaaS产品需要一个MCP服务器

让我直言不讳:如果你的SaaS产品有一个API,你现在应该构建一个MCP服务器。原因如下。

AI代理正在成为主要的API使用者。 在2025年第一季度,Anthropic报告称Claude Desktop用户每天调用MCP工具超过200万次。这个数字增长得很快。你的客户已经在尝试使用AI代理与你的产品交互——问题是你是让它变得容易还是令人沮丧。

这是一个分发渠道。 当有人安装Claude Desktop并输入"帮我管理我的项目"时,AI可以发现并使用用户配置的MCP服务器。你的产品通过自然语言变得可访问。这不是一个噱头——这是你产品的真实新表面积。

你的竞争对手在这样做。 Stripe、Linear、Notion、GitHub、Sentry和Supabase都在2025年上半年发布了MCP服务器。如果你在B2B SaaS领域而没有一个,你正在落后。

因素 仅REST API REST API + MCP服务器
AI代理可访问性 每个代理需要自定义集成 任何MCP客户端自动工作
发现 开发者阅读文档 AI在运行时发现功能
首次集成时间 数小时到数天 数分钟
自然语言交互 没有包装器不可能 内置
维护负担 一个代码库 两个代码库(但MCP包装REST)

MCP架构:它实际上是如何工作的

在我们写代码之前,让我们把架构搞清楚。一个MCP部署有三个部分:

  1. MCP客户端 -- AI应用程序(Claude Desktop、Cursor、你的自定义代理)。它发现你的服务器的功能并调用它们。
  2. MCP服务器 -- 你的代码。它通过MCP协议公开工具、资源和提示。这是我们正在构建的。
  3. 你的SaaS API -- 实际的后端,你的MCP服务器调用它来完成事情。

流程看起来像这样:

用户 → AI客户端 → MCP客户端 → MCP服务器 → 你的SaaS API
                                      ↓
                              响应流回来

你的MCP服务器本质上是一个协议适配器。它在MCP协议(AI客户端使用的)和你现有的REST/GraphQL API之间进行转换。这意味着你根本不需要修改你现有的API。MCP服务器与它一起运行。

传输选项

对于SaaS产品,你有两个现实的传输选项:

  • Streamable HTTP(推荐):使用标准HTTP请求,可选的SSE用于流式传输。适用于负载平衡器、CDN和标准基础设施。这是你想要用于远程/托管MCP服务器的。
  • SSE(旧版):原始的远程传输。仍然有效,但规范为新实现推荐Streamable HTTP。

Stdio传输对于本地工具很有用,但不适用于SaaS产品的MCP服务器。

设置你的MCP服务器项目

让我们用TypeScript构建这个。官方的@modelcontextprotocol/sdk包得到了很好的维护,是生产使用的正确选择。如果那是你的堆栈,Python有mcp(官方Python SDK),但我将专注于TypeScript,因为我工作的大多数SaaS后端都使用Node.js。

mkdir my-saas-mcp-server
cd my-saas-mcp-server
npm init -y
npm install @modelcontextprotocol/sdk zod express
npm install -D typescript @types/node @types/express tsx

设置你的tsconfig.json

{
  "compilerOptions": {
    "target": "ES2022",
    "module": "Node16",
    "moduleResolution": "Node16",
    "outDir": "./dist",
    "rootDir": "./src",
    "strict": true,
    "esModuleInterop": true,
    "skipLibCheck": true
  },
  "include": ["src/**/*"]
}

现在让我们创建基本的服务器结构:

// src/server.ts
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
import express from "express";

const app = express();
app.use(express.json());

const server = new McpServer({
  name: "my-saas-mcp",
  version: "1.0.0",
  description: "MCP server for My SaaS Product",
});

// 我们将在这里添加工具、资源和提示

app.post("/mcp", async (req, res) => {
  const transport = new StreamableHTTPServerTransport({
    sessionIdGenerator: () => crypto.randomUUID(),
  });
  
  await server.connect(transport);
  await transport.handleRequest(req, res);
});

app.get("/mcp", async (req, res) => {
  // SSE端点用于流式响应
  const transport = new StreamableHTTPServerTransport({
    sessionIdGenerator: () => crypto.randomUUID(),
  });
  
  await server.connect(transport);
  await transport.handleRequest(req, res);
});

const PORT = process.env.PORT || 3001;
app.listen(PORT, () => {
  console.log(`MCP server running on port ${PORT}`);
});

这是你的框架。让我们填充它。

定义工具、资源和提示

工具

工具是最重要的原语。每个工具都是AI可以用结构化参数调用的函数。以下是如何定义一个:

import { z } from "zod";

server.tool(
  "create_project",
  "Create a new project in the workspace",
  {
    name: z.string().describe("The project name"),
    description: z.string().optional().describe("Project description"),
    team_id: z.string().describe("ID of the team that owns this project"),
  },
  async ({ name, description, team_id }) => {
    // 在这里调用你的SaaS API
    const project = await apiClient.createProject({ name, description, team_id });
    
    return {
      content: [
        {
          type: "text",
          text: JSON.stringify(project, null, 2),
        },
      ],
    };
  }
);

关于工具设计,我学到了几件事:

具体的描述。 AI使用你的工具描述和参数描述来决定何时以及如何调用它。模糊的描述会导致错误的工具选择。"创建一个新项目"还可以。"在用户的工作区中创建一个新项目。需要team_id,可以从list_teams工具获得"要好得多。

保持工具数量可控。 我见过有人公开50多个工具,然后想知道为什么AI会感到困惑。从10-15个核心操作开始。你总是可以添加更多。

返回结构化数据。 始终在文本内容中返回JSON。AI解析它的效果比散文好。

资源

资源公开可读的数据。把它们想象成AI使用的GET端点:

server.resource(
  "project-list",
  "projects://list",
  "List of all projects in the workspace",
  async () => {
    const projects = await apiClient.listProjects();
    return {
      contents: [
        {
          uri: "projects://list",
          mimeType: "application/json",
          text: JSON.stringify(projects, null, 2),
        },
      ],
    };
  }
);

提示

提示是可重用的模板。它们被利用不足但很强大:

server.prompt(
  "weekly-report",
  "Generate a weekly status report for a project",
  {
    project_id: z.string().describe("The project ID to report on"),
  },
  async ({ project_id }) => {
    const stats = await apiClient.getProjectStats(project_id);
    return {
      messages: [
        {
          role: "user",
          content: {
            type: "text",
            text: `Generate a weekly status report based on this data: ${JSON.stringify(stats)}. Include completed tasks, blockers, and upcoming deadlines.`,
          },
        },
      ],
    };
  }
);

连接到你的SaaS API

你的MCP服务器需要与你现有的API通信。我建议创建一个类型化的API客户端类:

// src/api-client.ts
class SaaSApiClient {
  private baseUrl: string;
  private apiKey: string;

  constructor(baseUrl: string, apiKey: string) {
    this.baseUrl = baseUrl;
    this.apiKey = apiKey;
  }

  private async request<T>(path: string, options?: RequestInit): Promise<T> {
    const response = await fetch(`${this.baseUrl}${path}`, {
      ...options,
      headers: {
        "Authorization": `Bearer ${this.apiKey}`,
        "Content-Type": "application/json",
        ...options?.headers,
      },
    });

    if (!response.ok) {
      const error = await response.text();
      throw new Error(`API error ${response.status}: ${error}`);
    }

    return response.json() as T;
  }

  async createProject(data: CreateProjectInput): Promise<Project> {
    return this.request<Project>("/api/v1/projects", {
      method: "POST",
      body: JSON.stringify(data),
    });
  }

  async listProjects(): Promise<Project[]> {
    return this.request<Project[]>("/api/v1/projects");
  }

  // ... 更多方法
}

保持这个客户端精简。它是一个透传,不是业务逻辑层。

认证和多租户

这是事情变得有趣的地方——也是大多数教程掩盖艰难部分的地方。

你的MCP服务器需要以两种方式对请求进行身份验证:

  1. MCP客户端向你的MCP服务器进行身份验证("这是一个有效的用户吗?")
  2. 你的MCP服务器向你的SaaS API进行身份验证("代表这个用户行动")

OAuth 2.0(生产环境推荐)

MCP规范包括一个基于OAuth 2.1的授权框架。对于SaaS产品,这是正确的方法:

// 中间件提取和验证OAuth令牌
app.use("/mcp", async (req, res, next) => {
  const authHeader = req.headers.authorization;
  if (!authHeader?.startsWith("Bearer ")) {
    return res.status(401).json({ error: "Missing authorization" });
  }

  const token = authHeader.slice(7);
  
  try {
    const user = await validateOAuthToken(token);
    req.user = user;
    next();
  } catch {
    return res.status(401).json({ error: "Invalid token" });
  }
});

然后将用户上下文传递到你的工具处理程序。这对多租户很关键——每个API调用都应该限制在经过身份验证的用户的工作区。

API密钥方法(更简单,用于内部/早期阶段)

如果你处于早期阶段或这是内部专用的,API密钥就可以:

const apiKey = req.headers["x-api-key"] as string;
const client = new SaaSApiClient(process.env.API_BASE_URL!, apiKey);

用户在配置其客户端中的MCP服务器时提供其API密钥,并将其传递到你的API。

错误处理和验证

AI代理需要清晰的错误消息。当工具失败时,AI需要理解原因,以便它可以修复输入或向用户解释问题。

server.tool(
  "get_project",
  "Get project details by ID",
  { project_id: z.string().uuid().describe("The project's UUID") },
  async ({ project_id }) => {
    try {
      const project = await apiClient.getProject(project_id);
      return {
        content: [{ type: "text", text: JSON.stringify(project, null, 2) }],
      };
    } catch (error) {
      const message = error instanceof Error ? error.message : "Unknown error";
      return {
        isError: true,
        content: [
          {
            type: "text",
            text: `Failed to get project: ${message}. Make sure the project_id is a valid UUID and the project exists in your workspace.`,
          },
        ],
      };
    }
  }
);

注意isError: true标志。这告诉AI客户端工具调用失败,所以它可以适当地处理它。始终在错误消息中包括可行的指导。

测试你的MCP服务器

到2025年,测试MCP服务器变得容易得多。这是你的选项:

MCP检查器

官方MCP检查器是你开发期间最好的朋友:

npx @modelcontextprotocol/inspector

这给你一个网络UI,你可以在其中连接到你的服务器、浏览工具/资源,并交互地调用它们。经常使用它。

自动化测试

对于CI/CD,将你的工具作为常规异步函数进行测试:

import { describe, it, expect } from "vitest";

describe("create_project tool", () => {
  it("should create a project with valid input", async () => {
    const result = await createProjectHandler({
      name: "Test Project",
      team_id: "team-123",
    });

    expect(result.isError).toBeUndefined();
    const data = JSON.parse(result.content[0].text);
    expect(data.name).toBe("Test Project");
  });

  it("should return error for missing team_id", async () => {
    // Zod验证应该在处理程序运行前捕获这个
    // 测试验证层
  });
});

与Claude Desktop的集成测试

一旦你的服务器运行,将其添加到Claude Desktop的配置:

{
  "mcpServers": {
    "my-saas": {
      "url": "http://localhost:3001/mcp",
      "headers": {
        "Authorization": "Bearer your-test-token"
      }
    }
  }
}

然后只需与Claude交谈并尝试自然地使用你的工具。你会快速找到自动化测试遗漏的边界情况。

部署和生产考虑事项

部署位置

你的MCP服务器只是一个Express应用。在你部署Node.js服务的任何地方部署它。一些不错的选项:

平台 冷启动 成本(估计) 最适合
Railway ~$5-20/月 小到中等SaaS
Fly.io <500ms ~$5-15/月 全球分布
AWS ECS/Fargate ~$15-50/月 企业、现有AWS
Vercel (Edge) <100ms $0-20/月 如果你已经在Vercel上
Cloudflare Workers <5ms $0-5/月 性能关键的

速率限制

AI代理可能很多话。单个用户对话可能会触发20-30个工具调用。实现速率限制,对于正常的AI使用来说足够宽松,但可以防止滥用:

import rateLimit from "express-rate-limit";

const limiter = rateLimit({
  windowMs: 60 * 1000, // 1分钟
  max: 60, // 每分钟每个用户60个请求
  keyGenerator: (req) => req.user?.id || req.ip,
});

app.use("/mcp", limiter);

监控

记录每个工具调用,包括用户ID、工具名称和延迟。你想要看到哪些工具被使用最多、哪些失败最多,以及延迟瓶颈在哪里。Datadog、Axiom或什至结构化JSON日志到CloudWatch都可以。

版本控制

你的MCP服务器会进化。在你的服务器元数据中使用version字段,并考虑在过渡期间在路径前缀后面运行多个版本(/mcp/v1/mcp/v2)。

真实案例:为项目管理SaaS构建MCP服务器

让我走过一个真实的例子。假设你正在为项目管理工具(想象一个简化的Linear或Asana)构建MCP服务器。

以下是我会公开的工具集:

// 核心CRUD工具
server.tool("list_projects", ...);
server.tool("get_project", ...);
server.tool("create_project", ...);
server.tool("update_project", ...);

// 任务管理
server.tool("list_tasks", ...);  // 带有状态、分配人、项目的过滤器
server.tool("create_task", ...);
server.tool("update_task", ...); // 更新状态、分配人、优先级
server.tool("add_comment", ...);

// 搜索和报告
server.tool("search", ...);      // 跨项目和任务的全文搜索
server.tool("get_project_stats", ...); // 项目的摘要统计

// 资源
server.resource("workspace-info", ...); // 工作区配置、团队成员

// 提示
server.prompt("standup-report", ...);   // 从最近的活动生成站立会报告
server.prompt("sprint-planning", ...);  // 帮助规划冲刺

这是12个工具、1个资源和2个提示。足够有用而不会淹没AI的工具选择。

用户体验看起来像这样:有人打开Claude Desktop并说"Backend Rewrite项目中哪些任务逾期了?"Claude用状态过滤器和项目名称调用list_tasks,获得结果,并用自然语言呈现它们。用户说"将身份验证迁移任务分配给Sarah,并将其提升到高优先级。"Claude调用update_task。它感觉很神奇,实际上只是协议管道。

如果你正在构建类似的东西,并想帮助处理通常伴随这些项目的Next.js前端无头CMS层,那是我们在Social Animal做很多的事情。但MCP服务器本身?那是你可以用这份指南绝对在内部构建的。

常见问题

MCP和函数调用有什么区别? 函数调用(如OpenAI的函数调用或Claude的工具使用)是AI模型如何决定在单个API调用内调用函数。MCP是允许AI客户端从外部服务器发现哪些函数可用的协议。它们一起工作——AI客户端在内部使用函数调用来决定何时调用MCP工具。把MCP想象成系统之间的管道,函数调用是模型的决策过程。

构建和运行MCP服务器要花多少钱? 服务器本身很轻。对于一个典型的SaaS产品,拥有10-20个工具,你看的是几百行TypeScript。托管成本取决于你的流量和平台,每月$5-50。真实的成本是开发人员时间——为具有身份验证、错误处理、监控和测试的生产质量MCP服务器预算2-4周。如果这感觉像很多,我们帮助团队更快地发布这些。查看我们的价格页面了解详情。

我能使用Python而不是TypeScript吗? 绝对。官方Python SDK(pip install mcp)很好,并且可以说对工具定义有更好的人体工程学。使用你的团队知道的任何东西。协议与语言无关。如果你的SaaS后端是Python(Django、FastAPI),用Python构建MCP服务器甚至更有意义,因为你可以共享模型和验证逻辑。

我需要修改我现有的API吗? 不。你的MCP服务器是一个调用你现有API的独立服务。这是一个适配器层。那说,你可能会发现自己想要添加一些专门为AI使用的API端点——比如返回比你的UI需要更多上下文的搜索端点。这没关系。但这是附加的,不是修改。

我如何处理长期运行的操作? 一些工具可能会触发需要数分钟的操作(如生成报告或处理大型导入)。使用MCP的进度通知功能来让客户端保持知情。你的工具处理程序可以在等待操作完成时发出进度更新。对于非常长的操作(>30秒),考虑立即返回一个作业ID,并提供单独的check_job_status工具。

MCP对生产足够稳定吗? 是的,从2025年中期开始。该规范在3月2025年达到v1.0,并且2025-03-26修订版(添加了Streamable HTTP)是主要客户采用的。Anthropic、Microsoft、Google和OpenAI都在对该协议进行投资。它不会消失。也就是说,关注规范更新——有关于更好的身份验证流和服务器到服务器通信的活跃提议。

在MCP工具中处理分页的最佳方法是什么? 默认返回合理的结果页(20-50项),并接受cursorpage参数。在你的响应中包括分页元数据,以便AI知道有更多结果。类似的东西:{ results: [...], next_cursor: "abc123", total_count: 342 }。如果用户需要更多数据,AI会自然地要求下一页。

一个MCP服务器能否同时支持多个AI客户端? 是的,应该这样。你的MCP服务器只是一个HTTP服务器处理并发请求。每个请求都包括用户的身份验证令牌,所以你将所有内容限制在正确的租户。如果你正确使用Streamable HTTP传输,就没有客户端特定的状态需要担心。把它当作任何其他无状态API服务器对待。