Skip to main content
Glama
lch-ai222

Expert-Appointment-Booking-MCP-Server

by lch-ai222

专家预约 MCP Server

通过 MCP 向 AI Agent 暴露一组专家预约工具:查询专家 → 查询可约时段 → 临时占位 5 分钟 → 用户站外付款 → 确认预约 → 查询结果

传输方式 stdio,存储 SQLite,六个工具,216 条自动化测试。

配套文档:PLAN.md(方案与决策清单)· developer_guide.md(架构与流程)· TRADEOFFS.md(没做什么、为什么)· NOTES.md(一页纸说明)· CLAUDE.md(工程约束)


环境要求

要求

Python

3.12>=3.12,<3.13,见 .python-version

包管理

uv(唯一入口,不要用 pip / venv)

操作系统

macOS / Linux(Windows 未验证)

网络

仅首次 uv sync 需要

运行时依赖只有三个:mcp==2.2.0pydantic>=2.12anyio>=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

一条命令跑完三条路径并打印状态流转:

  1. 完整快乐路径 —— 查专家 → 查时段 → 占位 → 付款 → 确认 → 查询

  2. 失败路径:占位过期 → 被他人抢走 → 迟到付款 → 需退款

  3. 失败路径:支付服务查不通 —— 状态不变、时段不释放

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-mcp

Server 以 stdio 通信,前台运行、不打印任何东西到 stdout(日志一律走 stderr)。等价写法:uv run python -m expert_booking_mcp.mcp.server

环境变量

变量

必填

说明

BOOKING_DB_PATH

SQLite 文件路径。相对路径以启动时的工作目录为基准,接入 Host 时建议用绝对路径

BOOKING_PAYMENT_BASE_URL

支付页地址前缀。须 https、不含用户名密码、不含 query/fragment。只用于拼接地址,服务器绝不访问它

BOOKING_PAYMENT_TIMEOUT_SECONDS

默认 3,须 0 < x <= 30

BOOKING_SEED_DEMO

true 时启动补齐未来 7 天的预置专家与时段;默认 false(空库)

配置在启动时全量校验,任何一项不合法即拒绝启动并说明原因。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 里手工验证

  1. 发现:连接后应看到 6 个工具 —— list_expertssearch_available_slotshold_slotconfirm_bookingget_bookingcancel_hold

  2. 调用检索list_experts,参数 {"query": "玉米病虫害"}。应返回 status: "OK",命中张岚,且 matched_on 里写明命中依据。

  3. 调用查档search_available_slots,参数 {"from_time": "<明天>T12:00:00+08:00", "to_time": "<明天>T18:00:00+08:00"}。时间必须带时区

  4. 占位hold_slot,参数 {"slot_id": "<上一步的 slot_id>", "user_id": "u-1"}。返回 status: "HELD"booking_idpayment_urlexpires_in_seconds

  5. 确认confirm_booking,参数 {"booking_id": "<上一步>", "user_id": "u-1"}。因为没有真实支付,预期 status: "PAYMENT_PENDING",且占位仍然有效——这正是正确行为。

  6. 入参校验:故意把 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):

未做

一句话理由

鉴权

user_id 由调用方提供、不校验身份,位于信任边界之外

退款闭环

只产出 REFUND_REQUIRED 标记,不执行退款

多实例部署

SQLite + 单进程假设,横向扩展需换存储

专家 / 时段管理

题面明确不要求,只提供预置数据

自然语言时间解析

「明天下午」由 Agent 换算,Server 只收绝对时间

语义检索

中文子串匹配,不做同义词与向量

游标分页

只有 limit + truncated

MCP Resources / Prompts

只暴露 Tools

Windows 支持

未验证