首页 › tutorials › Python FastMCP打造代码片段管家

概述

本教程带你用 Python + FastMCP 从零构建一个可被 Claude Code 直接调用的本地 MCP 服务器——「代码片段管家(Snippet Vault)」。学完你将掌握:用装饰器把普通 Python 函数变成 MCP 工具、只读资源、提示模板;用 Context 向客户端回报进度与日志;用 uv/uvx 打包并通过 claude mcp add 接入 Claude Code;用 mcp dev 全链路调试。适合写过 Python、想给自己(或团队)的 Claude Code 加一套私有工具的工程师。前置已覆盖 Go/Rust 构建 MCP 的路径,本篇补齐最主流的 Python FastMCP 这条官方推荐路线。

前置条件

为什么是 FastMCP

手写 MCP 意味着自己实现 JSON-RPC 2.0、能力协商、stdio 帧解析、工具 schema 校验……而 FastMCP 把这套底层全部封装,让你只写业务函数:

原语 装饰器 语义 在 Snippet Vault 中的角色
工具 Tool @mcp.tool LLM 可调用、有副作用 增删改查片段
资源 Resource @mcp.resource 只读数据,LLM 按需读取 片段索引、单个片段内容
提示 Prompt @mcp.prompt 可复用的消息模板 “审查这段代码”模板

FastMCP 会根据函数签名与类型注解自动生成 JSON Schema,Claude 据此决定何时调用、传什么参数。它的开发者体验接近 FastAPI,是目前 Anthropic 官方在 Python 侧首推的构建方式(FastMCP 已与官方 mcp Python SDK 合流,2026 年稳定在 3.x)。

第一步:脚手架与依赖

创建项目目录并用 uv 初始化:

mkdir snippet-vault && cd snippet-vault
uv init --name snippet-vault
uv add fastmcp

uv init 会生成 pyproject.toml 和一个示例 main.py。把 main.py 删掉,我们从头写。确认 FastMCP 版本在 2.x/3.x:

uv run python -c "import fastmcp; print(fastmcp.__version__)"
# 预期输出类似:3.2.x 或 2.x

常见问题

第二步:最小可运行服务器

先写一个只有两个工具的最小版本,跑通“装饰器 → schema → Claude 调用”闭环。新建 server.py:

from fastmcp import FastMCP

mcp = FastMCP("Snippet Vault")

@mcp.tool
def ping(message: str = "hello") -> str:
    """健康检查:原样回显消息,证明链路畅通。"""
    return f"pong: {message}"

if __name__ == "__main__":
    mcp.run()          # 默认 stdio 传输,无需任何参数

预期结果:mcp.run() 会让进程进入 stdio 监听,不会打印任何东西到终端——stdio 协议要求 stdout 只能是 JSON-RPC 帧,任何 print 都会破坏协议。这是新手最大的坑。

常见问题

第三步:搭建 Snippet Vault 数据层

我们用一个 JSON 文件作为持久化(教学够用,生产可换 SQLite/Postgres)。新建 store.py:

from __future__ import annotations
import json, uuid, threading
from pathlib import Path
from typing import TypedDict

class Snippet(TypedDict):
    id: str
    title: str
    language: str
    code: str
    tags: list[str]

_PATH = Path.home() / ".snippet-vault.json"
_LOCK = threading.Lock()

def _load() -> list[Snippet]:
    if not _PATH.exists():
        return []
    return json.loads(_PATH.read_text("utf-8"))

def _save(data: list[Snippet]) -> None:
    _PATH.write_text(json.dumps(data, ensure_ascii=False, indent=2), "utf-8")

def add(title: str, language: str, code: str, tags: list[str]) -> Snippet:
    with _LOCK:
        data = _load()
        item: Snippet = {"id": uuid.uuid4().hex[:8], "title": title,
                         "language": language, "code": code, "tags": tags or []}
        data.append(item)
        _save(data)
        return item

def all_snippets() -> list[Snippet]:
    return _load()

def get(sid: str) -> Snippet | None:
    return next((s for s in _load() if s["id"] == sid), None)

def delete(sid: str) -> bool:
    with _LOCK:
        data = _load()
        before = len(data)
        data = [s for s in data if s["id"] != sid]
        _save(data)
        return len(data) < before

预期结果:首次写入会在 ~/.snippet-vault.json 生成文件。_LOCK 保证并发安全。

第四步:把能力暴露成 MCP 工具

回到 server.py,接入数据层,并写带类型注解 + docstring 的工具——这两样直接决定 Claude 调用的准确率:

from fastmcp import FastMCP, Context
import store

mcp = FastMCP("Snippet Vault")

@mcp.tool
def save_snippet(title: str, language: str, code: str, tags: list[str] | None = None) -> dict:
    """保存一段代码片段。

    Args:
        title: 片段标题,简短描述用途。
        language: 语言标签,如 python、sql、bash。
        code: 代码本体。
        tags: 可选标签列表,便于检索。
    """
    item = store.add(title, language, code, tags or [])
    return {"id": item["id"], "title": item["title"], "saved": True}

@mcp.tool
def search_snippets(query: str, ctx: Context) -> list[dict]:
    """按关键词在标题、标签、代码中模糊检索片段。"""
    q = query.lower()
    hits: list[dict] = []
    all_items = store.all_snippets()
    for i, s in enumerate(all_items, 1):
        ctx.report_progress(i, len(all_items))          # 向客户端推送进度
        blob = f"{s['title']} {' '.join(s['tags'])} {s['code']}".lower()
        if q in blob:
            hits.append({"id": s["id"], "title": s["title"], "language": s["language"]})
    ctx.info(f"检索完成,命中 {len(hits)} 条")              # 写入客户端日志
    return hits

@mcp.tool
def get_snippet(snippet_id: str) -> dict:
    """根据 ID 取片段完整内容。"""
    s = store.get(snippet_id)
    if s is None:
        raise ValueError(f"未找到片段: {snippet_id}")
    return s

@mcp.tool
def delete_snippet(snippet_id: str) -> dict:
    """删除指定 ID 的片段。"""
    deleted = store.delete(snippet_id)
    return {"id": snippet_id, "deleted": deleted}

if __name__ == "__main__":
    mcp.run()

为什么这样写:

预期结果:四个工具就绪。Claude 在收到“帮我存一段快速排序的 Python 片段”时,应能自主选择 save_snippet 并填好参数。

常见问题

第五步:用资源和提示补齐“三原语”

工具负责“动作”,资源负责“可读数据”,提示负责“任务模板”。补上后两类:

from fastmcp import FastMCP

# —— 静态资源:片段目录 ——
@mcp.resource("snippets://index")
def snippet_index() -> list[dict]:
    """所有片段的精简目录,便于 LLM 一览全貌。"""
    return [{"id": s["id"], "title": s["title"], "language": s["language"]}
            for s in store.all_snippets()]

# —— 资源模板:按 ID 取单个片段(URI 含占位符,动态解析)——
@mcp.resource("snippet://{snippet_id}")
def snippet_by_id(snippet_id: str) -> str:
    s = store.get(snippet_id)
    return f"# {s['title']}\n```{s['language']}\n{s['code']}\n```" if s else "(片段不存在)"

# —— 提示模板:固定一套审查流程 ——
@mcp.prompt
def review_snippet(snippet_id: str, focus: str = "正确性与边界") -> str:
    """生成一段“代码审查”提示,聚焦指定维度。"""
    s = store.get(snippet_id)
    if not s:
        return f"未找到片段 {snippet_id},请先用 search_snippets 查找。"
    return (
        f"请审查以下 {s['language']} 片段,重点:{focus}。\n"
        f"标题:{s['title']}\n代码:\n{s['code']}\n"
        "输出:1) 潜在 bug;2) 改进建议;3) 重写后的版本。"
    )

把上面三段追加到 server.py(mcp = FastMCP(...) 之后、mcp.run() 之前)。至此工具/资源/提示三原语齐备,与 Claude Code 的对接面完整。

资源 vs 工具的取舍:只读、想让 LLM 自主“翻阅”的用资源;需要副作用、精确参数校验的用工具。snippets://index 给 Claude 一个总览入口,避免它一上来就盲目调用 search_snippets。

第六步:打包成可执行入口(给 uvx 用)

要让 Claude Code 用 uvx 一键拉起服务器,得声明一个命令行入口。编辑 pyproject.toml:

[project]
name = "snippet-vault"
version = "0.1.0"
requires-python = ">=3.11"
dependencies = ["fastmcp"]

[project.scripts]
snippet-vault = "server:main"

[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"

[tool.hatch.build.targets.wheel]
packages = ["."]

再在 server.py 末尾把启动逻辑包成函数:

def main() -> None:
    mcp.run()

然后本地自测入口:

uv run snippet-vault --help   # 进程能起来、不报错即通过(stdio 模式会等待输入,Ctrl+C 退出)

常见问题

第七步:接入 Claude Code

Claude Code 支持三种传输:本地 stdio、HTTP、远程 SSE。本地工具用 stdio 最省事。两种接入方式任选其一。

方式 A:claude mcp add(命令行)

# 从项目目录运行;-- 之后的参数就是启动命令
claude mcp add snippet-vault -- uv run --directory $(pwd) snippet-vault

--directory 很关键——否则 Claude Code 可能在别的工作目录找不到 pyproject.toml。

方式 B:.mcp.json(项目级配置,可提交到仓库共享)

在项目根写 .mcp.json:

{
  "mcpServers": {
    "snippet-vault": {
      "command": "uv",
      "args": ["run", "--directory", "/绝对路径/snippet-vault", "snippet-vault"]
    }
  }
}

放进仓库后,团队成员 claude 一启动就会自动加载(首次会提示授权)。范围控制:claude mcp add --scope user 全局可用,--scope project 写入 .mcp.json,--scope local 仅本人本机。

验证连接:

claude mcp list                 # 应看到 snippet-vault ✓ connected
claude mcp get snippet-vault    # 列出它暴露的 tools/resources/prompts

进 Claude Code 交互界面,输入:“帮我存一段二分查找的 Python 实现,标签加上算法和查找,然后告诉我现在库里都有哪些片段。” Claude 应依次调用 save_snippet → snippets://index(或 search_snippets)。

常见问题

第八步:用 Inspector 全链路调试

写 MCP 最大的痛点是“它跑在 stdio 黑盒里,出错难定位”。官方调试器 mcp dev 能可视化地手动触发每个工具/资源/提示,并看到完整的 JSON-RPC 往返:

uv run mcp dev server.py

执行后终端会打印一个本地 Web 地址(默认 http://localhost:5173),浏览器打开即可:

  1. 左侧 Tools 列表点 save_snippet → 右侧填表单 → Run,下方看返回值。
  2. Resources 里能看到 snippets://index 与模板 snippet://{snippet_id}。
  3. Prompts 里执行 review_snippet,预览生成的消息。
  4. 底部 Notifications 区域会实时显示 ctx.report_progress 推送的进度——这是验证 Context 是否工作的最直观方式。

调试心法:

实战案例:把 Snippet Vault 变成“团队代码风格自检”闭环

把前面八步拼成一个真实场景:新人提交 PR 前,让 Claude Code 调用你的服务器自检是否符合团队片段库的既有范式。

  1. 往库里灌入团队约定片段(命名规范、错误处理范式、SQL 模板等)。
  2. 在仓库根放 .mcp.json,把 snippet-vault 设为 --scope project。
  3. 用 review_snippet 提示 + search_snippets 工具组合:让 Claude 先按“错误处理”检索现有范式,再据此审查当前改动。
  4. 配一个 hook:Stop 事件触发时若改动文件含 # TODO,自动调用 search_snippets("TODO 处理范式") 把规范片段贴进对话。

这样一个“私有知识 + 工具调用”的闭环就建好了,且全程零外部 API、零密钥。

工作流程图

flowchart TD
    A[Python 函数 + 装饰器] -->|FastMCP 自动生成| B[JSON Schema]
    B --> C[stdio / uvx 启动 server.py]
    C --> D{调试通道}
    D -->|mcp dev Inspector| E[可视化手动触发]
D --> |claude mcp add . mcp.json| F[Claude Code 加载]
    F --> G[自然语言触发]
    G --> H[Claude 按 schema 选工具]
    H --> I[store.py 读写 JSON]
    I --> J[Context 回报进度/日志]
    J --> G

三原语取舍决策图

flowchart LR
    Q{这份能力是?}
    Q -->|有副作用/需参数校验| T[@mcp.tool<br/>工具]
    Q -->|只读数据/可被浏览| R[@mcp.resource<br/>资源]
    Q -->|固定任务模板| P[@mcp.prompt<br/>提示]
    T --> CT[命名动词化 + 详尽 docstring]
    R --> CR[URI 清晰 + 静态/模板二选一]
    P --> CP[参数少 + 产出可预期]

延伸阅读

训练营适用性评估

参考来源

  1. Connect Claude Code to tools via MCP — Claude Code Docs — 官方 claude mcp add/list/get、三种 scope 与 .mcp.json 格式权威说明。
  2. How to Build a Python MCP Server with FastMCP — Pyrastra — 工具/资源/提示三原语 + Streamable HTTP 部署的完整 2026 实操。
  3. FastMCP in Python: Build a Real MCP Server (2026 Guide) — danilchenko.dev — FastMCP 3.2 的新特性、Context 用法与 GitHub OAuth 集成示例。
  4. Tools — FastMCP 官方文档 / Prompts — FastMCP — 装饰器签名、Context 对象、资源模板语法的官方定义。
  5. How to Build an MCP Server with Python, Docker, and Claude Code — freeCodeCamp — 从本地 stdio 到 Docker 化部署的端到端参考。

[!tip] 51学通信知识星球 星主:爱卫生(微信:gprshome201101)——公众号「51学通信」创办者,19 年通信行业运维与讲师经验,著有《GPRS网络信令实例详解》《EPC原理与实践》《5G核心网原理与实践》三本纸质书(各大电商平台有售)。 星球定位:深耕 5G 核心网、IMS、NFV/AI、云原生等 ICT 领域,主打运维岗位与信令分析。 当前规模:5200 篇主题 / 990 个回答,198 元/年。 加入福利:加入星球后请加爱卫生微信,拉您进知识星球年度陪伴与答疑群——不限量回复您的各种问题,另有群内分享。 AI 加持:51学通信知识星球已作为 skill 接入 WorkBuddy,使用 deepseek-4-flash 在海量星球帖子中精准找贴、找视频、找 FAQ 和案例,做总结、找文档和 PPT 等场景全覆盖。

51学通信知识星球海报-扫码加入