在 Vercel 上构建 Model Context Protocol 服务器

mcpvercelaiserverlessnextjstypescript
By sko X opus 4.19/19/202518 min read

Model Context Protocol(MCP)已成为连接 AI 系统与外部工具和数据源的基础标准,而 vercel/mcp-handler 为 Vercel 平台提供了生产就绪的实现。本综合指南探讨了从概念到部署的 MCP 服务器开发,融入了 2025 年的最新更新和最佳实践。

从开发者角度理解 MCP

将 MCP 服务器视为专为 AI 应用程序设计的 API 服务器。就像 REST API 为 Web 客户端公开端点一样,MCP 服务器为 AI 模型公开工具、资源和提示。关键区别:您返回的不是用于渲染的 JSON 数据,而是 AI 模型可以理解并据此行动的结构化信息。

以下是实际分解:

MCP 服务器 = 您的 API 服务器 您编写 AI 可以调用的函数。这些就像 API 端点,但不是像 /api/users 这样的 HTTP 路由,而是定义像 get_user_dataprocess_payment 这样的工具。

工具 = API 端点 每个工具本质上是一个带有输入验证(使用 Zod 模式)和返回值的函数。当 Claude 或 Gemini 需要执行操作时,它们会像前端调用您的 API 一样调用您的工具。

资源 = 静态数据端点 将这些视为返回上下文信息的 GET 端点。资源可以是静态的(如配置)或动态的(如用户配置文件)。它们是 AI 可以引用的只读数据源。

提示 = 请求模板 这些是常见 AI 操作的预配置模板 - 类似于 GraphQL 片段或保存的 Postman 请求。它们有助于标准化 AI 模型与服务器的交互方式。

vercel/mcp-handler 包处理所有协议复杂性 - WebSocket 连接、消息路由、错误处理 - 因此您可以专注于编写业务逻辑。它就像 MCP 的 Express.js:您定义处理程序,框架管理基础设施。

// 这就是您需要理解的全部:
// 1. AI 调用您的工具
// 2. 您处理请求
// 3. 您返回响应
// 其他一切都由框架处理

使用综合示例设置 vercel/mcp-handler

安装从构成任何 MCP 服务器基础的核心依赖项开始:

npm install mcp-handler @modelcontextprotocol/sdk zod@^3

基本服务器结构展示了处理程序模式的优雅。这个 Next.js 实现创建了一个简单的掷骰子工具,展示了基本组件:

// app/api/[transport]/route.ts
import { createMcpHandler } from "mcp-handler";
import { z } from "zod";

const handler = createMcpHandler(
  (server) => {
    server.tool(
      "roll_dice",
      "掷一个 N 面骰子",
      { sides: z.number().int().min(2).max(100) },
      async ({ sides }) => {
        const value = 1 + Math.floor(Math.random() * sides);
        return {
          content: [{ type: "text", text: ` 您掷出了 ${value}!` }],
        };
      }
    );
  },
  {
    capabilities: {
      tools: { roll_dice: { description: "掷指定面数的骰子" } },
    },
  },
  {
    redisUrl: process.env.REDIS_URL,
    basePath: "/api",
    maxDuration: 60,
    verboseLogs: process.env.NODE_ENV === "development",
  }
);

export { handler as GET, handler as POST, handler as DELETE };

配置对象接受几个关键参数。redisUrl 启用 SSE 传输状态管理,以维护跨请求的连接持久性。basePath 必须匹配您的路由结构,确定 MCP 端点的可访问位置。maxDuration 设置连接超时,而 verboseLogs 协助开发调试。

对于生产环境,vercel.json 配置变得至关重要:

{
  "$schema": "https://openapi.vercel.sh/vercel.json",
  "functions": {
    "api/[transport]/route.ts": {
      "maxDuration": 800,
      "memory": 1024
    }
  },
  "env": {
    "REDIS_URL": "@redis-url",
    "MCP_API_KEY": "@mcp-api-key"
  }
}

实现工具、资源和提示

工具代表 MCP 中的主要交互机制,使 AI 模型能够执行操作和检索信息。一个综合的工具实现演示了验证、错误处理和响应格式化:

server.tool(
  "process_data",
  "使用统计操作分析数据集",
  {
    data: z.array(z.object({
      id: z.string().uuid(),
      value: z.number(),
      timestamp: z.string().datetime(),
      metadata: z.record(z.any()).optional(),
    })).min(1, "数据数组不能为空"),
    operation: z.enum(["sum", "average", "max", "min", "stddev"]),
    groupBy: z.string().optional(),
  },
  async ({ data, operation, groupBy }) => {
    try {
      const values = data.map(item => item.value);
      let result: number;

      switch (operation) {
        case "sum":
          result = values.reduce((acc, val) => acc + val, 0);
          break;
        case "average":
          result = values.reduce((acc, val) => acc + val, 0) / values.length;
          break;
        case "stddev":
          const mean = values.reduce((a, b) => a + b, 0) / values.length;
          const variance = values.reduce((acc, val) =>
            acc + Math.pow(val - mean, 2), 0) / values.length;
          result = Math.sqrt(variance);
          break;
        case "max":
          result = Math.max(...values);
          break;
        case "min":
          result = Math.min(...values);
          break;
      }

      return {
        content: [{
          type: "text",
          text: `${data.length} 项的 ${operation.toUpperCase()}:${result.toFixed(2)}`
        }],
      };
    } catch (error) {
      return {
        content: [{
          type: "text",
          text: `处理错误:${error.message}`
        }],
        isError: true,
      };
    }
  }
);

资源为 AI 模型提供上下文数据,支持静态和动态内容。动态资源模板启用参数化数据访问:

import {
  ListResourceTemplatesRequestSchema,
  ReadResourceRequestSchema,
} from "@modelcontextprotocol/sdk/types.js";

server.setRequestHandler(ListResourceTemplatesRequestSchema, () => ({
  resourceTemplates: [
    {
      uriTemplate: "user://{userId}/profile",
      name: "用户配置文件",
      description: "包含偏好设置的完整用户配置文件",
      mimeType: "application/json",
    },
    {
      uriTemplate: "analytics://{metric}/{timeRange}",
      name: "分析数据",
      description: "指定指标的时间序列分析",
      mimeType: "application/json",
    },
  ],
}));

server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
  const { uri } = request.params;

  const userMatch = uri.match(/^user:\/\/([^\/]+)\/profile$/);
  if (userMatch) {
    const userId = decodeURIComponent(userMatch[1]);
    const userData = await fetchUserFromDatabase(userId);

    return {
      contents: [{
        uri,
        text: JSON.stringify(userData, null, 2),
        mimeType: "application/json",
      }],
    };
  }

  const analyticsMatch = uri.match(/^analytics:\/\/([^\/]+)\/(.+)$/);
  if (analyticsMatch) {
    const [, metric, timeRange] = analyticsMatch;
    const data = await fetchAnalytics(metric, timeRange);

    return {
      contents: [{
        uri,
        text: JSON.stringify(data, null, 2),
        mimeType: "application/json",
      }],
    };
  }

  throw new Error(`未找到资源:${uri}`);
});

提示为常见的 AI 交互提供可重用模板,特别对复杂的多步操作有价值:

import { GetPromptRequestSchema } from "@modelcontextprotocol/sdk/types.js";

server.setRequestHandler(GetPromptRequestSchema, (request) => {
  const { name, arguments: args } = request.params;

  if (name === "code-review") {
    const { language, code, focus } = args as {
      language: string;
      code: string;
      focus?: string;
    };

    return {
      messages: [
        {
          role: "user",
          content: {
            type: "text",
            text: `审查这段 ${language} 代码${focus ? `(重点关注 ${focus})` : ''}:

\`\`\`${language}
${code}
\`\`\`

分析内容:
- 潜在的错误和边缘情况
- 性能优化
- 安全漏洞
- 代码风格和最佳实践
- 建议的重构改进`,
          },
        },
      ],
    };
  }

  throw new Error(`未找到提示:${name}`);
});

认证模式和安全实现

安全是生产 MCP 服务器的关键考虑因素。OAuth 2.1 实现遵循最新标准,包括资源指示器(RFC 8707)以防止令牌误用:

import { createMcpHandler, withMcpAuth } from "mcp-handler";
import { AuthInfo } from "@modelcontextprotocol/sdk/server/auth/types.js";

const baseHandler = createMcpHandler((server) => {
  server.tool(
    "protected_operation",
    "执行敏感数据操作",
    {
      operation: z.enum(["read", "write", "delete"]),
      resource: z.string(),
    },
    async ({ operation, resource }, extra) => {
      const authInfo = extra.authInfo;

      // 基于范围的授权
      if (operation === "delete" && !authInfo?.scopes?.includes("admin:delete")) {
        return {
          content: [{ type: "text", text: "删除权限不足" }],
          isError: true,
        };
      }

      // 使用用户上下文执行操作
      const result = await performOperation(operation, resource, authInfo?.clientId);

      return {
        content: [{
          type: "text",
          text: `为 ${authInfo?.clientId} 完成 ${operation}:${result}`
        }],
      };
    }
  );
});

const verifyToken = async (
  req: Request,
  bearerToken?: string
): Promise<AuthInfo | undefined> => {
  if (!bearerToken) return undefined;

  try {
    // 使用授权服务器验证令牌
    const response = await fetch(process.env.AUTH_SERVER_URL + "/introspect", {
      method: "POST",
      headers: {
        "Content-Type": "application/x-www-form-urlencoded",
        "Authorization": `Basic ${Buffer.from(
          `${process.env.CLIENT_ID}:${process.env.CLIENT_SECRET}`
        ).toString("base64")}`,
      },
      body: new URLSearchParams({
        token: bearerToken,
        token_type_hint: "access_token",
      }),
    });

    if (!response.ok) return undefined;

    const introspection = await response.json();

    if (!introspection.active) return undefined;

    return {
      token: bearerToken,
      scopes: introspection.scope?.split(" ") || [],
      clientId: introspection.sub,
      extra: {
        userId: introspection.sub,
        organizationId: introspection.org_id,
        permissions: introspection.permissions,
      },
    };
  } catch (error) {
    console.error("令牌验证失败:", error);
    return undefined;
  }
};

const authHandler = withMcpAuth(baseHandler, verifyToken, {
  required: true,
  requiredScopes: ["mcp:read", "mcp:execute"],
  resourceMetadataPath: "/.well-known/oauth-protected-resource",
});

export { authHandler as GET, authHandler as POST };

OAuth 受保护资源元数据端点提供发现信息:

// app/.well-known/oauth-protected-resource/route.ts
import { protectedResourceHandler } from "mcp-handler";

const handler = protectedResourceHandler({
  authServerUrls: [process.env.AUTH_SERVER_URL],
});

export { handler as GET };

Vercel 特定的部署考虑

Vercel 平台为 MCP 服务器提供两种具有不同权衡的函数类型。无服务器函数由于完整的 Node.js 运行时兼容性、企业计划中长达 900 秒的执行限制以及全面的 npm 包支持,提供了推荐的方法。虽然边缘函数提供快 40% 的冷启动和低 15 倍的成本,但其仅限 V8 的运行时和 4MB 大小限制使其不适合复杂的 MCP 实现。

部署架构利用 Vercel 的 Fluid Compute 实现比传统无服务器节省 90% 的成本,特别有利于具有不规则使用模式的 AI 工作负载。冷启动缓解策略对于保持响应性变得至关重要:

// api/keepalive.js - 防止冷启动
export default async function handler(req, res) {
  // 简单的健康检查以保持函数温暖
  const timestamp = new Date().toISOString();
  res.status(200).json({ status: "warm", timestamp });
}

// 在 vercel.json 中配置 cron 作业
{
  "crons": [{
    "path": "/api/keepalive",
    "schedule": "*/5 * * * *"
  }]
}

CORS 配置确保跨不同域的正确客户端连接:

export const config = {
  api: {
    cors: {
      origin: ["https://claude.ai", "https://gemini.google.com"],
      methods: ["GET", "POST", "OPTIONS"],
      allowedHeaders: ["Content-Type", "Authorization", "X-API-Key"],
      credentials: true,
    }
  }
}

调试、测试和监控策略

开发从使用 MCP Inspector 进行本地测试开始,为服务器功能提供即时反馈:

# 启动开发服务器
vercel dev

# 使用 MCP Inspector 测试
npx @modelcontextprotocol/inspector http://localhost:3000/api/mcp

全面的日志记录有助于在生产环境中进行调试:

import { createLogger } from "./utils/logger";

const logger = createLogger({
  service: "mcp-server",
  environment: process.env.VERCEL_ENV,
});

server.tool("complex_operation", "执行复杂处理", schema,
  async (args) => {
    const requestId = crypto.randomUUID();

    logger.info("工具执行开始", {
      requestId,
      tool: "complex_operation",
      args: JSON.stringify(args),
    });

    try {
      const result = await performComplexOperation(args);

      logger.info("工具执行完成", {
        requestId,
        duration: Date.now() - startTime,
        resultSize: JSON.stringify(result).length,
      });

      return result;
    } catch (error) {
      logger.error("工具执行失败", {
        requestId,
        error: error.message,
        stack: error.stack,
      });

      throw error;
    }
  }
);

集成测试验证端到端功能:

import { describe, it, expect, beforeAll } from "vitest";
import { Client } from "@modelcontextprotocol/sdk/client";
import { SSEClientTransport } from "@modelcontextprotocol/sdk/client/sse";

describe("MCP 服务器集成", () => {
  let client: Client;

  beforeAll(async () => {
    client = new Client({
      transport: new SSEClientTransport({
        url: "http://localhost:3000/api/mcp/sse",
      }),
    });

    await client.connect();
  });

  it("应该使用适当的验证执行工具", async () => {
    const result = await client.callTool({
      name: "process_data",
      args: {
        data: [
          { id: "123e4567-e89b-12d3-a456-426614174000", value: 42, timestamp: new Date().toISOString() },
          { id: "987f6543-e21b-12d3-a456-426614174111", value: 84, timestamp: new Date().toISOString() },
        ],
        operation: "average",
      },
    });

    expect(result.content[0].text).toContain("2 项的 AVERAGE:63.00");
  });
});

生产监控利用 Vercel 的内置分析以及自定义指标:

const metrics = {
  toolExecutions: new Map(),
  errors: [],
  responseTimings: [],
};

const withMetrics = (toolName: string, toolFunction: Function) => {
  return async (...args: any[]) => {
    const startTime = Date.now();
    const executionId = crypto.randomUUID();

    metrics.toolExecutions.set(executionId, {
      tool: toolName,
      startTime,
      status: "running",
    });

    try {
      const result = await Promise.race([
        toolFunction(...args),
        new Promise<never>((_, reject) =>
          setTimeout(() => reject(new Error("操作超时")), this.timeout)
        ),
      ]);

      metrics.toolExecutions.set(executionId, {
        ...metrics.toolExecutions.get(executionId),
        status: "completed",
        duration: Date.now() - startTime,
      });

      metrics.responseTimings.push(Date.now() - startTime);

      return result;
    } catch (error) {
      metrics.errors.push({
        tool: toolName,
        error: error.message,
        timestamp: new Date().toISOString(),
      });

      throw error;
    }
  };
};

真实世界的用例和架构模式

生产部署展示了跨行业的多样化 MCP 应用。**Block(Square)**为内部金融工具运营 60 多个 MCP 服务器,使用领域驱动设计实现付款、库存和分析的独立服务器。Netflix 利用 MCP 进行内容管理工作流程,使用多服务器模式独立处理元数据、转码和推荐系统。

多服务器架构模式有效地分离关注点:

// 核心业务逻辑服务器
const coreServer = createMcpHandler((server) => {
  server.tool("create_order", "创建新订单", orderSchema,
    async (orderData) => {
      const order = await createOrder(orderData);
      await publishEvent("order.created", order);
      return { content: [{ type: "text", text: `订单 ${order.id} 已创建` }] };
    }
  );
});

// 具有只读操作的分析服务器
const analyticsServer = createMcpHandler((server) => {
  server.tool("generate_report", "生成分析报告", reportSchema,
    async (params) => {
      const report = await generateReport(params);
      return { content: [{ type: "text", text: JSON.stringify(report) }] };
    }
  );
});

// 具有提升权限的管理服务器
const adminServer = createMcpHandler((server) => {
  server.tool("manage_users", "用户管理操作", userManagementSchema,
    async (operation, extra) => {
      if (!extra.authInfo?.scopes?.includes("admin:users")) {
        throw new Error("需要管理员权限");
      }
      const result = await performUserManagement(operation);
      return { content: [{ type: "text", text: result }] };
    }
  );
});

断路器模式确保在集成外部服务时的弹性:

class CircuitBreaker {
  private failureCount = 0;
  private lastFailureTime?: number;
  private state: "CLOSED" | "OPEN" | "HALF_OPEN" = "CLOSED";

  constructor(
    private threshold = 5,
    private timeout = 60000,
    private resetTimeout = 120000
  ) {}

  async execute<T>(operation: () => Promise<T>): Promise<T> {
    if (this.state === "OPEN") {
      if (Date.now() - this.lastFailureTime! > this.resetTimeout) {
        this.state = "HALF_OPEN";
        this.failureCount = 0;
      } else {
        throw new Error("断路器已打开 - 服务不可用");
      }
    }

    try {
      const result = await Promise.race([
        operation(),
        new Promise<never>((_, reject) =>
          setTimeout(() => reject(new Error("操作超时")), this.timeout)
        ),
      ]);

      if (this.state === "HALF_OPEN") {
        this.state = "CLOSED";
        this.failureCount = 0;
      }

      return result;
    } catch (error) {
      this.failureCount++;
      this.lastFailureTime = Date.now();

      if (this.failureCount >= this.threshold) {
        this.state = "OPEN";
      }

      throw error;
    }
  }
}

const breaker = new CircuitBreaker();

server.tool("external_api_call", "使用断路器调用外部 API", schema,
  async (params) => {
    try {
      const result = await breaker.execute(async () => {
        const response = await fetch("https://api.external.com/data", {
          method: "POST",
          body: JSON.stringify(params),
        });
        return response.json();
      });

      return { content: [{ type: "text", text: JSON.stringify(result) }] };
    } catch (error) {
      return {
        content: [{
          type: "text",
          text: "外部服务暂时不可用 - 请稍后重试"
        }],
        isError: true,
      };
    }
  }
);

最近更新和生态系统演进

MCP 生态系统自 2024 年 11 月发布以来经历了爆炸性增长。2025 年的主要平台采用包括 3 月 Google 在 Gemini 桌面和 Agents SDK 中的全面集成,5 月 Microsoft 的原生 Copilot Studio 支持(具有一键式服务器连接),以及 7 月 AWS 为 Lambda、ECS 和其他服务提供的官方 MCP 服务器。

安全改进通过使用资源指示器(RFC 8707)的 OAuth 2.1 实现、用于行为声明的增强工具注释以及全面的审计日志功能解决了早期漏洞。该协议现在支持音频内容以及文本和图像、用于提高效率的 JSON-RPC 批处理,以及替代已弃用的 SSE 机制的 Streamable HTTP 传输。

到 2025 年 2 月,社区已创建了1000 多个 MCP 服务器,涵盖从金融服务(Stripe、Block)到开发工具(GitHub、VS Code)再到数据平台(Databricks、Supabase)的各种用例。像 Netflix 和 Block 这样的公司在生产环境中运行数十个服务器,企业部署展示了 MCP 的生产准备状态。

市场预测表明,到 2025 年底,MCP 生态系统将达到 45 亿美元,预计 90% 的组织将采用该协议。技术演进继续通过 Google 的 Agent2Agent 协议解决多代理协调问题、扩展云平台集成以及持续的安全框架成熟。

最佳实践和架构建议

成功的 MCP 服务器实现需要仔细关注安全性、性能和可维护性。安全优先设计从一开始就实现 OAuth 2.1,使用 Zod 模式验证所有输入,实现速率限制和断路器,并维护全面的审计日志。使用 Docker 或 Kubernetes 的容器隔离为生产部署提供额外的安全边界。

性能优化利用 Redis 进行连接状态管理,实现智能缓存策略,使用连接池进行数据库访问,并持续监控指标。多服务器模式分离读写操作,隔离繁重的处理工作负载,并支持不同功能的独立扩展。

开发工作流最佳实践包括为新项目从 Streamable HTTP 传输开始,使用官方 SDK 确保一致的实现,实现带有回退的全面错误处理,以及维护包括集成测试在内的高测试覆盖率。文档应涵盖 API 规范、认证要求、示例请求和响应以及故障排除指南。

部署策略优先考虑使用功能标志的逐步推出、从第一天开始的全面监控、定期安全审计和更新以及灾难恢复计划。组织应从低风险试点计划开始,通过小规模部署验证模式,并根据已证实的成功逐步扩展。

Model Context Protocol 代表了 AI 系统如何与外部工具和数据源交互的根本性转变。MCP 深思熟虑的协议设计与 vercel/mcp-handler 的生产就绪实现相结合,为构建复杂的 AI 集成创造了强大的基础。随着生态系统继续成熟,增强的安全功能、更广泛的平台采用和经过验证的企业部署,MCP 确立了自己作为下一代 AI 驱动应用程序的标准协议的地位。