关键词:MCP,Model Context Protocol,Python,FastMCP,StreamableHTTP,AI Agent,Hermes,SQLite

从零开发一个 MCP Server:架构、实践与踩坑实录

本文基于 2026-08-15 本机真实开发会话。文中所有命令输出均为实际运行结果,不是示例数据。

适合读者:已有 Python 基础、想给 Agent/LLM 接入外部数据能力的开发者。通读约 15 分钟,完整跟练约 1 小时。

一、MCP 简介:为什么 Agent 需要一套“外设接口”

大模型 Agent 的瓶颈从来不是“会想”,而是“够不到”。模型只能看到对话文本,拿不到数据库、文件系统、内部 API 里的真实数据。早期方案是给每个 Agent 硬编码工具函数,但工具一多,维护就失控:每个 Agent 框架一套工具协议,换个框架全部重写。

MCP(Model Context Protocol)解决的就是这个问题。它由 Anthropic 于 2024 年底提出,是一个开放协议,定义了 MCP Server(能力提供方)MCP Client(能力消费方) 之间的标准通信方式:

  • Server 把能力包装成“工具”(tool),暴露给客户端
  • Client 启动时自动发现工具列表,运行时可调用
  • 传输层支持 stdio(本地子进程)和 StreamableHTTP(远程传输,2025-03 版协议规范引入,取代旧的 HTTP+SSE 方案)

类比一句话:MCP 之于 Agent,就像 USB 之于电脑——外设只要符合协议就能即插即用,不用管里面是什么芯片。

对比项 传统硬编码工具 MCP Server
工具协议 每框架一套,各自为政 统一开放协议
复用性 换框架重写 一套 Server 到处接
部署 与 Agent 同进程 本地子进程 / 远程独立部署
工具发现 手工注册 启动自动发现

二、MCP Server 应用介绍:

本文的实战场景:开发一个“学生成绩查询”MCP Server,让 Agent 能直接回答“张伟的均分是多少”这类问题,而不是让人先查好再喂给模型。

2.1 需求拆解

能力 说明
查学生 按学号精确 / 按姓名模糊
查成绩 按学生查全部成绩、按课程查成绩排名
统计分析 平均分、加权均分、最高最低分

2.2 技术选型

组件 选型 理由
语言 Python 3.12 MCP 官方 SDK 生态成熟
SDK mcp Python SDK(FastMCP) 一份代码支持 stdio + HTTP 双传输
存储 SQLite 单文件、零运维,演示/内网够用;可替换 MySQL/API
接入端 Hermes Agent 原生 MCP 客户端,配置即用

2.3 架构定位

这个 Server 属于“数据访问型 MCP”:后端接 SQLite,前端通过 MCP 协议向 Agent 暴露只读查询工具。后续接真实教务系统时,只改数据层,工具层不动。

三、MCP Server 架构说明

3.1 总体架构

在这里插入图片描述

Server 侧: grade-mcp-server

客户端侧

JSON-RPC 2.0
stdio / StreamableHTTP

Hermes Agent

MCP Client 模块

FastMCP Server

工具层
list_students / query_student_grades
query_course_grades / query_student_stats ...

数据层 db.py
SQLite 读写封装

SQLite
data/grades.db

通信细节:

  • 协议:MCP 基于 JSON-RPC 2.0,核心方法 initialize(握手)、tools/list(发现工具)、tools/call(调用工具)
  • 传输:本地用 stdio(子进程 stdin/stdout),远程用 StreamableHTTP(POST /mcp
  • 工具命名:接入 Hermes 后自动加前缀,如 mcp_grades_query_student_grades

3.2 项目结构

grade-mcp-server/
├── pyproject.toml           # 项目元数据 + 依赖声明
├── grade_mcp/
│   ├── __init__.py
│   ├── server.py            # FastMCP Server 入口,工具定义
│   └── db.py                # SQLite 数据层:建表/种子数据/查询
├── tests/
│   └── test_db.py           # 数据层单元测试(9 个用例)
├── scripts/
│   ├── test_stdio.py        # stdio 模式客户端冒烟测试
│   └── test_http.py         # HTTP 模式客户端冒烟测试
├── deploy/
│   ├── deploy.sh            # rsync + systemd 一键部署脚本
│   ├── grade-mcp.service    # systemd 单元文件
│   └── notes.md             # 防火墙/反代/认证建议
├── Dockerfile               # 容器化部署
└── README.md

3.3 分层职责

  • server.py(工具层):只声明工具签名和 docstring,不碰 SQL。docstring 就是给 LLM 看的工具说明,写清楚参数格式(如 S0012024-秋)能显著提高模型调用准确率。
  • db.py(数据层):所有 SQL 在这里,通过 GRADES_DB_PATH 环境变量控制库文件位置。换 MySQL 只改这一个文件。
  • 传输层:FastMCP 封装,server.run(transport="stdio")streamable_http_app() 两行切换。

四、MCP Server 开发实践

4.1 环境准备

系统 Python 是 3.9.6,MCP SDK 需要 3.10+,用 uv 建独立环境:

$ cd ~/workspace/hermes && mkdir grade-mcp-server && cd grade-mcp-server
$ uv venv --python 3.12 .venv
Using CPython 3.12.13
Creating virtual environment at: .venv

4.2 依赖声明

[project]
name = "grade-mcp-server"
version = "0.1.0"
requires-python = ">=3.10"
dependencies = [
    "mcp>=1.28,<2",
    "uvicorn>=0.30.0",
]

这里锁 mcp<2 是本会话踩的第一个大坑,见“测试验证”章节的踩坑 1。

安装依赖(以可编辑模式装进虚拟环境,方便后续改代码即时生效):

$ uv pip install -e .

若不想打可编辑安装,也可以直接 uv pip install "mcp>=1.28,<2" uvicorn,再从项目根目录运行 python -m grade_mcp.server(利用当前目录的包路径)。

4.3 数据层 db.py(节选)

SCHEMA = """
CREATE TABLE IF NOT EXISTS students (
    id         TEXT PRIMARY KEY,
    name       TEXT NOT NULL,
    class_name TEXT NOT NULL
);

CREATE TABLE IF NOT EXISTS courses (
    id      TEXT PRIMARY KEY,
    name    TEXT NOT NULL,
    credits INTEGER NOT NULL DEFAULT 3
);

CREATE TABLE IF NOT EXISTS grades (
    id         INTEGER PRIMARY KEY AUTOINCREMENT,
    student_id TEXT NOT NULL REFERENCES students(id),
    course_id  TEXT NOT NULL REFERENCES courses(id),
    semester   TEXT NOT NULL,
    score      REAL NOT NULL CHECK (score >= 0 AND score <= 100),
    UNIQUE (student_id, course_id, semester)
);
"""

种子数据:5 名学生、5 门课程、16 条成绩记录。首次启动自动建库写入。

4.4 Server 入口 server.py(节选)

from mcp.server.fastmcp import FastMCP
from grade_mcp import db

server = FastMCP(
    "grade-mcp-server",
    instructions=(
        "成绩查询 MCP server。提供学生成绩查询、课程成绩查询、"
        "学生统计等工具。学生 ID 形如 S001,课程 ID 形如 C001。"
    ),
)

@server.tool()
def query_student_grades(student_id: str, semester: str | None = None) -> dict:
    """查询某学生的全部成绩。student_id 为学号(如 S001);semester 可选,如 '2024-秋'。"""
    student = db.get_student_by_id(student_id.strip().upper())
    if not student:
        return {"error": f"学生 {student_id} 不存在"}
    grades = db.get_grades_by_student(student["id"], semester)
    return {"student": student, "grades": grades}

入口同时支持两种传输:

if args.transport == "http":
    app = server.streamable_http_app()   # StreamableHTTP,端点 /mcp
    uvicorn.run(app, host=args.host, port=args.port)
else:
    server.run(transport="stdio")        # 本地子进程模式

4.5 接入 Hermes

本地开发用 stdio,一条命令接入:

$ hermes mcp add grades --command /Users/workspace/hermes/grade-mcp-server/.venv/bin/python \
    --connect-timeout 30 --args -m grade_mcp.server
  ✓ Saved 'grades' to ~/.hermes/config.yaml (6/6 tools enabled)

生成配置:

mcp_servers:
  grades:
    command: /Users/workspace/hermes/grade-mcp-server/.venv/bin/python
    args:
      - -m
      - grade_mcp.server
    connect_timeout: 30.0
    enabled: true

4.6 切换到 HTTP 模式(远程部署预览)

$ .venv/bin/python -m grade_mcp.server --transport http --host 127.0.0.1 --port 8000
Grade MCP Server listening on http://127.0.0.1:8000/mcp
INFO:     Application startup complete.
INFO:     Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)

$ hermes mcp remove grades
  ✓ Removed 'grades' from config

$ hermes mcp add grades --url http://127.0.0.1:8000/mcp --connect-timeout 30
  ✓ Saved 'grades' to ~/.hermes/config.yaml (6/6 tools enabled)

HTTP 模式配置:

mcp_servers:
  grades:
    url: http://127.0.0.1:8000/mcp
    connect_timeout: 30.0
    headers: {}
    enabled: true

⚠️ 注意:HTTP 模式默认没有任何认证。上面的示例刻意只绑定 127.0.0.1;一旦要暴露到内网/公网,务必先加反代 + 鉴权(如 Nginx + Bearer Token,或 OAuth),方案见仓库 deploy/notes.md

五、测试验证过程

5.1 数据层单元测试

先给数据层写 9 个 pytest 用例,覆盖种子数据、精确/模糊查询、学期过滤、成绩降序、统计聚合、异常学生:

$ .venv/bin/python -m pytest tests/ -q
.........                                                                [100%]
9 passed in 0.03s

5.2 stdio 模式端到端冒烟

写一个最小 MCP 客户端,真实走一遍 initialize → list_tools → call_tool:

$ .venv/bin/python scripts/test_stdio.py
[tools] 6 registered:
  - list_students: 列出所有学生(学号、姓名、班级)。
  - list_courses: 列出所有课程(课程号、课程名、学分)。
  - find_student: 按学号或姓名关键字查找学生。...
  - query_student_grades: 查询某学生的全部成绩。...
  - query_course_grades: 查询某课程的所有学生成绩。...
  - query_student_stats: 查询学生的成绩统计:...

[query_student_grades S001] {
  "student": {"id": "S001", "name": "张伟", "class_name": "软件工程 2023级1班"},
  "grades": [
    {"semester": "2024-秋", "score": 88.0, "course_id": "C001", "course_name": "高等数学", "credits": 5},
    {"semester": "2024-秋", "score": 92.0, "course_id": "C002", "course_name": "数据结构", "credits": 4},
    ...
  ]
}

5.3 HTTP 模式端到端验证

$ curl -s http://127.0.0.1:8000/health
{"status":"ok","service":"grade-mcp-server"}

$ hermes mcp test grades
  Transport: HTTP → http://127.0.0.1:8000/mcp
  Auth: none
  ✓ Connected (47ms)
  ✓ Tools discovered: 6

5.4 在 Hermes 会话里真实调用

切换成 HTTP 后,直接在对话中让 Agent 查所有学生成绩统计,5 个并行查询全部返回:

学号  姓名  班级                  课程数  平均分  加权均分  最低  最高
S005  陈静  人工智能 2023级3班     3      92.67   92.67    89.0  96.0
S003  王强  计算机科学 2023级2班   3      91.33   91.67    88.0  95.0
S001  张伟  软件工程 2023级1班     4      88.75   88.63    85.0  92.0
S002  李娜  软件工程 2023级1班     3      78.67   78.46    76.0  81.0
S004  刘洋  计算机科学 2023级2班   3      68.33   68.31    65.0  72.0

5.5 验证工具链

hermes verify 跑完整验证链:bootstrap → test → 启动服务 → 健康检查:

{"recipe": "grade-mcp-server (uv + pytest)", "ok": true,
 "phases": [{"phase": "bootstrap", "ok": true},
            {"phase": "test", "ok": true, "outputTail": "9 passed in 0.03s"}],
 "readiness": {"ready": true, "statusCode": 200}}

5.6 踩坑记录

坑 1:mcp 2.0 API 大改,导入直接失败

现象:装最新版 mcp==2.0.0 后,from mcp.server.fastmcp import FastMCP 报 ModuleNotFoundError。

原因:mcp 2.0 移除了 mcp.server.fastmcp 模块,改用新的 MCPServer API(mcp.server.mcpserver),且 stdio 握手协议与 1.x 不兼容——Hermes 内置客户端是 1.28.1,两端协议对不上,连接直接 Connection closed

修复:锁定 mcp>=1.28,<2,代码改回 FastMCP API。手动 spawn 正常、但 Hermes 连不上,是排查这个问题的关键信号——两端版本不一致。

坑 2:hermes mcp add--args 会吞掉后面的选项

现象:执行 hermes mcp add grades --command python --args -m grade_mcp.server --connect-timeout 30,server 报 unrecognized arguments: --connect-timeout 30

原因:--args 是贪婪参数,会吃掉它后面所有 token,--connect-timeout 被当成 server 的启动参数传进去了。

修复:--connect-timeout 必须放在 --args 之前:

hermes mcp add grades --command <python> --connect-timeout 30 --args -m grade_mcp.server

坑 3:交互式确认被管道输入误答,生成脏配置

现象:用 printf 'y' | hermes mcp add ... 喂交互提示,配置里多出 headers: Authorization: Bearer y(真实值就是 “y”),.env 里多了 MCP_GRADES_API_KEY=y

原因:add 流程里还有认证相关提示,一个 y 被误认为“使用 header 认证”。

修复:清掉假 token,headers 设为 {}。注意 hermes config set mcp_servers.grades.headers '{}' 会存成字符串,导致 test 崩('str' object has no attribute 'items'),要用 YAML 结构写入真正的空对象。

共性教训:给 CLI 的交互式流程喂管道输入,先确认提示顺序和数量;工具链版本(尤其协议类 SDK)先对齐再开发,省掉一整轮排查。

六、总结说明

这次实践把“开发一个 MCP Server 并接入 Agent”的完整链路走通了:FastMCP 一份代码支持 stdio 和 HTTP 双传输,SQLite 做数据层,接入 Hermes 后对话里直接查成绩。

核心收获:

  1. MCP 的价值在于协议标准化——工具定义、发现、调用全部标准化,Server 与 Client 解耦,换 Agent 框架不用重写工具。
  2. 传输模式要按场景选——本地开发用 stdio(免运维、免端口),远程部署用 StreamableHTTP(独立进程、可扩展),一份代码两行切换。
  3. 工具 docstring 就是 AI 的 API 文档——写清楚参数格式和返回结构,模型调用准确率明显提升,这是 MCP 开发区别于传统 API 开发的地方。
  4. 版本对齐是第一优先级——协议类 SDK 大版本之间不兼容,开发前先确认 Client 端版本,能省一整轮排障。

边界:当前 Server 是只读查询,没有写权限和鉴权;SQLite 只适合单机/内网,生产接真实教务系统时建议换 MySQL/PostgreSQL 并加 OAuth 认证。后续方向:接真实数据源、加写入工具、部署到云服务器(Docker/systemd 方案已就绪)。

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐