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 SDK | Python SDK(FastMCP) |
|---|---|---|
| API 风格 | McpServer 类 + 方法注册 | 装饰器风格(@mcp.tool()) |
| 类型安全 | Zod Schema 验证 | Python 类型注解自动推断 |
| 传输支持 | stdio、Streamable HTTP | stdio、Streamable HTTP、SSE |
| 适合场景 | 前端/全栈团队、Node.js 生态 | 数据科学、后端团队、快速原型 |
| 包管理 | npm / pnpm | pip / 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 选择准确性 |
inputSchema | Zod Schema 定义参数 | 为每个参数添加 .describe(),帮助 AI 理解参数含义 |
| 返回值 | content 数组 | 支持 text、image、resource 类型 |
| 错误处理 | 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.js3. 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.md3.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 messages3.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 HTTP4. 使用 MCP Inspector 测试调试
MCP Inspector 是官方提供的交互式调试工具,可以在浏览器中直观地测试 Server 的所有功能。
操作步骤
步骤 1:启动 Inspector
# 直接通过 npx 启动(无需安装)
npx @modelcontextprotocol/inspectorInspector 会在浏览器中打开(默认 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:测试工具
- 切换到 Tools 标签页
- 点击 List Tools 查看所有注册的工具
- 选择一个工具(如
text_stats) - 填入参数(如
text: "Hello World") - 点击 Run Tool 查看执行结果
步骤 5:测试资源
- 切换到 Resources 标签页
- 点击 List Resources 查看所有注册的资源
- 选择一个资源(如
config://server) - 点击 Read Resource 查看资源内容
步骤 6:测试 Prompt 模板
- 切换到 Prompts 标签页
- 点击 List Prompts 查看所有注册的模板
- 选择一个模板(如
code_review) - 填入参数
- 点击 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-server5.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 综合对比表
| 维度 | TypeScript | Python(FastMCP) |
|---|---|---|
| 代码量 | 较多(需要显式定义返回结构) | 较少(装饰器 + 类型注解自动推断) |
| 类型安全 | Zod Schema 编译时检查 | 运行时类型检查 |
| 参数描述 | z.string().describe("...") | docstring Args 部分 |
| 错误处理 | 返回 { isError: true } | 直接抛出异常 |
| 异步支持 | 原生 async/await | 支持 async def |
| 生态集成 | npm 生态(Express、Prisma 等) | pip 生态(pandas、SQLAlchemy 等) |
| 部署方式 | npx、Docker、npm publish | uv、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 开发的几个关键模式:
- 工具设计:每个工具对应一个 CRUD 操作,职责单一明确
- 资源设计:列表资源(
todos://all)+ 统计资源(todos://stats)+ 详情模板(todos://{id}) - Prompt 设计:结合资源数据动态生成 Prompt,让 AI 基于实际数据做决策
- 错误处理:对不存在的 ID 抛出明确错误,AI 会据此调整策略
避坑指南
❌ 常见错误
-
在 stdout 中输出日志或调试信息
- 问题:stdio 模式下,stdout 仅用于 JSON-RPC 消息。混入其他输出会导致 Client 解析失败,表现为”连接断开”或”无响应”
- 正确做法:所有日志输出到 stderr(TypeScript 用
console.error,Python 用logging模块或print(..., file=sys.stderr))
-
工具描述写得太简略
- 问题:AI 模型根据
description决定何时调用哪个工具。描述不清会导致工具选择错误或根本不被调用 - 正确做法:描述中写清楚”做什么”、“什么时候用”、“输入什么”。例如:
"查询 PostgreSQL 数据库,执行只读 SQL 查询并返回结果。适用于需要从数据库获取数据的场景"
- 问题:AI 模型根据
-
把所有功能塞进一个”万能工具”
- 问题:一个工具做太多事情,AI 模型难以准确选择参数组合
- 正确做法:遵循单一职责原则,每个工具只做一件事。宁可多几个小工具,也不要一个大工具
-
忽略输入验证
- 问题:AI 模型可能传入意外的参数值(如负数、超长字符串、SQL 注入)
- 正确做法:TypeScript 用 Zod 的
.min()、.max()、.regex()等验证器;Python 在函数开头做参数检查
-
资源和工具混淆使用
- 问题:把只读数据查询实现为 Tool,或把有副作用的操作实现为 Resource
- 正确做法:只读数据用 Resource(如查看配置、列出文件),有副作用的操作用 Tool(如创建文件、发送消息)
-
Prompt 模板中硬编码数据
- 问题:Prompt 模板中写死了具体数据,无法适应不同场景
- 正确做法:使用参数化模板,通过
arguments接收动态输入;需要实时数据时,在 Prompt 处理函数中读取 Resource
-
未处理异步操作的超时
- 问题:工具调用外部 API 时没有设置超时,导致 Client 长时间等待
- 正确做法:为所有外部调用设置合理的超时时间(建议 30 秒以内)
✅ 最佳实践
- 从 MCP Inspector 开始调试——先确保 Server 在 Inspector 中正常工作,再集成到 AI 客户端
- 为每个 Zod Schema 参数添加
.describe()(TypeScript)或在 docstring 中写 Args(Python),这是 AI 理解参数的唯一途径 - 工具返回结构化 JSON 而非纯文本——AI 模型更容易解析和利用结构化数据
- 使用
isError: true(TypeScript)或抛出异常(Python)明确标记错误——AI 会据此决定是否重试或换策略 - 生产环境使用 Streamable HTTP 传输,并配置 OAuth 2.1 认证(详见 08e-MCP安全最佳实践)
- 将 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 | 协议规范 | 资源暴露和读取的完整协议规范 |
参考来源
- MCP 官方文档 - Servers 概念 (2025-11)
- MCP 官方规范 2025-11-25 - Tools (2025-11-25)
- MCP 官方规范 2025-11-25 - Resources (2025-11-25)
- MCP 官方规范 2025-06-18 - Prompts (2025-06-18)
- MCP TypeScript SDK - npm @modelcontextprotocol/sdk (2025-09)
- MCP Python SDK - GitHub (2025-11)
- How to Build an MCP Server with TypeScript - Thomas Wiegold (2026-01)
- Build Your First MCP Server with TypeScript - Noqta (2026-02)
- How to Build Your First MCP Server using FastMCP - freeCodeCamp (2025-12)
- Custom MCP Server Development Guide - Harish Garg (2025-01)
- Building and Deploying a Python MCP Server with FastMCP - CircleCI (2025-09)
- Testing MCP Servers with MCP Inspector - Chris Ebert (2026-01)
📖 返回 总览与导航 | 上一节:08b-MCP深度架构解析 | 下一节:08d-MCP工具模式目录