Skip to Content

08c - 自定义 MCP Server 开发

本文是《AI Agent 实战手册》第 8 章第 3 节。 上一节:08b-MCP深度架构解析 | 下一节:08d-MCP工具模式目录

概述

上一节深入拆解了 MCP 的协议架构。本节将手把手带你从零构建自定义 MCP Server——分别使用 TypeScript 和 Python 两种语言。你将学会定义工具(Tools)、暴露资源(Resources)、创建提示词模板(Prompts),并使用 MCP Inspector 进行调试测试。完成本节后,你将具备为任何业务场景构建 MCP Server 的能力。


1. 开发工具与 SDK 概览

工具推荐

工具用途价格适用场景
@modelcontextprotocol/sdk 官方 TypeScript SDK免费开源TypeScript/Node.js Server 开发
mcp Python SDK 官方 Python SDK(内置 FastMCP)免费开源Python Server 开发
MCP Inspector 交互式 MCP Server 调试工具免费开源开发调试、功能验证
Zod TypeScript Schema 验证库免费开源TypeScript 工具参数定义
Claude Desktop AI 助手客户端免费 / Pro $20/月集成测试 MCP Server
Cursor AI IDE免费 / Pro $20/月集成测试 MCP Server
uv Python 包管理器免费开源Python 项目管理和依赖安装

SDK 选择指南

维度TypeScript SDKPython SDK(FastMCP)
API 风格McpServer 类 + 方法注册装饰器风格(@mcp.tool()
类型安全Zod Schema 验证Python 类型注解自动推断
传输支持stdio、Streamable HTTPstdio、Streamable HTTP、SSE
适合场景前端/全栈团队、Node.js 生态数据科学、后端团队、快速原型
包管理npm / pnpmpip / uv

2. TypeScript 从零构建教程

2.1 项目初始化

步骤 1:创建项目并安装依赖

# 创建项目目录 mkdir my-mcp-server && cd my-mcp-server # 初始化 Node.js 项目 npm init -y # 安装 MCP SDK 和 Zod(Schema 验证) npm install @modelcontextprotocol/sdk zod # 安装开发依赖 npm install -D typescript @types/node

步骤 2:配置 TypeScript

创建 tsconfig.json

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

步骤 3:配置 package.json

{ "name": "my-mcp-server", "version": "1.0.0", "type": "module", "bin": { "my-mcp-server": "./dist/index.js" }, "scripts": { "build": "tsc", "start": "node dist/index.js", "dev": "tsc --watch" } }

2.2 创建基础 Server

TypeScript SDK 提供了高层 McpServer 类,简化了 Server 开发。创建 src/index.ts

#!/usr/bin/env node import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; // 创建 MCP Server 实例 const server = new McpServer({ name: "my-mcp-server", // Server 名称(用于能力协商) version: "1.0.0", // Server 版本 description: "一个示例 MCP Server,展示工具、资源和 Prompt 模板" }); // 使用 stdio 传输启动 Server async function main() { const transport = new StdioServerTransport(); await server.connect(transport); console.error("MCP Server 已启动(stdio 模式)"); // 日志输出到 stderr } main().catch(console.error);

⚠️ 重要:stdio 模式下,stdout 仅用于 JSON-RPC 消息。所有日志必须输出到 stderr(使用 console.error)。

2.3 定义工具(Tools)

工具是 MCP Server 最核心的能力——让 AI 模型能够执行操作。使用 server.tool() 方法注册工具:

import { z } from "zod"; // ========== 工具 1:字符串处理工具 ========== server.tool( "text_stats", // 工具名称 "分析文本的统计信息(字数、字符数、行数)", // 工具描述(AI 模型据此选择工具) { // 使用 Zod 定义输入参数 Schema text: z.string().describe("要分析的文本内容"), include_whitespace: z.boolean() .optional() .default(false) .describe("是否在字符计数中包含空白字符") }, async ({ text, include_whitespace }) => { // 工具执行逻辑 const lines = text.split("\n").length; const words = text.split(/\s+/).filter(w => w.length > 0).length; const chars = include_whitespace ? text.length : text.replace(/\s/g, "").length; return { content: [ { type: "text" as const, text: JSON.stringify({ lines, words, chars }, null, 2) } ] }; } ); // ========== 工具 2:带外部 API 调用的工具 ========== server.tool( "get_weather", "获取指定城市的当前天气信息", { city: z.string().describe("城市名称(如:北京、上海、Tokyo)"), unit: z.enum(["celsius", "fahrenheit"]) .optional() .default("celsius") .describe("温度单位") }, async ({ city, unit }) => { try { // 实际项目中调用真实天气 API // 这里用模拟数据演示 const weatherData = { city, temperature: unit === "celsius" ? 25 : 77, unit, condition: "晴", humidity: 45, wind_speed: "12 km/h" }; return { content: [ { type: "text" as const, text: JSON.stringify(weatherData, null, 2) } ] }; } catch (error) { return { content: [ { type: "text" as const, text: `获取天气信息失败: ${error instanceof Error ? error.message : "未知错误"}` } ], isError: true // 标记为错误结果 }; } } ); // ========== 工具 3:带结构化输出的工具 ========== server.tool( "generate_uuid", "生成指定数量的 UUID v4", { count: z.number() .int() .min(1) .max(100) .default(1) .describe("要生成的 UUID 数量(1-100)") }, async ({ count }) => { const uuids = Array.from({ length: count }, () => crypto.randomUUID()); return { content: [ { type: "text" as const, text: JSON.stringify({ uuids, count: uuids.length }, null, 2) } ] }; } );

工具定义要点

要素说明最佳实践
name工具唯一标识符使用 snake_case,简洁明确
description工具功能描述写清楚”做什么”和”什么时候用”,直接影响 AI 选择准确性
inputSchemaZod Schema 定义参数为每个参数添加 .describe(),帮助 AI 理解参数含义
返回值content 数组支持 textimageresource 类型
错误处理isError: true工具执行失败时设置,AI 会据此调整策略

2.4 暴露资源(Resources)

资源是 Server 提供的只读数据,AI 模型可以浏览和引用。使用 server.resource() 注册:

// ========== 静态资源:固定 URI ========== server.resource( "server-config", // 资源名称 "config://server", // 资源 URI { description: "当前 Server 的配置信息", // 资源描述 mimeType: "application/json" // MIME 类型 }, async (uri) => { const config = { name: "my-mcp-server", version: "1.0.0", uptime: process.uptime(), nodeVersion: process.version, platform: process.platform, tools: ["text_stats", "get_weather", "generate_uuid"] }; return { contents: [ { uri: uri.href, mimeType: "application/json", text: JSON.stringify(config, null, 2) } ] }; } ); // ========== 动态资源模板:URI 中包含参数 ========== import { ResourceTemplate } from "@modelcontextprotocol/sdk/server/mcp.js"; server.resource( "user-profile", new ResourceTemplate("users://{userId}/profile", { list: async () => { // 返回可用的资源实例列表(供 AI 浏览) const userIds = ["user-001", "user-002", "user-003"]; return { resources: userIds.map(id => ({ uri: `users://${id}/profile`, name: `用户 ${id} 的资料`, description: `查看用户 ${id} 的详细资料` })) }; } }), { description: "用户资料信息", mimeType: "application/json" }, async (uri, { userId }) => { // 实际项目中从数据库查询 const profiles: Record<string, object> = { "user-001": { name: "张三", role: "开发者", team: "后端" }, "user-002": { name: "李四", role: "设计师", team: "产品" }, "user-003": { name: "王五", role: "运维", team: "基础设施" } }; const profile = profiles[userId as string]; if (!profile) { throw new Error(`用户 ${userId} 不存在`); } return { contents: [ { uri: uri.href, mimeType: "application/json", text: JSON.stringify(profile, null, 2) } ] }; } );

资源类型对比

类型URI 格式适用场景示例
静态资源固定 URI(如 config://server配置信息、系统状态、文档服务器配置、API 文档
动态资源模板含参数的 URI(如 users://{id}/profile按 ID 查询的数据用户资料、订单详情

2.5 创建提示词模板(Prompts)

Prompt 模板让 Server 提供标准化的交互模式,用户可以从模板列表中选择并填入参数:

// ========== Prompt 模板 1:代码审查 ========== server.prompt( "code_review", // 模板名称 "对代码进行专业审查,提供改进建议", // 模板描述 { // 模板参数 code: z.string().describe("要审查的代码"), language: z.string() .optional() .default("auto") .describe("编程语言(auto 表示自动检测)"), focus: z.enum(["security", "performance", "readability", "all"]) .optional() .default("all") .describe("审查重点") }, async ({ code, language, focus }) => { const focusMap: Record<string, string> = { security: "安全漏洞(SQL 注入、XSS、认证绕过等)", performance: "性能问题(时间复杂度、内存泄漏、N+1 查询等)", readability: "可读性(命名规范、代码结构、注释质量等)", all: "安全性、性能和可读性" }; return { messages: [ { role: "user" as const, content: { type: "text" as const, text: [ `请对以下${language !== "auto" ? ` ${language} ` : ""}代码进行专业审查。`, ``, `## 审查重点`, `${focusMap[focus ?? "all"]}`, ``, `## 代码`, "```" + (language !== "auto" ? language : ""), code, "```", ``, `## 请按以下格式输出`, `1. **总体评价**:代码质量评分(1-10)和一句话总结`, `2. **问题列表**:按严重程度排序,每个问题包含位置、描述、修复建议`, `3. **改进建议**:3-5 条具体的改进建议`, `4. **优点**:代码中做得好的地方` ].join("\n") } } ] }; } ); // ========== Prompt 模板 2:SQL 生成 ========== server.prompt( "generate_sql", "根据自然语言描述生成 SQL 查询", { description: z.string().describe("用自然语言描述你想查询的数据"), dialect: z.enum(["postgresql", "mysql", "sqlite"]) .optional() .default("postgresql") .describe("SQL 方言"), tables: z.string() .optional() .describe("相关表结构描述(可选,提供后生成更准确)") }, async ({ description, dialect, tables }) => { const messages: Array<{ role: "user" | "assistant"; content: { type: "text"; text: string }; }> = []; // 系统上下文(通过 assistant 消息提供) messages.push({ role: "assistant" as const, content: { type: "text" as const, text: `我是一个 SQL 专家,擅长 ${dialect?.toUpperCase()} 方言。我会根据你的描述生成安全、高效的 SQL 查询。` } }); // 用户请求 let userText = `请根据以下描述生成 ${dialect?.toUpperCase()} 查询:\n\n${description}`; if (tables) { userText += `\n\n相关表结构:\n${tables}`; } userText += "\n\n请提供:\n1. SQL 查询语句\n2. 查询说明\n3. 性能优化建议(如需要)"; messages.push({ role: "user" as const, content: { type: "text" as const, text: userText } }); return { messages }; } );

2.6 完整项目结构

my-mcp-server/ ├── package.json ├── tsconfig.json ├── src/ │ └── index.ts # 入口文件(包含 Server、Tools、Resources、Prompts) └── dist/ # 编译输出 └── index.js

构建和运行

# 编译 TypeScript npm run build # 直接运行测试 node dist/index.js

3. Python 从零构建教程(FastMCP)

Python SDK 内置了 FastMCP 高层框架,使用装饰器风格定义工具、资源和 Prompt,开发体验非常 Pythonic。

3.1 项目初始化

步骤 1:创建项目并安装依赖

方式 A:使用 uv(推荐)

# 创建项目 uv init my-mcp-server cd my-mcp-server # 添加 MCP SDK 依赖 uv add "mcp[cli]"

方式 B:使用 pip

# 创建虚拟环境 python -m venv venv source venv/bin/activate # Windows: venv\Scripts\activate # 安装 MCP SDK pip install "mcp[cli]"

步骤 2:创建项目结构

my-mcp-server/ ├── pyproject.toml ├── server.py # 主入口文件 └── README.md

3.2 创建基础 Server

创建 server.py

#!/usr/bin/env python3 """一个示例 MCP Server,展示工具、资源和 Prompt 模板。""" from mcp.server.fastmcp import FastMCP # 创建 FastMCP Server 实例 mcp = FastMCP( name="my-mcp-server", version="1.0.0", description="一个示例 MCP Server,展示工具、资源和 Prompt 模板" ) if __name__ == "__main__": mcp.run() # 默认使用 stdio 传输

💡 FastMCP 的核心理念:用 Python 类型注解自动生成 JSON Schema。你只需要写标准的 Python 函数签名和 docstring,SDK 自动处理参数验证和 Schema 生成。

3.3 定义工具(Tools)

使用 @mcp.tool() 装饰器注册工具:

import json import uuid from datetime import datetime, timezone @mcp.tool() def text_stats(text: str, include_whitespace: bool = False) -> str: """分析文本的统计信息(字数、字符数、行数)。 Args: text: 要分析的文本内容 include_whitespace: 是否在字符计数中包含空白字符 """ lines = len(text.split("\n")) words = len(text.split()) chars = len(text) if include_whitespace else len(text.replace(" ", "").replace("\n", "").replace("\t", "")) return json.dumps({"lines": lines, "words": words, "chars": chars}, indent=2) @mcp.tool() def get_weather(city: str, unit: str = "celsius") -> str: """获取指定城市的当前天气信息。 Args: city: 城市名称(如:北京、上海、Tokyo) unit: 温度单位,可选 celsius 或 fahrenheit """ # 实际项目中调用真实天气 API weather_data = { "city": city, "temperature": 25 if unit == "celsius" else 77, "unit": unit, "condition": "晴", "humidity": 45, "wind_speed": "12 km/h", "queried_at": datetime.now(timezone.utc).isoformat() } return json.dumps(weather_data, indent=2, ensure_ascii=False) @mcp.tool() def generate_uuid(count: int = 1) -> str: """生成指定数量的 UUID v4。 Args: count: 要生成的 UUID 数量(1-100) """ if not 1 <= count <= 100: raise ValueError("count 必须在 1 到 100 之间") uuids = [str(uuid.uuid4()) for _ in range(count)] return json.dumps({"uuids": uuids, "count": len(uuids)}, indent=2)

FastMCP 工具定义要点

要素说明FastMCP 处理方式
工具名称默认使用函数名可通过 @mcp.tool(name="custom_name") 自定义
工具描述从 docstring 第一行提取写清楚功能和使用场景
参数 Schema从类型注解自动生成str→string, int→integer, bool→boolean
参数描述从 docstring 的 Args 部分提取使用 Google 风格 docstring
默认值从函数签名提取有默认值的参数自动标记为 optional
错误处理抛出异常即可FastMCP 自动捕获并返回错误响应

3.4 暴露资源(Resources)

使用 @mcp.resource() 装饰器注册资源:

import platform @mcp.resource("config://server") def get_server_config() -> str: """当前 Server 的配置信息。""" config = { "name": "my-mcp-server", "version": "1.0.0", "python_version": platform.python_version(), "platform": platform.system(), "tools": ["text_stats", "get_weather", "generate_uuid"] } return json.dumps(config, indent=2) # ========== 动态资源模板 ========== @mcp.resource("users://{user_id}/profile") def get_user_profile(user_id: str) -> str: """用户资料信息。 Args: user_id: 用户 ID """ profiles = { "user-001": {"name": "张三", "role": "开发者", "team": "后端"}, "user-002": {"name": "李四", "role": "设计师", "team": "产品"}, "user-003": {"name": "王五", "role": "运维", "team": "基础设施"}, } profile = profiles.get(user_id) if not profile: raise ValueError(f"用户 {user_id} 不存在") return json.dumps(profile, indent=2, ensure_ascii=False)

3.5 创建提示词模板(Prompts)

使用 @mcp.prompt() 装饰器注册 Prompt 模板:

from mcp.server.fastmcp.prompts import base @mcp.prompt() def code_review( code: str, language: str = "auto", focus: str = "all" ) -> list[base.Message]: """对代码进行专业审查,提供改进建议。 Args: code: 要审查的代码 language: 编程语言(auto 表示自动检测) focus: 审查重点(security/performance/readability/all) """ focus_map = { "security": "安全漏洞(SQL 注入、XSS、认证绕过等)", "performance": "性能问题(时间复杂度、内存泄漏、N+1 查询等)", "readability": "可读性(命名规范、代码结构、注释质量等)", "all": "安全性、性能和可读性", } lang_hint = f" {language} " if language != "auto" else "" code_block = f"```{language if language != 'auto' else ''}\n{code}\n```" prompt_text = f"""请对以下{lang_hint}代码进行专业审查。 ## 审查重点 {focus_map.get(focus, focus_map["all"])} ## 代码 {code_block} ## 请按以下格式输出 1. **总体评价**:代码质量评分(1-10)和一句话总结 2. **问题列表**:按严重程度排序,每个问题包含位置、描述、修复建议 3. **改进建议**:3-5 条具体的改进建议 4. **优点**:代码中做得好的地方""" return [base.UserMessage(content=prompt_text)] @mcp.prompt() def generate_sql( description: str, dialect: str = "postgresql", tables: str = "" ) -> list[base.Message]: """根据自然语言描述生成 SQL 查询。 Args: description: 用自然语言描述你想查询的数据 dialect: SQL 方言(postgresql/mysql/sqlite) tables: 相关表结构描述(可选) """ messages: list[base.Message] = [ base.AssistantMessage( content=f"我是一个 SQL 专家,擅长 {dialect.upper()} 方言。" "我会根据你的描述生成安全、高效的 SQL 查询。" ) ] user_text = f"请根据以下描述生成 {dialect.upper()} 查询:\n\n{description}" if tables: user_text += f"\n\n相关表结构:\n{tables}" user_text += "\n\n请提供:\n1. SQL 查询语句\n2. 查询说明\n3. 性能优化建议(如需要)" messages.append(base.UserMessage(content=user_text)) return messages

3.6 完整 server.py

将上述所有代码组合到一个文件中:

#!/usr/bin/env python3 """一个完整的示例 MCP Server。""" import json import uuid import platform from datetime import datetime, timezone from mcp.server.fastmcp import FastMCP from mcp.server.fastmcp.prompts import base # 创建 Server mcp = FastMCP( name="my-mcp-server", version="1.0.0", description="一个示例 MCP Server,展示工具、资源和 Prompt 模板" ) # --- Tools --- @mcp.tool() def text_stats(text: str, include_whitespace: bool = False) -> str: """分析文本的统计信息。""" lines = len(text.split("\n")) words = len(text.split()) chars = len(text) if include_whitespace else len(text.replace(" ", "")) return json.dumps({"lines": lines, "words": words, "chars": chars}, indent=2) @mcp.tool() def get_weather(city: str, unit: str = "celsius") -> str: """获取指定城市的当前天气信息。""" data = {"city": city, "temperature": 25, "condition": "晴", "unit": unit} return json.dumps(data, indent=2, ensure_ascii=False) @mcp.tool() def generate_uuid(count: int = 1) -> str: """生成指定数量的 UUID v4。""" uuids = [str(uuid.uuid4()) for _ in range(count)] return json.dumps({"uuids": uuids}, indent=2) # --- Resources --- @mcp.resource("config://server") def get_server_config() -> str: """Server 配置信息。""" return json.dumps({"name": "my-mcp-server", "version": "1.0.0"}, indent=2) @mcp.resource("users://{user_id}/profile") def get_user_profile(user_id: str) -> str: """用户资料。""" profiles = {"user-001": {"name": "张三"}, "user-002": {"name": "李四"}} return json.dumps(profiles.get(user_id, {"error": "未找到"}), ensure_ascii=False) # --- Prompts --- @mcp.prompt() def code_review(code: str, language: str = "auto") -> list[base.Message]: """代码审查模板。""" return [base.UserMessage(content=f"请审查以下代码:\n```{language}\n{code}\n```")] # 启动 if __name__ == "__main__": mcp.run()

运行方式

# 方式 1:直接运行 python server.py # 方式 2:使用 uv 运行 uv run server.py # 方式 3:指定传输方式 python server.py --transport stdio # stdio(默认) python server.py --transport sse # SSE(旧版兼容) python server.py --transport streamable-http # Streamable HTTP

4. 使用 MCP Inspector 测试调试

MCP Inspector 是官方提供的交互式调试工具,可以在浏览器中直观地测试 Server 的所有功能。

操作步骤

步骤 1:启动 Inspector

# 直接通过 npx 启动(无需安装) npx @modelcontextprotocol/inspector

Inspector 会在浏览器中打开(默认 http://localhost:6274)。

步骤 2:连接 TypeScript Server

在 Inspector 界面中:

  • Transport Type:选择 STDIO
  • Command:输入 node
  • Arguments:输入 dist/index.js(你的编译后文件路径)
  • 点击 Connect

步骤 3:连接 Python Server

在 Inspector 界面中:

  • Transport Type:选择 STDIO
  • Command:输入 python(或 uv run
  • Arguments:输入 server.py
  • 点击 Connect

步骤 4:测试工具

  1. 切换到 Tools 标签页
  2. 点击 List Tools 查看所有注册的工具
  3. 选择一个工具(如 text_stats
  4. 填入参数(如 text: "Hello World"
  5. 点击 Run Tool 查看执行结果

步骤 5:测试资源

  1. 切换到 Resources 标签页
  2. 点击 List Resources 查看所有注册的资源
  3. 选择一个资源(如 config://server
  4. 点击 Read Resource 查看资源内容

步骤 6:测试 Prompt 模板

  1. 切换到 Prompts 标签页
  2. 点击 List Prompts 查看所有注册的模板
  3. 选择一个模板(如 code_review
  4. 填入参数
  5. 点击 Get Prompt 查看生成的消息

提示词模板

使用 Inspector 排查问题

我的 MCP Server 在 Inspector 中连接失败。请帮我排查: 环境信息: - 语言:[TypeScript / Python] - 运行命令:[具体命令] - 错误信息:[Inspector 显示的错误] 请检查: 1. Server 是否正确输出 JSON-RPC 消息到 stdout 2. 是否有日志或调试信息混入 stdout 3. 依赖是否正确安装 4. 传输方式配置是否正确

5. 集成到 AI 客户端

Server 开发完成后,需要配置到 AI 客户端中使用。

5.1 Claude Desktop 配置

编辑配置文件(macOS:~/Library/Application Support/Claude/claude_desktop_config.json):

TypeScript Server:

{ "mcpServers": { "my-mcp-server": { "command": "node", "args": ["/absolute/path/to/my-mcp-server/dist/index.js"] } } }

Python Server:

{ "mcpServers": { "my-mcp-server": { "command": "python", "args": ["/absolute/path/to/my-mcp-server/server.py"] } } }

使用 uv 运行 Python Server(推荐):

{ "mcpServers": { "my-mcp-server": { "command": "uv", "args": ["run", "--directory", "/absolute/path/to/my-mcp-server", "server.py"] } } }

5.2 Claude Code 配置

# 添加 MCP Server(TypeScript) claude mcp add my-mcp-server node /absolute/path/to/dist/index.js # 添加 MCP Server(Python) claude mcp add my-mcp-server python /absolute/path/to/server.py # 查看已配置的 Server claude mcp list # 移除 Server claude mcp remove my-mcp-server

5.3 Cursor / VS Code 配置

在项目根目录创建 .cursor/mcp.json(Cursor)或 .vscode/mcp.json(VS Code):

{ "servers": { "my-mcp-server": { "command": "node", "args": ["./dist/index.js"], "cwd": "/absolute/path/to/my-mcp-server" } } }

5.4 环境变量传递

如果 Server 需要 API Key 等敏感信息:

{ "mcpServers": { "my-mcp-server": { "command": "node", "args": ["/path/to/dist/index.js"], "env": { "API_KEY": "your-api-key-here", "DATABASE_URL": "postgresql://localhost:5432/mydb" } } } }

在 Server 代码中通过 process.env.API_KEY(TypeScript)或 os.environ["API_KEY"](Python)读取。


6. TypeScript vs Python 对比速查

以下对比帮助你根据团队技术栈快速选择:

6.1 工具定义对比

TypeScript:

server.tool( "greet", "向用户打招呼", { name: z.string().describe("用户名") }, async ({ name }) => ({ content: [{ type: "text" as const, text: `你好,${name}!` }] }) );

Python:

@mcp.tool() def greet(name: str) -> str: """向用户打招呼。 Args: name: 用户名 """ return f"你好,{name}!"

6.2 资源定义对比

TypeScript:

server.resource( "status", "status://server", { description: "服务器状态" }, async (uri) => ({ contents: [{ uri: uri.href, mimeType: "text/plain", text: "running" }] }) );

Python:

@mcp.resource("status://server") def get_status() -> str: """服务器状态。""" return "running"

6.3 Prompt 模板对比

TypeScript:

server.prompt( "summarize", "总结文本", { text: z.string().describe("要总结的文本") }, async ({ text }) => ({ messages: [{ role: "user" as const, content: { type: "text" as const, text: `请总结:\n${text}` } }] }) );

Python:

@mcp.prompt() def summarize(text: str) -> list[base.Message]: """总结文本。""" return [base.UserMessage(content=f"请总结:\n{text}")]

6.4 综合对比表

维度TypeScriptPython(FastMCP)
代码量较多(需要显式定义返回结构)较少(装饰器 + 类型注解自动推断)
类型安全Zod Schema 编译时检查运行时类型检查
参数描述z.string().describe("...")docstring Args 部分
错误处理返回 { isError: true }直接抛出异常
异步支持原生 async/await支持 async def
生态集成npm 生态(Express、Prisma 等)pip 生态(pandas、SQLAlchemy 等)
部署方式npx、Docker、npm publishuv、Docker、pip install

实战案例:构建一个项目管理 MCP Server

以下是一个更贴近真实场景的案例——构建一个项目管理 MCP Server,让 AI 助手能够管理待办事项。

Python 实现(FastMCP)

#!/usr/bin/env python3 """项目管理 MCP Server:管理待办事项。""" import json from datetime import datetime, timezone from dataclasses import dataclass, asdict from enum import Enum from mcp.server.fastmcp import FastMCP from mcp.server.fastmcp.prompts import base mcp = FastMCP(name="todo-server", version="1.0.0") class Priority(str, Enum): LOW = "low" MEDIUM = "medium" HIGH = "high" @dataclass class Todo: id: str title: str description: str = "" priority: str = "medium" completed: bool = False created_at: str = "" def __post_init__(self): if not self.created_at: self.created_at = datetime.now(timezone.utc).isoformat() # 内存存储(生产环境应使用数据库) todos: dict[str, Todo] = {} _counter = 0 def _next_id() -> str: global _counter _counter += 1 return f"todo-{_counter:04d}" # ===== Tools ===== @mcp.tool() def add_todo(title: str, description: str = "", priority: str = "medium") -> str: """创建一个新的待办事项。 Args: title: 待办事项标题 description: 详细描述(可选) priority: 优先级(low/medium/high) """ todo_id = _next_id() todo = Todo(id=todo_id, title=title, description=description, priority=priority) todos[todo_id] = todo return json.dumps({"message": f"已创建待办事项", "todo": asdict(todo)}, indent=2, ensure_ascii=False) @mcp.tool() def complete_todo(todo_id: str) -> str: """将待办事项标记为已完成。 Args: todo_id: 待办事项 ID """ if todo_id not in todos: raise ValueError(f"待办事项 {todo_id} 不存在") todos[todo_id].completed = True return json.dumps({"message": "已标记完成", "todo": asdict(todos[todo_id])}, indent=2, ensure_ascii=False) @mcp.tool() def delete_todo(todo_id: str) -> str: """删除一个待办事项。 Args: todo_id: 待办事项 ID """ if todo_id not in todos: raise ValueError(f"待办事项 {todo_id} 不存在") deleted = todos.pop(todo_id) return json.dumps({"message": "已删除", "deleted": asdict(deleted)}, indent=2, ensure_ascii=False) # ===== Resources ===== @mcp.resource("todos://all") def list_all_todos() -> str: """所有待办事项列表。""" return json.dumps( [asdict(t) for t in todos.values()], indent=2, ensure_ascii=False ) @mcp.resource("todos://stats") def todo_stats() -> str: """待办事项统计信息。""" total = len(todos) completed = sum(1 for t in todos.values() if t.completed) by_priority = {} for t in todos.values(): by_priority[t.priority] = by_priority.get(t.priority, 0) + 1 return json.dumps({ "total": total, "completed": completed, "pending": total - completed, "by_priority": by_priority }, indent=2) @mcp.resource("todos://{todo_id}") def get_todo(todo_id: str) -> str: """单个待办事项详情。""" if todo_id not in todos: raise ValueError(f"待办事项 {todo_id} 不存在") return json.dumps(asdict(todos[todo_id]), indent=2, ensure_ascii=False) # ===== Prompts ===== @mcp.prompt() def plan_day(tasks_context: str = "") -> list[base.Message]: """根据当前待办事项规划今日工作。 Args: tasks_context: 额外的上下文信息(如会议安排、截止日期等) """ todo_list = [asdict(t) for t in todos.values() if not t.completed] prompt = f"""请根据以下待办事项帮我规划今天的工作: ## 未完成的待办事项 {json.dumps(todo_list, indent=2, ensure_ascii=False)} {f"## 额外上下文{chr(10)}{tasks_context}" if tasks_context else ""} 请按以下格式输出: 1. **今日重点**:最重要的 3 件事 2. **时间安排**:按时间段分配任务 3. **建议**:提高效率的建议""" return [base.UserMessage(content=prompt)] if __name__ == "__main__": mcp.run()

案例分析

这个案例展示了 MCP Server 开发的几个关键模式:

  1. 工具设计:每个工具对应一个 CRUD 操作,职责单一明确
  2. 资源设计:列表资源(todos://all)+ 统计资源(todos://stats)+ 详情模板(todos://{id}
  3. Prompt 设计:结合资源数据动态生成 Prompt,让 AI 基于实际数据做决策
  4. 错误处理:对不存在的 ID 抛出明确错误,AI 会据此调整策略

避坑指南

❌ 常见错误

  1. 在 stdout 中输出日志或调试信息

    • 问题:stdio 模式下,stdout 仅用于 JSON-RPC 消息。混入其他输出会导致 Client 解析失败,表现为”连接断开”或”无响应”
    • 正确做法:所有日志输出到 stderr(TypeScript 用 console.error,Python 用 logging 模块或 print(..., file=sys.stderr)
  2. 工具描述写得太简略

    • 问题:AI 模型根据 description 决定何时调用哪个工具。描述不清会导致工具选择错误或根本不被调用
    • 正确做法:描述中写清楚”做什么”、“什么时候用”、“输入什么”。例如:"查询 PostgreSQL 数据库,执行只读 SQL 查询并返回结果。适用于需要从数据库获取数据的场景"
  3. 把所有功能塞进一个”万能工具”

    • 问题:一个工具做太多事情,AI 模型难以准确选择参数组合
    • 正确做法:遵循单一职责原则,每个工具只做一件事。宁可多几个小工具,也不要一个大工具
  4. 忽略输入验证

    • 问题:AI 模型可能传入意外的参数值(如负数、超长字符串、SQL 注入)
    • 正确做法:TypeScript 用 Zod 的 .min().max().regex() 等验证器;Python 在函数开头做参数检查
  5. 资源和工具混淆使用

    • 问题:把只读数据查询实现为 Tool,或把有副作用的操作实现为 Resource
    • 正确做法:只读数据用 Resource(如查看配置、列出文件),有副作用的操作用 Tool(如创建文件、发送消息)
  6. Prompt 模板中硬编码数据

    • 问题:Prompt 模板中写死了具体数据,无法适应不同场景
    • 正确做法:使用参数化模板,通过 arguments 接收动态输入;需要实时数据时,在 Prompt 处理函数中读取 Resource
  7. 未处理异步操作的超时

    • 问题:工具调用外部 API 时没有设置超时,导致 Client 长时间等待
    • 正确做法:为所有外部调用设置合理的超时时间(建议 30 秒以内)

✅ 最佳实践

  1. 从 MCP Inspector 开始调试——先确保 Server 在 Inspector 中正常工作,再集成到 AI 客户端
  2. 为每个 Zod Schema 参数添加 .describe()(TypeScript)或在 docstring 中写 Args(Python),这是 AI 理解参数的唯一途径
  3. 工具返回结构化 JSON 而非纯文本——AI 模型更容易解析和利用结构化数据
  4. 使用 isError: true(TypeScript)或抛出异常(Python)明确标记错误——AI 会据此决定是否重试或换策略
  5. 生产环境使用 Streamable HTTP 传输,并配置 OAuth 2.1 认证(详见 08e-MCP安全最佳实践
  6. 将 Server 发布为 npm 包或 Python 包,方便团队共享和版本管理

相关资源与延伸阅读

资源类型说明
MCP 官方文档 - Server 开发指南 官方文档Server 开发的权威参考,覆盖所有原语和传输方式
MCP TypeScript SDK GitHub 开发工具官方 TypeScript SDK 源码、示例和 API 文档
MCP Python SDK GitHub 开发工具官方 Python SDK 源码,内置 FastMCP 框架
MCP Inspector 调试工具官方交互式 MCP Server 调试工具
MCP Servers 官方仓库 参考实现官方维护的 MCP Server 集合,可作为开发参考
Build Your First MCP Server with TypeScript - Noqta 教程TypeScript MCP Server 从零构建的详细教程
How to Build Your First MCP Server using FastMCP - freeCodeCamp 教程Python FastMCP 入门教程,覆盖工具、资源和 Prompt
Custom MCP Server Development Guide - Harish Garg 教程从设置到部署的完整 MCP Server 开发指南
MCP 官方规范 - Tools 协议规范工具定义和调用的完整协议规范
MCP 官方规范 - Resources 协议规范资源暴露和读取的完整协议规范

参考来源


📖 返回 总览与导航 | 上一节:08b-MCP深度架构解析 | 下一节:08d-MCP工具模式目录

Last updated on