如何在 2025 年為您的 SaaS 產品建構 MCP 伺服器
如果你一直在關注 2025 年 AI 工具領域,你可能已經注意到模型上下文協議 (MCP) 已經從「Anthropic 的有趣規範」演變成「每個認真的 SaaS 產品都需要支持的東西」。這是有充分理由的。MCP 為 AI 代理 -- Claude、基於 GPT 的助手、自訂代理 -- 提供了一個標準化的方式來發現和呼叫你的 API。可以將其視為智能體時代的 OpenAPI,但內置了實時雙向通訊。
我在過去幾個月為多個 SaaS 產品構建了 MCP 伺服器,我想分享什麼真正有效、文檔沒有告訴你的內容,以及重要的架構決策。這不是對規範的重新陳述。這是我開始時希望擁有的指南。
目錄
- 什麼是模型上下文協議 (MCP)?
- 為什麼你的 SaaS 產品需要 MCP 伺服器
- MCP 架構:它實際上是如何運作的
- 設置你的 MCP 伺服器項目
- 定義工具、資源和提示
- 連接到你的 SaaS API
- 認證和多租戶
- 錯誤處理和驗證
- 測試你的 MCP 伺服器
- 部署和生產考慮
- 真實示例:為項目管理 SaaS 構建 MCP 伺服器
- 常見問題
什麼是模型上下文協議 (MCP)?
MCP 是由 Anthropic 最初創建的開放協議,定義了 AI 模型如何與外部工具和數據源進行通訊。在 2024 年底發布,並在 2025 年初達到 v1.0 穩定版,現在受到 Claude Desktop、Cursor、Windsurf、OpenAI Agents SDK 和數十個其他 AI 客戶端的支持。
核心概念:不是每個 AI 集成都是具有專有格式的自訂插件,而是 MCP 提供了一個單一協議,任何兼容的客戶端都可以用它來發現你的服務提供什麼並與之交互。
以下是 MCP 定義的內容:
- 工具 -- AI 可以呼叫的函數(如
create_ticket、search_users、generate_report) - 資源 -- AI 可以讀取的數據(如文檔、資料庫記錄、配置文件)
- 提示 -- 你的伺服器可以公開的可重用提示範本
- 採樣 -- 你的伺服器可以從客戶端請求 LLM 完成的能力
傳輸層使用 JSON-RPC 2.0,可通過 stdio(用於本地伺服器)或 HTTP 與服務器發送事件 (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 部署有三個部分:
- MCP 客戶端 -- AI 應用程式(Claude Desktop、Cursor、你的自訂代理)。它發現你的伺服器的功能並呼叫它們。
- MCP 伺服器 -- 你的程式碼。它通過 MCP 協議公開工具、資源和提示。這是我們正在構建的。
- 你的 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 伺服器需要以兩種方式認證請求:
- MCP 客戶端向你的 MCP 伺服器認證(「這是有效用戶嗎?」)
- 你的 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 伺服器
測試 MCP 伺服器在 2025 年變得容易得多。以下是你的選項:
MCP 檢查器
官方 MCP Inspector 是開發過程中你最好的朋友:
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 並說「後端重寫項目中有哪些任務超期?」Claude 調用帶有狀態篩選器和項目名稱的 list_tasks,獲取結果,並以自然語言呈現它們。用戶說「將身份驗證遷移任務分配給 Sarah,並將其提升為高優先級。」Claude 調用 update_task。它感覺神奇,它真的只是協議管道。
如果你正在構建類似的東西,並希望在 Next.js 前端或通常伴隨這些項目的無頭 CMS 層上獲得幫助,那是我們在 Social Animal 做很多的事情。但 MCP 伺服器本身?這是你可以用這個指南在內部構建的絕對東西。
常見問題
MCP 和函數呼叫之間有什麼區別? 函數呼叫(如 OpenAI 的函數呼叫或 Claude 的工具使用)是 AI 模型決定在單個 API 呼叫中調用函數的方式。MCP 是允許 AI 客戶端發現從外部伺服器可用的函數的協議。它們一起運作 -- AI 客戶端在內部使用函數呼叫來決定何時調用 MCP 工具。將 MCP 視為系統之間的管道,將函數呼叫視為模型的決策過程。
構建和運行 MCP 伺服器需要花費多少? 伺服器本身是輕量級的。對於具有 10-20 個工具的典型 SaaS 產品,你查看幾百行 TypeScript。託管成本 $5-50/月,取決於你的流量和平台。真正的成本是開發者時間 -- 預算 2-4 週用於生產質量的 MCP 伺服器,包括認證、錯誤處理、監控和測試。如果這感覺很多,我們已經幫助團隊更快地運行這些。檢查我們的定價頁面了解詳情。
我可以使用 Python 而不是 TypeScript 嗎?
絕對。官方 Python SDK(pip install mcp)非常好,可能對工具定義有更好的人體工程學。使用你的團隊知道的任何東西。協議是不可知的語言。如果你的 SaaS 後端是 Python(Django、FastAPI),在 Python 中構建 MCP 伺服器更有意義,因為你可以共享模型和驗證邏輯。
我需要修改我的現有 API 嗎? 不。你的 MCP 伺服器是一個分別的服務,調用你的現有 API。它是一個適配器層。也就是說,你可能會發現自己想要添加一些 API 端點,特別是為 AI 消費 -- 比如返回比你的 UI 需要更多上下文的搜索端點。沒關係。但它是附加的,不是修改。
我如何處理長時間運行的操作?
某些工具可能觸發需要數分鐘的操作(如生成報告或處理大型導入)。使用 MCP 的進度通知功能讓客戶端始終知道。你的工具處理程序可以在等待操作完成時發出進度更新。對於非常長的操作(>30 秒),考慮立即返回作業 ID 並提供單獨的 check_job_status 工具。
MCP 對生產足夠穩定嗎? 是的,從 2025 年中期。規範在 2025 年 3 月達到 v1.0,2025-03-26 修訂版(添加了 Streamable HTTP)是主要客戶端採用的。Anthropic、Microsoft、Google 和 OpenAI 都在投資協議。它不會消失。也就是說,請關注規範更新 -- 有關於更好的認證流和服務器到服務器通訊的活躍提案。
在 MCP 工具中處理分頁的最佳方式是什麼?
默認返回合理數量的結果(20-50 項),並接受 cursor 或 page 參數。在你的回應中包含分頁元數據,以便 AI 知道有更多結果。類似:{ results: [...], next_cursor: "abc123", total_count: 342 }。如果用戶需要更多數據,AI 將自然地要求下一頁。
一個 MCP 伺服器可以同時支持多個 AI 客戶端嗎? 是的,應該。你的 MCP 伺服器只是一個 Express 應用程式處理並發請求。每個請求都包括用戶的認證令牌,因此你將所有內容限制在正確的租戶。如果你正確使用 Streamable HTTP 傳輸,就沒有要擔心的客戶端特定狀態。像任何其他無狀態 API 伺服器一樣對待它。