MCP 服务端开发实战
大约 8 分钟
MCP 服务端开发实战
环境准备
官方 SDK 一览
MCP 提供了多种语言的官方 SDK:
| 语言 | 包名 | 适合场景 |
|---|---|---|
| Python | mcp | AI/数据科学项目,快速原型 |
| TypeScript | @modelcontextprotocol/sdk | Web 开发,Node.js 生态 |
| Java | mcp-sdk-java | 企业级应用 |
| Kotlin | mcp-sdk-kotlin | Android / JVM 项目 |
| C# | ModelContextProtocol | .NET 生态 |
| Go | mcp-go | 高性能后端服务 |
| Rust | mcp-rust-sdk | 性能敏感场景 |
[!tip] 本章以 Python 和 TypeScript 为主
两种语言覆盖了最常见的开发场景。Python 适合 AI/数据领域,TypeScript 适合 Web 开发。
Python 环境搭建
# 推荐使用 uv(现代 Python 包管理器)
uv init my-mcp-server && cd my-mcp-server
uv add "mcp[cli]"
# 或使用 pip
pip install "mcp[cli]"
TypeScript 环境搭建
mkdir my-mcp-server && cd my-mcp-server
npm init -y
npm install @modelcontextprotocol/sdk zod
# TypeScript 支持
npm install -D typescript @types/node
npx tsc --init
使用 FastMCP(Python)快速开发
FastMCP 简介
FastMCP 是 Python SDK 提供的高层封装,类似于 FastAPI 之于 ASGI。它通过装饰器将普通 Python 函数自动转换为 MCP 工具/资源/提示词,极大简化了开发流程:
最简 Server
# server.py
from mcp.server.fastmcp import FastMCP
mcp = FastMCP("my-server")
@mcp.tool()
async def hello(name: str) -> str:
"""向用户打招呼"""
return f"你好,{name}!"
if __name__ == "__main__":
mcp.run() # 默认使用 stdio 传输
FastMCP 会自动完成以下工作:
- 从函数名生成 Tool 名称(
hello) - 从 docstring 生成 Tool 描述(
向用户打招呼) - 从类型注解生成输入 Schema(
name: str→{"type": "string"}) - 处理 JSON-RPC 消息编解码
- 管理连接生命周期
完整实战:文件管理 Server
下面构建一个功能完整的文件管理 MCP Server,综合运用 Tool、Resource 和 Prompt:
# file_manager_server.py
import os
import json
from pathlib import Path
from typing import Optional
from mcp.server.fastmcp import FastMCP
# 创建 Server,指定允许访问的根目录
ALLOWED_DIR = Path.home() / "Documents"
mcp = FastMCP(
"file-manager",
instructions="文件管理助手,可以读取、搜索和列出文件。",
)
def _safe_path(filepath: str) -> Path:
"""确保路径在允许的目录内,防止路径遍历攻击"""
path = (ALLOWED_DIR / filepath).resolve()
if not str(path).startswith(str(ALLOWED_DIR.resolve())):
raise ValueError(f"路径越权: {filepath}")
return path
# ==================== Tools ====================
@mcp.tool()
async def read_file(filepath: str) -> str:
"""读取文件内容
Args:
filepath: 相对于 Documents 目录的文件路径
"""
path = _safe_path(filepath)
if not path.exists():
return f"错误:文件不存在 {filepath}"
if path.is_dir():
return f"错误:{filepath} 是目录,不是文件"
try:
return path.read_text(encoding="utf-8")
except Exception as e:
return f"读取失败: {e}"
@mcp.tool()
async def list_directory(dirpath: str = ".") -> str:
"""列出目录内容
Args:
dirpath: 相对于 Documents 目录的目录路径,默认为根目录
"""
path = _safe_path(dirpath)
if not path.exists():
return f"错误:目录不存在 {dirpath}"
if not path.is_dir():
return f"错误:{dirpath} 不是目录"
items = []
for item in sorted(path.iterdir()):
prefix = "📁 " if item.is_dir() else "📄 "
size = item.stat().st_size if item.is_file() else "-"
items.append(f"{prefix}{item.name} ({size} bytes)")
return "\n".join(items) if items else "目录为空"
@mcp.tool()
async def search_files(pattern: str, dirpath: str = ".") -> str:
"""在目录中搜索文件
Args:
pattern: 文件名搜索模式(支持 * 通配符)
dirpath: 搜索的目录路径,默认为根目录
"""
path = _safe_path(dirpath)
matches = list(path.glob(f"**/{pattern}"))
if not matches:
return f"未找到匹配 '{pattern}' 的文件"
results = []
for m in matches[:20]: # 限制返回数量
rel = m.relative_to(path)
results.append(str(rel))
return "\n".join(results)
@mcp.tool()
async def write_file(filepath: str, content: str) -> str:
"""写入文件内容
Args:
filepath: 文件路径
content: 要写入的内容
"""
path = _safe_path(filepath)
path.parent.mkdir(parents=True, exist_ok=True)
path.write_text(content, encoding="utf-8")
return f"成功写入 {filepath} ({len(content)} 字符)"
# ==================== Resources ====================
@mcp.resource("config://allowed-dir")
def get_allowed_dir() -> str:
"""获取允许访问的根目录"""
return str(ALLOWED_DIR)
@mcp.resource("file://{filepath}/info")
def get_file_info(filepath: str) -> str:
"""获取文件元信息"""
path = _safe_path(filepath)
if not path.exists():
return json.dumps({"error": "文件不存在"})
stat = path.stat()
return json.dumps({
"name": path.name,
"size": stat.st_size,
"is_dir": path.is_dir(),
"modified": stat.st_mtime,
}, indent=2)
# ==================== Prompts ====================
@mcp.prompt()
def summarize_document(filepath: str) -> str:
"""文档摘要提示词 — 读取并总结文档内容"""
return f"""请阅读以下文件并生成一份简洁的摘要。
文件路径: {filepath}
要点:
1. 用 2-3 句话概括主要内容
2. 列出关键要点(不超过 5 个)
3. 标注适合的目标读者"""
@mcp.prompt()
def compare_files(file1: str, file2: str) -> str:
"""文件对比提示词 — 比较两个文件的差异"""
return f"""请对比以下两个文件的内容,指出它们之间的差异:
文件 1: {file1}
文件 2: {file2}
请从以下角度分析:
1. 内容差异
2. 结构差异
3. 各自的优缺点"""
if __name__ == "__main__":
mcp.run()
[!tip] FastMCP 自动推断
FastMCP 从你的 Python 代码自动推断 MCP 定义:
- 函数名 → Tool 名称
- docstring → Tool 描述(支持
Args:段落解析参数说明)- 类型注解 → 输入 Schema(
str→ string,int→ integer,bool→ boolean)- 默认值 → 可选参数
Optional[str]→ 可选字符串参数
使用 McpServer(TypeScript)开发
TypeScript Server 示例
// server.ts
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { z } from "zod";
import * as fs from "fs/promises";
import * as path from "path";
const ALLOWED_DIR = path.join(process.env.HOME || process.env.USERPROFILE || ".", "Documents");
// 创建 Server 实例
const server = new McpServer({
name: "file-manager",
version: "1.0.0",
});
// 辅助函数:安全路径
function safePath(filepath: string): string {
const resolved = path.resolve(ALLOWED_DIR, filepath);
if (!resolved.startsWith(ALLOWED_DIR)) {
throw new Error(`路径越权: ${filepath}`);
}
return resolved;
}
// ==================== Tools ====================
server.registerTool(
"read_file",
{
description: "读取文件内容",
inputSchema: {
filepath: z.string().describe("文件路径"),
},
},
async ({ filepath }) => {
const fullPath = safePath(filepath);
try {
const content = await fs.readFile(fullPath, "utf-8");
return { content: [{ type: "text", text: content }] };
} catch (e: any) {
return { content: [{ type: "text", text: `读取失败: ${e.message}` }] };
}
}
);
server.registerTool(
"list_directory",
{
description: "列出目录内容",
inputSchema: {
dirpath: z.string().default(".").describe("目录路径"),
},
},
async ({ dirpath }) => {
const fullPath = safePath(dirpath);
const entries = await fs.readdir(fullPath, { withFileTypes: true });
const items = entries.map((e) => {
const prefix = e.isDirectory() ? "📁 " : "📄 ";
return `${prefix}${e.name}`;
});
return { content: [{ type: "text", text: items.join("\n") || "目录为空" }] };
}
);
server.registerTool(
"search_files",
{
description: "搜索文件",
inputSchema: {
pattern: z.string().describe("文件名搜索模式"),
},
},
async ({ pattern }) => {
// 简化的搜索实现
return {
content: [{ type: "text", text: `搜索模式: ${pattern}` }],
};
}
);
// ==================== 启动 Server ====================
async function main() {
const transport = new StdioServerTransport();
await server.connect(transport);
console.error("File Manager MCP Server 已启动"); // 注意:用 stderr
}
main().catch(console.error);
测试与调试
使用 MCP Inspector
MCP Inspector 是官方提供的可视化调试工具,无需编写 Client 代码即可测试 Server:
# Python Server
mcp dev server.py
# TypeScript Server
mcp dev node server.ts
Inspector 界面提供以下功能:
配置到 Claude Desktop 测试
将你的 Server 配置到 Claude Desktop 的 claude_desktop_config.json 中:
{
"mcpServers": {
"file-manager": {
"command": "python",
"args": ["path/to/file_manager_server.py"]
}
}
}
配置完成后重启 Claude Desktop,你的工具就可以在对话中被 Claude 调用了。
安装到 Claude Desktop
使用 mcp install 命令一键安装:
# Python Server
mcp install server.py
# 指定环境变量
mcp install server.py --env API_KEY=xxx
Server 设计最佳实践
1. 工具粒度设计
| 原则 | 说明 |
|---|---|
| 单一职责 | 每个工具做一件事,LLM 更容易正确选择 |
| 清晰的描述 | 写好 description 和参数说明,LLM 靠它判断是否调用 |
| 合理的参数 | 必要的参数设为 required,有默认值的参数设为 optional |
| 错误处理 | 返回有意义的错误信息,不要抛出未捕获的异常 |
2. 安全设计
# ✅ 始终验证和清理输入
@mcp.tool()
async def read_file(filepath: str) -> str:
"""读取文件"""
path = _safe_path(filepath) # 路径安全检查
if not path.exists():
return f"文件不存在: {filepath}"
return path.read_text(encoding="utf-8")
# ❌ 危险 — 没有路径检查
@mcp.tool()
async def read_file_unsafe(filepath: str) -> str:
return Path(filepath).read_text() # 可能读取任意文件!
3. 性能考虑
| 建议 | 说明 |
|---|---|
| 限制返回大小 | Tool 返回内容不宜过大,建议限制在合理范围内 |
| 使用异步 | 所有 Tool 函数使用 async def,避免阻塞事件循环 |
| 缓存数据 | 对频繁访问的 Resource 使用缓存 |
| 分页支持 | 大量数据支持分页,避免一次返回过多 |
4. Server 元信息
FastMCP 支持 instructions 字段,为 Host 提供使用说明:
mcp = FastMCP(
"file-manager",
instructions="""这是一个文件管理助手。
允许的操作:读取、写入、搜索文件
工作目录:用户的 Documents 文件夹
限制:不能访问 Documents 目录之外的文件""",
)
这些说明会展示给 LLM,帮助它更好地理解和使用你的 Server。
打包与分发
Python Server 打包
使用 pyproject.toml 配置入口点:
[project]
name = "my-mcp-server"
version = "0.1.0"
dependencies = ["mcp[cli]"]
[project.scripts]
my-mcp-server = "my_server:main"
安装后即可直接使用命令名启动:
pip install .
my-mcp-server # 等同于 python my_server.py
npm 发布
# TypeScript Server 发布到 npm
npm publish
发布后用户可以通过 npx 直接运行:
npx my-mcp-server
总结
| 要点 | 内容 |
|---|---|
| FastMCP | Python 高层封装,装饰器驱动,自动推断定义 |
| McpServer | TypeScript SDK,使用 Zod 定义 Schema |
| 调试工具 | MCP Inspector 可视化调试,mcp dev 一键启动 |
| 设计原则 | 单一职责、清晰描述、安全验证、异步优先 |
| 打包分发 | Python 入口点 / npm 发布,支持一键安装 |