3.1 第一个 MCP Server——Python/TypeScript 快速上手
上一节你学了 MCP 的通信协议。现在概念够了,小周要动手了。他的目标:写一个能跑的 MCP Server,在 Claude Desktop 中用起来。
选择你的武器
本节提供 Python 和 TypeScript 两个版本。选你熟悉的那个即可。
pip install mcpnpm install @modelcontextprotocol/sdk zod目标:Todo Server
小周要写一个简单的 Todo 管理 Server,暴露一个工具:
| 工具 | 功能 | 参数 |
|---|---|---|
add-todo | 添加待办事项 | text(必填):待办内容 |
list-todos | 列出所有待办 | 无 |
Python 版本
# server.py
from mcp.server.fastmcp import FastMCP
mcp = FastMCP("todo-server")
# 内存存储(简化示例)
todos: list[str] = []
@mcp.tool()
def add_todo(text: str) -> str:
"""添加一条待办事项"""
todos.append(text)
return f"已添加:{text}(共 {len(todos)} 条)"
@mcp.tool()
def list_todos() -> str:
"""列出所有待办事项"""
if not todos:
return "暂无待办事项"
return "\n".join(f"{i+1}. {t}" for i, t in enumerate(todos))
mcp.run()就这么简单。 FastMCP 自动处理协议细节:
- 把
@mcp.tool()注册的函数暴露为 MCP Tools - 根据函数签名和 docstring 自动生成 JSON Schema
- 处理 JSON-RPC 消息的收发
运行
python server.pyServer 以 stdio 模式启动,等待 Client 连接。
添加资源
除了工具,还可以暴露资源(Resources):
@mcp.resource("todo://stats")
def todo_stats() -> str:
"""待办事项统计"""
return f"共 {len(todos)} 条待办,0 条已完成"
@mcp.resource("todo://list")
def todo_list_resource() -> str:
"""待办列表(只读视图)"""
return "\n".join(todos) if todos else "空"添加模板
还可以暴露提示词模板(Prompts):
@mcp.prompt()
def plan_day(focus: str) -> str:
"""根据待办事项规划今天的工作"""
current = "\n".join(f"- {t}" for t in todos) if todos else "暂无"
return f"我的待办事项:\n{current}\n\n今天重点关注:{focus}\n请帮我规划今天的工作。"TypeScript 版本
// server.ts
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { z } from "zod";
const server = new McpServer({ name: "todo-server", version: "1.0.0" });
const todos: string[] = [];
server.tool("add-todo", "添加一条待办事项",
{ text: z.string().describe("待办内容") },
async ({ text }) => {
todos.push(text);
return {
content: [{ type: "text" as const, text: `已添加:${text}(共 ${todos.length} 条)` }]
};
}
);
server.tool("list-todos", "列出所有待办事项",
{},
async () => {
if (todos.length === 0) {
return { content: [{ type: "text" as const, text: "暂无待办事项" }] };
}
const list = todos.map((t, i) => `${i + 1}. ${t}`).join("\n");
return { content: [{ type: "text" as const, text: list }] };
}
);
const transport = new StdioServerTransport();
await server.connect(transport);运行
npx tsx server.ts
# 或者先编译
npx tsc server.ts && node server.js在 Claude Desktop 中配置
写好 Server 后,需要在 Claude Desktop 中配置才能使用。
找到配置文件
| 系统 | 路径 |
|---|---|
| macOS | ~/Library/Application Support/Claude/claude_desktop_config.json |
| Windows | %APPDATA%\Claude\claude_desktop_config.json |
编辑配置
{
"mcpServers": {
"todo": {
"command": "python",
"args": ["server.py 的完整路径"]
}
}
}TypeScript 版本把 command 改为 npx,args 改为 ["tsx", "server.ts 的完整路径"]。
重启 Claude Desktop
配置修改后必须完全退出并重启 Claude Desktop(关闭窗口不够)。
验证
重启后,在 Claude Desktop 中测试:
成功了! 小周的第一反应是:代码这么少?是的,FastMCP 和 McpServer 封装了几乎所有协议细节。
关键代码模式
工具返回值
工具必须返回包含 content 数组的对象:
# 直接返回字符串(FastMCP 自动包装)
@mcp.tool()
def my_tool() -> str:
return "结果文本"
# 需要返回多块内容时
from mcp.types import TextContent
@mcp.tool()
def my_tool() -> list[TextContent]:
return [TextContent(type="text", text="第一部分"), TextContent(type="text", text="第二部分")]// 必须返回 { content: [...] }
server.tool("my-tool", "描述", {}, async () => {
return {
content: [{ type: "text", text: "结果文本" }]
};
});错误处理
工具执行出错时,返回 isError: true:
Python 和 TypeScript 的错误处理方式不同:Python 的 FastMCP 会自动将未捕获异常(如
raise ValueError(...))包装为isError: true的 Tool Execution Error;TypeScript SDK 需要手动构造{ content: [...], isError: true }对象。
@mcp.tool()
def divide(a: float, b: float) -> str:
"""除法运算"""
if b == 0:
# FastMCP 会自动将未捕获异常包装为 isError: true 的 Tool Execution Error
# LLM 看到错误描述后可以修正参数重试
raise ValueError("除数不能为零,请提供非零的 b 值")
return f"结果:{a / b}"server.tool("divide", "除法运算",
{ a: z.number(), b: z.number() },
async ({ a, b }) => {
if (b === 0) {
return {
content: [{ type: "text", text: "除数不能为零,请提供非零的 b 值" }],
isError: true
};
}
return { content: [{ type: "text", text: `结果:${a / b}` }] };
}
);本节核心要点
- Python 用
FastMCP,TypeScript 用McpServer——都封装了协议细节 @mcp.tool()/server.tool()注册工具,函数签名自动生成 JSON Schema- 配置 Claude Desktop 的
claude_desktop_config.json,然后重启 - 工具执行错误返回
isError: true,LLM 可以自我修正 - 先从最简单的工具开始,确认跑通后再加资源和模板
练习:给 Todo Server 加一个 clear-todos 工具,清除所有待办。想想这个工具的 destructiveHint 应该设为什么?