合集:MCP(模型上下文协议)系列 · 中级篇(一)
前言
安装现成的 MCP Server 很简单,但真正的威力在于:你能构建任何你需要的 MCP Server,让 AI 连接到你特有的系统、数据库或内部工具。
本篇带你从零开始,用 Python 和 TypeScript 分别构建一个完整的 MCP Server,包含工具、资源和提示模板三类能力。国内使用Claud Code 访问 ccAiHub.com
一、理解 MCP Server 的生命周期
主机启动 MCP Server 进程
↓
握手(Handshake):交换能力列表
↓
主机发现 Server 提供的 Tools/Resources/Prompts
↓
用户发起请求
↓
AI 决定调用某个 Tool
↓
主机通过 JSON-RPC 调用 Server
↓
Server 执行逻辑,返回结果
↓
AI 将结果纳入推理,生成最终回复
二、Python 实战:使用 FastMCP
2.1 环境准备
# 推荐使用 uv 管理 Python 项目
pip install uv
# 创建项目
mkdir my-mcp-server && cd my-mcp-server
uv init
uv add mcp
# 或使用 pip
pip install mcp
2.2 最简单的 MCP Server
server.py:
from mcp.server.fastmcp import FastMCP
# 创建 Server 实例
mcp = FastMCP("my-first-server")
# 定义一个工具
@mcp.tool()
def hello(name: str) -> str:
"""向指定的人打招呼"""
return f"你好,{name}!来自 MCP Server 的问候。"
if __name__ == "__main__":
mcp.run()
就这样!FastMCP 会自动处理:
- JSON Schema 生成(从函数签名推断)
- 参数验证
- 错误处理
- 协议握手
2.3 完整示例:代码质量分析 Server
code_analysis_server.py:
import subprocess
import json
from pathlib import Path
from mcp.server.fastmcp import FastMCP
mcp = FastMCP("code-analysis")
# ==================== Tools ====================
@mcp.tool()
def run_linter(file_path: str, fix: bool = False) -> str:
"""
对指定文件运行 ESLint 检查
Args:
file_path: 要检查的文件路径
fix: 是否自动修复可修复的问题
"""
if not Path(file_path).exists():
return f"❌ 文件不存在:{file_path}"
cmd = ["npx", "eslint", "--format", "json"]
if fix:
cmd.append("--fix")
cmd.append(file_path)
result = subprocess.run(cmd, capture_output=True, text=True)
try:
lint_results = json.loads(result.stdout)
errors = sum(r["errorCount"] for r in lint_results)
warnings = sum(r["warningCount"] for r in lint_results)
if errors == 0 and warnings == 0:
return f"✅ {file_path} 通过检查,无问题"
issues = []
for file_result in lint_results:
for msg in file_result.get("messages", []):
severity = "❌" if msg["severity"] == 2 else "⚠️"
issues.append(
f"{severity} 第 {msg['line']} 行:{msg['message']} ({msg['ruleId']})"
)
return f"发现 {errors} 个错误,{warnings} 个警告:\n" + "\n".join(issues)
except json.JSONDecodeError:
return result.stdout or result.stderr
@mcp.tool()
def calculate_complexity(file_path: str) -> dict:
"""
计算文件的圈复杂度
Returns:
包含函数名和复杂度的字典列表
"""
if not Path(file_path).exists():
return {"error": f"文件不存在:{file_path}"}
result = subprocess.run(
["npx", "complexity-report", "--format", "json", file_path],
capture_output=True, text=True
)
try:
data = json.loads(result.stdout)
functions = []
for func in data.get("functions", []):
complexity = func.get("complexity", {}).get("cyclomatic", 0)
functions.append({
"name": func.get("name", "anonymous"),
"complexity": complexity,
"risk": "高" if complexity > 10 else "中" if complexity > 5 else "低"
})
return {"functions": functions, "file": file_path}
except:
return {"error": "无法计算复杂度", "output": result.stdout}
@mcp.tool()
def find_duplicate_code(directory: str, min_lines: int = 5) -> str:
"""
在目录中查找重复代码块
Args:
directory: 要扫描的目录
min_lines: 最小重复行数阈值
"""
result = subprocess.run(
["npx", "jscpd", "--min-lines", str(min_lines), "--output", "json", directory],
capture_output=True, text=True
)
# 处理结果...
return f"扫描完成,结果见 report/ 目录"
# ==================== Resources ====================
@mcp.resource("analysis://reports/{report_id}")
def get_analysis_report(report_id: str) -> str:
"""获取历史分析报告"""
report_path = Path(f"reports/{report_id}.json")
if not report_path.exists():
return f"报告 {report_id} 不存在"
return report_path.read_text()
@mcp.resource("analysis://config")
def get_analysis_config() -> str:
"""获取当前分析配置"""
config_path = Path(".eslintrc.json")
if config_path.exists():
return config_path.read_text()
return "{}"
# ==================== Prompts ====================
@mcp.prompt()
def code_review_prompt(file_path: str, focus: str = "全面") -> str:
"""
生成代码审查提示
Args:
file_path: 要审查的文件
focus: 审查重点(安全/性能/规范/全面)
"""
return f"""
请对 {file_path} 进行代码审查,重点关注:{focus}
审查维度:
1. 先运行 run_linter 工具检查语法和规范问题
2. 用 calculate_complexity 评估代码复杂度
3. 根据结果提出具体的改进建议
请以 Markdown 格式输出审查报告,包含:
- 总体评分(1-10)
- 主要问题列表(按优先级排序)
- 具体修改建议(带代码示例)
"""
if __name__ == "__main__":
mcp.run()
2.4 连接到 Claude Desktop
claude_desktop_config.json:
{
"mcpServers": {
"code-analysis": {
"command": "python",
"args": ["/absolute/path/to/code_analysis_server.py"]
}
}
}
三、TypeScript 实战:使用官方 SDK
3.1 环境准备
mkdir my-ts-mcp-server && cd my-ts-mcp-server
npm init -y
npm install @modelcontextprotocol/sdk
npm install -D typescript @types/node
npx tsc --init
3.2 完整示例:项目管理助手 Server
src/index.ts:
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import {
ListToolsRequestSchema,
CallToolRequestSchema,
ListResourcesRequestSchema,
ReadResourceRequestSchema,
ListPromptsRequestSchema,
GetPromptRequestSchema,
Tool,
Resource,
Prompt,
} from "@modelcontextprotocol/sdk/types.js";
import fs from "fs";
import path from "path";
// ==================== 初始化 Server ====================
const server = new Server(
{
name: "project-manager",
version: "1.0.0",
},
{
capabilities: {
tools: {},
resources: {},
prompts: {},
},
}
);
// ==================== 定义工具列表 ====================
const TOOLS: Tool[] = [
{
name: "create_task",
description: "在 tasks.json 中创建新任务",
inputSchema: {
type: "object",
properties: {
title: { type: "string", description: "任务标题" },
description: { type: "string", description: "任务描述" },
priority: {
type: "string",
enum: ["low", "medium", "high"],
description: "优先级",
default: "medium",
},
assignee: { type: "string", description: "负责人" },
},
required: ["title"],
},
},
{
name: "list_tasks",
description: "列出所有任务,支持按状态和优先级过滤",
inputSchema: {
type: "object",
properties: {
status: {
type: "string",
enum: ["todo", "in_progress", "done"],
description: "按状态过滤",
},
priority: {
type: "string",
enum: ["low", "medium", "high"],
description: "按优先级过滤",
},
},
},
},
{
name: "update_task_status",
description: "更新任务状态",
inputSchema: {
type: "object",
properties: {
task_id: { type: "string", description: "任务 ID" },
status: {
type: "string",
enum: ["todo", "in_progress", "done"],
description: "新状态",
},
},
required: ["task_id", "status"],
},
},
];
// ==================== 工具处理逻辑 ====================
const TASKS_FILE = "tasks.json";
function loadTasks(): any[] {
if (!fs.existsSync(TASKS_FILE)) return [];
return JSON.parse(fs.readFileSync(TASKS_FILE, "utf-8"));
}
function saveTasks(tasks: any[]): void {
fs.writeFileSync(TASKS_FILE, JSON.stringify(tasks, null, 2));
}
server.setRequestHandler(ListToolsRequestSchema, async () => ({ tools: TOOLS }));
server.setRequestHandler(CallToolRequestSchema, async (request) => {
const { name, arguments: args } = request.params;
switch (name) {
case "create_task": {
const tasks = loadTasks();
const newTask = {
id: `task-${Date.now()}`,
title: args.title as string,
description: args.description as string || "",
priority: args.priority as string || "medium",
assignee: args.assignee as string || "未分配",
status: "todo",
createdAt: new Date().toISOString(),
};
tasks.push(newTask);
saveTasks(tasks);
return {
content: [
{
type: "text",
text: `✅ 任务创建成功!\nID: ${newTask.id}\n标题: ${newTask.title}\n优先级: ${newTask.priority}`,
},
],
};
}
case "list_tasks": {
let tasks = loadTasks();
if (args.status) tasks = tasks.filter((t: any) => t.status === args.status);
if (args.priority) tasks = tasks.filter((t: any) => t.priority === args.priority);
if (tasks.length === 0) {
return { content: [{ type: "text", text: "没有找到符合条件的任务" }] };
}
const table = tasks
.map((t: any) => `- [${t.status}] ${t.title} (${t.priority}) - ${t.assignee}`)
.join("\n");
return { content: [{ type: "text", text: `找到 ${tasks.length} 个任务:\n${table}` }] };
}
case "update_task_status": {
const tasks = loadTasks();
const task = tasks.find((t: any) => t.id === args.task_id);
if (!task) {
return { content: [{ type: "text", text: `❌ 未找到任务:${args.task_id}` }] };
}
const oldStatus = task.status;
task.status = args.status;
task.updatedAt = new Date().toISOString();
saveTasks(tasks);
return {
content: [
{
type: "text",
text: `✅ 任务状态已更新:${task.title}\n${oldStatus} → ${args.status}`,
},
],
};
}
default:
throw new Error(`未知工具:${name}`);
}
});
// ==================== 资源处理 ====================
server.setRequestHandler(ListResourcesRequestSchema, async () => ({
resources: [
{
uri: "tasks://all",
name: "所有任务",
description: "完整的任务列表(JSON 格式)",
mimeType: "application/json",
},
{
uri: "tasks://summary",
name: "任务摘要",
description: "按状态统计的任务摘要",
mimeType: "text/plain",
},
] as Resource[],
}));
server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
const { uri } = request.params;
if (uri === "tasks://all") {
const tasks = loadTasks();
return {
contents: [
{ uri, mimeType: "application/json", text: JSON.stringify(tasks, null, 2) },
],
};
}
if (uri === "tasks://summary") {
const tasks = loadTasks();
const summary = {
todo: tasks.filter((t: any) => t.status === "todo").length,
in_progress: tasks.filter((t: any) => t.status === "in_progress").length,
done: tasks.filter((t: any) => t.status === "done").length,
total: tasks.length,
};
return {
contents: [
{
uri,
mimeType: "text/plain",
text: `任务摘要:\n待办:${summary.todo}\n进行中:${summary.in_progress}\n已完成:${summary.done}\n共计:${summary.total}`,
},
],
};
}
throw new Error(`未知资源:${uri}`);
});
// ==================== 提示模板 ====================
server.setRequestHandler(ListPromptsRequestSchema, async () => ({
prompts: [
{
name: "daily_standup",
description: "生成每日站会报告",
arguments: [
{ name: "team", description: "团队名称", required: false },
],
},
] as Prompt[],
}));
server.setRequestHandler(GetPromptRequestSchema, async (request) => {
const { name, arguments: args } = request.params;
if (name === "daily_standup") {
const team = args?.team || "我们的团队";
return {
description: "每日站会报告模板",
messages: [
{
role: "user",
content: {
type: "text",
text: `请为 ${team} 生成今日站会报告。
步骤:
1. 读取 tasks://summary 资源了解整体进度
2. 列出所有 in_progress 状态的任务
3. 列出所有 done 状态且今天更新的任务
4. 识别可能的阻塞项
输出格式:
## 昨日完成
## 今日计划
## 阻塞项(如有)`,
},
},
],
};
}
throw new Error(`未知提示模板:${name}`);
});
// ==================== 启动 ====================
async function main() {
const transport = new StdioServerTransport();
await server.connect(transport);
console.error("项目管理 MCP Server 已启动");
}
main().catch(console.error);
3.3 编译和配置
# 编译
npx tsc
# 配置到 Claude Desktop
{
"mcpServers": {
"project-manager": {
"command": "node",
"args": ["/absolute/path/to/my-ts-mcp-server/dist/index.js"]
}
}
}
四、调试技巧
4.1 MCP Inspector(官方调试工具)
# 启动调试界面
npx @modelcontextprotocol/inspector python server.py
# 或
npx @modelcontextprotocol/inspector node dist/index.js
打开 http://localhost:5173,可以:
- 查看 Server 暴露的所有工具/资源/提示
- 直接调用工具测试
- 查看请求/响应的原始 JSON
4.2 日志输出
# Python:写入 stderr(不干扰 stdio 协议)
import sys
print("Debug info", file=sys.stderr)
// TypeScript:写入 stderr
console.error("Debug info");
五、本篇小结
| 语言 | 框架 | 特点 |
|---|---|---|
| Python | FastMCP | 装饰器语法,最简洁,推荐入门 |
| TypeScript | 官方 SDK | 类型安全,生态更完善 |
| Go/Java/... | 社区 SDK | 适合已有技术栈 |
核心原则:最小化 Server 的职责——一个 Server 专注一件事,通过组合多个 Server 实现复杂能力。
下一篇深入 MCP 的三大原语,学习更高级的 Resources 订阅、Tools 注解和动态 Prompts。
系列导航
- 初级篇(三):MCP 生态地图:工具、数据库、搜索全覆盖
- 中级篇(一):动手构建 MCP Server:Python & TypeScript 实战 ← 当前
- 中级篇(二):深入三大原语:Resources、Tools 和 Prompts
- 中级篇(三):MCP + RAG:构建企业知识库问答系统
- 高级篇(一):企业级 MCP 架构:安全、认证与高可用
- 高级篇(二):MCP OAuth 2.1 实战:标准化身份认证
- 高级篇(三):MCP + 多智能体编排:下一代 AI 工作流