Expert-Appointment-Booking-MCP-Server
专家预约 MCP Server
通过 MCP 向 AI Agent 暴露一组专家预约工具:查询专家 → 查询可约时段 → 临时占位 5 分钟 → 用户站外付款 → 确认预约 → 查询结果。
传输方式 stdio,存储 SQLite,六个工具,216 条自动化测试。
配套文档:PLAN.md(方案与决策清单)· developer_guide.md(架构与流程)· TRADEOFFS.md(没做什么、为什么)· NOTES.md(一页纸说明)· CLAUDE.md(工程约束)
环境要求
项 | 要求 |
Python | 3.12( |
包管理 | uv(唯一入口,不要用 pip / venv) |
操作系统 | macOS / Linux(Windows 未验证) |
网络 | 仅首次 |
运行时依赖只有三个:mcp==2.2.0、pydantic>=2.12、anyio>=4.9。存储用标准库 sqlite3,不引入 ORM。
安装
uv sync若尚未安装 uv:curl -LsSf https://astral.sh/uv/install.sh | sh
运行测试
uv run pytest预期 216 passed,约 2 秒。测试不联网、不写仓库目录、不含任何 sleep——时间由可推进的假时钟驱动,所以"5 分钟过期"是秒级确定性验证的。
变异测试(可选,约 1 分钟):
uv run mutmut run运行 Demo
uv run booking-demo一条命令跑完三条路径并打印状态流转:
完整快乐路径 —— 查专家 → 查时段 → 占位 → 付款 → 确认 → 查询
失败路径:占位过期 → 被他人抢走 → 迟到付款 → 需退款
失败路径:支付服务查不通 —— 状态不变、时段不释放
Demo 每次使用全新临时库、固定起始时刻与确定性 ID,连跑两次输出逐字一致。全部调用经由真实的 MCP Client/Server,不是直接调内部函数。
启动 MCP Server
BOOKING_DB_PATH=.data/bookings.sqlite3 \
BOOKING_PAYMENT_BASE_URL=https://payments.example.com/bookings \
BOOKING_SEED_DEMO=true \
uv run booking-mcpServer 以 stdio 通信,前台运行、不打印任何东西到 stdout(日志一律走 stderr)。等价写法:uv run python -m expert_booking_mcp.mcp.server。
环境变量
变量 | 必填 | 说明 |
| 是 | SQLite 文件路径。相对路径以启动时的工作目录为基准,接入 Host 时建议用绝对路径 |
| 是 | 支付页地址前缀。须 https、不含用户名密码、不含 query/fragment。只用于拼接地址,服务器绝不访问它 |
| 否 | 默认 3,须 |
| 否 |
|
配置在启动时全量校验,任何一项不合法即拒绝启动并说明原因。Server 不会自动加载 .env,请由 MCP Host 或 shell 提供。字段说明见 .env.example。
接入 MCP Client
方式一:Claude Desktop
编辑 claude_desktop_config.json(macOS 路径 ~/Library/Application Support/Claude/claude_desktop_config.json),把 /ABS/PATH 换成本仓库的绝对路径:
{
"mcpServers": {
"expert-booking": {
"command": "/ABS/PATH/.venv/bin/python",
"args": ["-m", "expert_booking_mcp.mcp.server"],
"env": {
"BOOKING_DB_PATH": "/ABS/PATH/.data/bookings.sqlite3",
"BOOKING_PAYMENT_BASE_URL": "https://payments.example.com/bookings",
"BOOKING_SEED_DEMO": "true"
}
}
}
}直接指向 .venv/bin/python 而不是 uv,可以免去 Host 环境里 uv 是否在 PATH 上的问题。改完重启 Claude Desktop。
方式二:MCP Inspector
npx @modelcontextprotocol/inspector \
--command "$PWD/.venv/bin/python" \
--args "-m,expert_booking_mcp.mcp.server" \
-e BOOKING_DB_PATH="$PWD/.data/bookings.sqlite3" \
-e BOOKING_PAYMENT_BASE_URL=https://payments.example.com/bookings \
-e BOOKING_SEED_DEMO=true验证 Client 能发现并调用工具
最快的一条:跑内置探针
uv run booking-probe它会把 Server 当成真正的子进程用 stdio 拉起来(不是内存对接),列出全部工具及其入参,然后实调一次。预期输出以 ✓ 子进程启动成功,发现 6 个工具 开头。
在 Inspector / Claude Desktop 里手工验证
发现:连接后应看到 6 个工具 ——
list_experts、search_available_slots、hold_slot、confirm_booking、get_booking、cancel_hold。调用检索:
list_experts,参数{"query": "玉米病虫害"}。应返回status: "OK",命中张岚,且matched_on里写明命中依据。调用查档:
search_available_slots,参数{"from_time": "<明天>T12:00:00+08:00", "to_time": "<明天>T18:00:00+08:00"}。时间必须带时区。占位:
hold_slot,参数{"slot_id": "<上一步的 slot_id>", "user_id": "u-1"}。返回status: "HELD"、booking_id、payment_url与expires_in_seconds。确认:
confirm_booking,参数{"booking_id": "<上一步>", "user_id": "u-1"}。因为没有真实支付,预期status: "PAYMENT_PENDING",且占位仍然有效——这正是正确行为。入参校验:故意把
from_time写成不带时区的2026-01-01T00:00:00,应收到isError且消息指明「from_time 必须带时区」。
想看到确认成功,请跑
uv run booking-demo—— 那里的支付查询是可编排的。
在 Claude Desktop 里也可以直接说:「帮我预约明天下午一位擅长玉米病虫害的专家」,观察它依次调用上述工具。
已完成
六个正交工具,输入输出契约在 PLAN.md §6 冻结,含统一返回信封(
status/message/next_action)与 15 个机器可读状态码。完整的预约状态机:
HELD / CONFIRMED / EXPIRED / CANCELLED / PAYMENT_FAILED / REFUND_REQUIRED,含迟到付款的通用和解规则。时段唯一占用:应用层事务 + SQLite 部分唯一索引双保险,8 线程并发抢占测试验证。
5 分钟惰性过期:可用性是带
now的派生量,读路径不写库。支付查询端口与可编排替身;金额与币种校验。
216 条自动化测试(领域 / 适配器 / 工具三层)+ 变异测试(1272 个变异体,906 杀)。
可重复 Demo 与 跨进程 stdio 探针。
预置 3 位专家、未来 7 天共 63 个时段。
未完成 / 不在范围
题面已排除的:真实支付服务与支付页、前端、完整认证、退款执行、通知、运营后台、云部署。
我主动裁掉的(理由见 TRADEOFFS.md):
未做 | 一句话理由 |
鉴权 |
|
退款闭环 | 只产出 |
多实例部署 | SQLite + 单进程假设,横向扩展需换存储 |
专家 / 时段管理 | 题面明确不要求,只提供预置数据 |
自然语言时间解析 | 「明天下午」由 Agent 换算,Server 只收绝对时间 |
语义检索 | 中文子串匹配,不做同义词与向量 |
游标分页 | 只有 |
MCP Resources / Prompts | 只暴露 Tools |
Windows 支持 | 未验证 |