第 02 篇:5 分钟搭建第一个 MCP 服务器

本篇是《MCP 开发实战教程》专栏的第 2 篇。上一篇我们搞清楚了 MCP 的架构和核心概念,本篇将动手------用 FastMCP 从零搭建一个真正有用的 MCP Server,不只是 Hello World,而是一个能管理任务、查询数据的实战级服务器。

引言

你可能有过这种体验:看完一个技术概念的介绍,觉得"我懂了",但打开编辑器准备动手时,却不知道从哪里开始。

上一篇我们讲了 MCP 的 Host/Client/Server 架构、三种原语(Tools/Resources/Prompts)、AAIF 治理。概念都懂了,但"懂"和"会做"之间差着一个动手的环节。

本篇的目标很明确:用 5 分钟时间,让你亲手搭建一个能跑的 MCP Server,并在 Claude Desktop 中实际使用它。 我们不会写一个只返回 "Hello World" 的玩具------而是一个个人任务管理器,支持添加任务、查看任务列表、按状态筛选。这个例子虽然简单,但完整覆盖了 MCP 的三种核心原语。

读完本篇,你将获得:

  • 一个可运行的 MCP Server 代码
  • 理解 Tools、Resources、Prompts 在代码中怎么写
  • 掌握 MCP Inspector 测试工具的使用方法
  • 知道如何把 Server 接入 Claude Desktop

1. 环境准备

1.1 你需要什么

环境要求 最低版本 说明
Python 3.11+ FastMCP 3.x 要求
pip 或 uv 最新版 包管理器
Claude Desktop 最新版 用于实际体验 MCP 交互

检查你的 Python 版本:

bash 复制代码
python --version
# 预期输出:Python 3.11.x 或更高

1.2 安装 FastMCP

bash 复制代码
# 推荐使用 uv(更快的 Python 包管理器)
uv add fastmcp

# 或者用 pip
pip install fastmcp

FastMCP v3 的 CLI 工具(fastmcp runfastmcp dev inspectorfastmcp install 等)随主包自动安装,不需要额外的 extra。

验证安装(fastmcp version 是 FastMCP v3 的内置命令,详见安装文档):

bash 复制代码
fastmcp version
# 预期输出:
# FastMCP version: 3.x.x
# MCP version: 1.x.x
# Python version: 3.x.x

依赖:fastmcp>=3.0, python>=3.11

1.3 常见坑:fastmcp 和 mcp 包冲突

如果你之前安装过官方的 mcp 包(MCP Python SDK),可能会和 fastmcp 冲突。两个包都有 FastMCP 类,但版本不同:

bash 复制代码
# 检查是否有冲突
pip list | grep -i mcp

# 如果同时有 mcp 和 fastmcp,卸载 mcp 包
pip uninstall mcp

FastMCP 1.0 已合并进官方 mcp SDK,fastmcp 包是独立维护的后续版本(当前 v3.x),功能远超 v1。本专栏统一使用 fastmcp 包。


2. 编写你的第一个 MCP Server

我们要建一个个人任务管理器,支持:

  • 添加任务(Tool)
  • 完成任务(Tool)
  • 查看所有任务(Resource)
  • 按状态筛选任务(Tool)
  • 标准化的任务创建流程(Prompt)

2.1 创建项目文件

创建文件 task_manager.py

python 复制代码
"""个人任务管理器 MCP Server"""

from datetime import datetime
from fastmcp import FastMCP

# 创建 MCP Server 实例
mcp = FastMCP("任务管理器")

# 内存存储(实际项目中应使用数据库)
tasks: list[dict] = []
next_id = 1


# ============ Tools(工具)============

@mcp.tool()
def add_task(title: str, priority: str = "medium") -> str:
    """添加一个新任务。

    Args:
        title: 任务标题
        priority: 优先级,可选 high/medium/low
    """
    global next_id
    task = {
        "id": next_id,
        "title": title,
        "priority": priority,
        "status": "pending",
        "created_at": datetime.now().strftime("%Y-%m-%d %H:%M"),
    }
    tasks.append(task)
    next_id += 1
    return f"任务已创建:#{task['id']} {title}(优先级:{priority})"


@mcp.tool()
def complete_task(task_id: int) -> str:
    """将指定任务标记为完成。

    Args:
        task_id: 任务 ID
    """
    for task in tasks:
        if task["id"] == task_id:
            task["status"] = "done"
            return f"任务 #{task_id} 已完成:{task['title']}"
    return f"未找到任务 #{task_id}"


@mcp.tool()
def search_tasks(keyword: str) -> str:
    """按关键词搜索任务。

    Args:
        keyword: 搜索关键词
    """
    matched = [t for t in tasks if keyword.lower() in t["title"].lower()]
    if not matched:
        return f"没有找到包含「{keyword}」的任务"
    lines = [f"找到 {len(matched)} 个任务:"]
    for t in matched:
        status = "✅" if t["status"] == "done" else "⏳"
        lines.append(f"  {status} #{t['id']} [{t['priority']}] {t['title']}")
    return "\n".join(lines)


# ============ Resources(资源)============

@mcp.resource("tasks://all")
def get_all_tasks() -> str:
    """返回所有任务的列表"""
    if not tasks:
        return "当前没有任务"
    lines = [f"共 {len(tasks)} 个任务:\n"]
    for t in tasks:
        status = "✅" if t["status"] == "done" else "⏳"
        lines.append(
            f"  {status} #{t['id']} [{t['priority']}] {t['title']}"
            f"  (创建于 {t['created_at']})"
        )
    return "\n".join(lines)


@mcp.resource("tasks://stats")
def get_task_stats() -> str:
    """返回任务统计信息"""
    total = len(tasks)
    done = sum(1 for t in tasks if t["status"] == "done")
    pending = total - done
    high = sum(1 for t in tasks if t["priority"] == "high" and t["status"] == "pending")
    return (
        f"任务统计:\n"
        f"  总数:{total}\n"
        f"  已完成:{done}\n"
        f"  待处理:{pending}\n"
        f"  高优先级待处理:{high}"
    )


# ============ Prompts(提示模板)============

@mcp.prompt()
def daily_review() -> str:
    """每日任务回顾模板"""
    pending = [t for t in tasks if t["status"] == "pending"]
    if not pending:
        return "所有任务已完成!可以安排新的工作了。"
    lines = ["请帮我回顾今天的待办任务,并给出优先级建议:\n"]
    for t in pending:
        lines.append(f"- #{t['id']} [{t['priority']}] {t['title']}")
    lines.append("\n请根据紧急程度和重要性,建议我今天先做哪些任务。")
    return "\n".join(lines)


# 启动服务器
if __name__ == "__main__":
    mcp.run()

2.2 代码解析

这段代码大约 100 行,但覆盖了 MCP 的全部三种原语:

Tools(3 个):

  • add_task --- 创建新任务,支持优先级设置
  • complete_task --- 按 ID 标记任务完成
  • search_tasks --- 关键词搜索

Resources(2 个):

  • tasks://all --- 返回完整任务列表(只读数据)
  • tasks://stats --- 返回统计摘要

Prompts(1 个):

  • daily_review --- 每日回顾模板,LLM 可以用它来帮用户做任务优先级排序

注意 @mcp.tool() 装饰器的写法:函数的 docstring 会自动变成工具的描述,类型注解 会自动变成参数的 JSON Schema。这意味着你不需要手写任何 JSON 定义------Python 代码本身就是协议定义。


3. 测试:用 MCP Inspector 验证

FastMCP v3 内置了 MCP Inspector 集成,让你不需要配置 Claude Desktop 就能测试 Server。

3.1 启动 Inspector

有两种方式启动 MCP Inspector:

方式一:使用 FastMCP CLI(推荐,不需要 Node.js)

bash 复制代码
fastmcp dev inspector task_manager.py

fastmcp dev inspector 是 FastMCP v3 的内置命令,详见 官方 CLI 文档

方式二:使用官方 MCP Inspector(需要 Node.js)

bash 复制代码
npx @modelcontextprotocol/inspector python task_manager.py

预期输出:

复制代码
Starting MCP inspector...
Proxy server listening on 127.0.0.1:6277
MCP Inspector is up and running at http://localhost:6274

浏览器会自动打开 MCP Inspector 界面。如果没有自动打开,手动访问 http://localhost:6274

3.2 测试 Tools

在 Inspector 中:

  1. 点击 Tools 标签页
  2. 你应该看到 3 个工具:add_taskcomplete_tasksearch_tasks
  3. 点击 add_task,在参数面板中填入:
    • title: "学习 MCP 协议"
    • priority: "high"
  4. 点击 Run Tool

预期返回:

复制代码
任务已创建:#1 学习 MCP 协议(优先级:high)

再添加几个任务,然后用 search_tasks 搜索试试。

3.3 测试 Resources

  1. 点击 Resources 标签页
  2. 你应该看到 2 个资源:tasks://alltasks://stats
  3. 点击 tasks://all,然后点击 Read Resource

预期返回(假设你之前添加了 2 个任务):

复制代码
共 2 个任务:

  ⏳ #1 [high] 学习 MCP 协议 (创建于 2026-05-25 14:30)
  ⏳ #2 [medium] 写第二个 MCP Server (创建于 2026-05-25 14:31)

3.4 测试 Prompts

  1. 点击 Prompts 标签页
  2. 点击 daily_review
  3. 点击 Get Prompt

预期返回:

复制代码
请帮我回顾今天的待办任务,并给出优先级建议:

- #1 [high] 学习 MCP 协议
- #2 [medium] 写第二个 MCP Server

请根据紧急程度和重要性,建议我今天先做哪些任务。

3.5 实时重载

fastmcp run 支持 --reload 参数,修改代码后自动重启服务器:

bash 复制代码
fastmcp run task_manager.py --reload

使用 Inspector 测试时,修改代码后需要手动重新连接(点击 Inspector 中的 Connect 按钮)。如果配合 --reload 使用,服务器会自动重启,Inspector 重连即可看到最新代码的效果。


4. 接入 Claude Desktop

测试通过后,把 Server 接入 Claude Desktop,让 AI 真正使用你的工具。

4.1 找到配置文件

操作系统 配置文件路径
macOS ~/Library/Application Support/Claude/claude_desktop_config.json
Windows %APPDATA%\Claude\claude_desktop_config.json
Linux ~/.config/Claude/claude_desktop_config.json

如果文件不存在,创建一个空的 JSON 文件。

4.2 添加 Server 配置

编辑配置文件,添加你的 Server:

json 复制代码
{
  "mcpServers": {
    "task-manager": {
      "command": "python",
      "args": ["/绝对路径/task_manager.py"]
    }
  }
}

重要args 中必须使用绝对路径 ,不能用相对路径 ./task_manager.py。Claude Desktop 启动 Server 时的工作目录不确定,相对路径会找不到文件。

如果你用 uv 管理虚拟环境,需要指定虚拟环境中的 Python:

macOS / Linux:

json 复制代码
{
  "mcpServers": {
    "task-manager": {
      "command": "/绝对路径/.venv/bin/python",
      "args": ["/绝对路径/task_manager.py"]
    }
  }
}

Windows:

json 复制代码
{
  "mcpServers": {
    "task-manager": {
      "command": "C:\\绝对路径\\.venv\\Scripts\\python.exe",
      "args": ["C:\\绝对路径\\task_manager.py"]
    }
  }
}

4.3 重启并验证

  1. 完全退出 Claude Desktop(不是最小化,是退出)
  2. 重新打开 Claude Desktop
  3. 在输入框旁边,你应该看到一个锤子图标(🔧),表示有 MCP 工具可用
  4. 点击锤子图标,确认能看到 add_taskcomplete_tasksearch_tasks

4.4 实际对话测试

试试和 Claude 这样对话:

:帮我添加三个任务:1)学习 MCP 协议,优先级高;2)写单元测试,优先级中;3)整理文档,优先级低。

Claude 会自动调用 add_task 工具三次,返回:

复制代码
任务已创建:#1 学习 MCP 协议(优先级:high)
任务已创建:#2 写单元测试(优先级:medium)
任务已创建:#3 整理文档(优先级:low)

:现在帮我看看所有任务。

Claude 会读取 tasks://all 资源,返回格式化的任务列表。

:帮我做个今日任务回顾,建议我先做哪个。

Claude 会使用 daily_review Prompt 模板,结合任务数据给出优先级建议。

整个过程中,Claude 自动决定调用哪个工具、读取哪个资源、使用哪个 Prompt------你不需要做任何手动配置。


5. 深入理解:代码背后的设计决策

5.1 为什么用 docstring 和类型注解?

你可能注意到了,我们没有手写任何 JSON Schema 或工具描述。FastMCP 自动从 Python 代码中提取:

Python 代码元素 MCP 协议中的对应
函数名 add_task 工具名称
docstring 工具描述(LLM 据此决定何时调用)
参数名和类型注解 title: str JSON Schema 的 properties
默认值 priority = "medium" JSON Schema 的 default

这意味着写 Python 代码就是在写协议定义。代码即文档,代码即协议。

5.2 Tool vs Resource:什么时候用哪个?

你可能会问:tasks://all 既能做成 Resource,也能做成 Tool(比如 list_tasks()),该怎么选?

维度 Tool Resource
语义 执行操作 提供数据
副作用 可以有(修改状态) 不应有(只读)
触发方式 LLM 主动决定调用 Client 读取或 LLM 请求
典型场景 创建/修改/删除 配置/状态/上下文

简单来说:能改变状态的用 Tool,只提供信息的用 Resource。

在我们的例子中:

  • add_taskcomplete_task 会改变任务状态 → Tool
  • tasks://alltasks://stats 只是读取数据 → Resource

5.3 Prompt 的作用

Prompt 看起来好像"没什么技术含量"------不就是返回一段文字吗?但它的价值在于标准化

没有 Prompt 时,用户每次都要手动描述"帮我回顾一下今天的任务并给出优先级建议"。有了 daily_review Prompt,用户只需选择这个模板,LLM 就知道该怎么做。

在企业场景中,Prompt 可以标准化团队的工作流程:代码审查模板、故障排查步骤、日报格式等。


6. 常见问题与踩坑记录

Q1: fastmcp 命令报错 "No module named fastmcp"

原因:你可能在虚拟环境中安装了 fastmcp,但运行 fastmcp 时用的是全局 Python。

解决:

bash 复制代码
# 确认 fastmcp 安装位置
# macOS / Linux:
which fastmcp
# Windows:
where fastmcp

# 如果不在虚拟环境中,用 uv 运行
uv run fastmcp dev inspector task_manager.py

Q2: Claude Desktop 看不到工具

排查步骤:

  1. 确认配置文件路径正确(注意 Windows 的 %APPDATA% 路径)
  2. 确认使用了绝对路径
  3. 完全退出 Claude Desktop 后重新打开(不是最小化)
  4. 检查 JSON 格式是否正确(多余的逗号会导致解析失败)

Q3: Server 启动后 Claude Desktop 连接失败

最常见的原因是 Python 路径问题。确认你用的是哪个 Python:

bash 复制代码
# macOS / Linux
which python
# Windows
where python

然后在配置中使用完整路径。

Q4: from mcp import FastMCPfrom fastmcp import FastMCP 有什么区别?

mcp 包是官方 SDK,内置 FastMCP v1(功能有限)。fastmcp 包是独立项目,当前版本 v3.x(功能丰富)。本专栏统一使用后者。如果两个包都安装了,可能会冲突,建议卸载 mcp 包:

bash 复制代码
pip uninstall mcp

注意 :卸载 mcp 包不影响 fastmcp 的使用。fastmcp 包有自己独立的依赖管理。


总结

  1. FastMCP 让 MCP Server 开发变得极其简单:一个装饰器注册一个工具,Python 代码即协议定义。十几行代码就能搭建一个功能完整的 Server。

  2. 三种原语各有分工:Tool 负责执行操作(可改变状态),Resource 负责提供数据(只读),Prompt 负责标准化工作流程。理解这个区别是写好 MCP Server 的关键。

  3. MCP Inspector 是开发必备工具fastmcp dev inspector 命令启动 Inspector,可以在浏览器中交互式测试所有工具、资源和提示。先用 Inspector 测试,再接入 Claude Desktop。

  4. 接入 Claude Desktop 只需一个配置文件 :编辑 claude_desktop_config.json,添加 Server 的命令和路径,重启即可。注意使用绝对路径。

  5. docstring 写得好,LLM 用得准:工具的描述直接影响 LLM 的调用决策。写清楚工具做什么、参数是什么、什么时候该用,比写代码本身更重要。

下篇预告

这一篇我们用 FastMCP 搭了一个简单的任务管理器。但你可能注意到,我们的 Server 只暴露了 Tools 和 Resources,还没有深入协议本身的通信机制。下一篇《协议详解》将带你拆解 MCP 的 JSON-RPC 消息格式、规范版本演进、以及传输层的工作原理------让你不只是"会用",还"懂原理"。


附录:参考资料

相关推荐
lauo1 小时前
从0.04%到即插即用:RedSkill的种草困境与ibbot手机青春版的Token经济反击战
人工智能·智能手机
AI刀刀1 小时前
文心粘贴到 word 格式混乱,AI 导出鸭智能转文档零失真
人工智能·c#·word·ai导出鸭
商业模式源码开发1 小时前
餐饮实体商业模式拆解:推三享一与异业联盟的合规落地架构
大数据·架构·异业联盟·私域流量·推三返一·商业观察
jinxindeep1 小时前
世界模型:架构、方法、推理与应用全景综述
人工智能·架构·机器人
醉颜凉1 小时前
Scala自定义Monad实战:从理论到应用的完整指南
大数据·算法·scala
zhangfeng11331 小时前
非传统架构 AI 算力卡前沿研究报告:技术痛点、破局路2021-2026
人工智能·语言模型·transformer·gpu算力·芯片
流浪0011 小时前
Linux篇(十):取代命令行 GDB?CGDB 可视化调试全解析
linux·运维·服务器
兴通物联科技1 小时前
CRPT 俄罗斯诚信标签数据采集系统架构与 CSV 合规文件生成原理
大数据·图像处理·人工智能·计算机视觉·系统架构
IvanCodes1 小时前
二、Scala流程控制:分支与循环
大数据·scala