Skip to content

3.1 第一个 MCP Server——Python/TypeScript 快速上手

上一节你学了 MCP 的通信协议。现在概念够了,小周要动手了。他的目标:写一个能跑的 MCP Server,在 Claude Desktop 中用起来。


选择你的武器

本节提供 Python 和 TypeScript 两个版本。选你熟悉的那个即可。

bash
pip install mcp
bash
npm install @modelcontextprotocol/sdk zod

目标:Todo Server

小周要写一个简单的 Todo 管理 Server,暴露一个工具:

工具功能参数
add-todo添加待办事项text(必填):待办内容
list-todos列出所有待办

Python 版本

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 消息的收发

运行

bash
python server.py

Server 以 stdio 模式启动,等待 Client 连接。

添加资源

除了工具,还可以暴露资源(Resources):

python
@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):

python
@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 版本

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);

运行

bash
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

编辑配置

json
{
  "mcpServers": {
    "todo": {
      "command": "python",
      "args": ["server.py 的完整路径"]
    }
  }
}

TypeScript 版本把 command 改为 npxargs 改为 ["tsx", "server.ts 的完整路径"]

重启 Claude Desktop

配置修改后必须完全退出并重启 Claude Desktop(关闭窗口不够)。


验证

重启后,在 Claude Desktop 中测试:

Claude Desktop — MCP 对话
你:帮我添加三个待办:写 MCP 教程、测试代码、部署上线
[调用工具] add-todo({text: "写 MCP 教程"})
→ 已添加:写 MCP 教程(共 1 条)
[调用工具] add-todo({text: "测试代码"})
→ 已添加:测试代码(共 2 条)
[调用工具] add-todo({text: "部署上线"})
→ 已添加:部署上线(共 3 条)
──────────────────────────────
你:列出所有待办
[调用工具] list-todos()
→ 1. 写 MCP 教程
2. 测试代码
3. 部署上线

成功了! 小周的第一反应是:代码这么少?是的,FastMCP 和 McpServer 封装了几乎所有协议细节。


关键代码模式

工具返回值

工具必须返回包含 content 数组的对象:

python
# 直接返回字符串(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="第二部分")]
typescript
// 必须返回 { 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 } 对象。

python
@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}"
typescript
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 应该设为什么?


← 上一节:JSON-RPC 与消息流 | 目录 | 下一节:第一个 MCP Client →