Expert-Appointment-Booking-MCP-Server
by lch-ai222
README.md
# 专家预约 MCP Server
通过 MCP 向 AI Agent 暴露一组专家预约工具:**查询专家 → 查询可约时段 → 临时占位 5 分钟 → 用户站外付款 → 确认预约 → 查询结果**。
传输方式 stdio,存储 SQLite,六个工具,216 条自动化测试。
> 配套文档:[PLAN.md](PLAN.md)(方案与决策清单)· [developer_guide.md](developer_guide.md)(架构与流程)· [TRADEOFFS.md](TRADEOFFS.md)(没做什么、为什么)· [NOTES.md](NOTES.md)(一页纸说明)· [CLAUDE.md](CLAUDE.md)(工程约束)
---
## 环境要求
| 项 | 要求 |
|---|---|
| Python | **3.12**(`>=3.12,<3.13`,见 `.python-version`) |
| 包管理 | **uv**(唯一入口,不要用 pip / venv) |
| 操作系统 | macOS / Linux(Windows 未验证) |
| 网络 | 仅首次 `uv sync` 需要 |
运行时依赖只有三个:`mcp==2.2.0`、`pydantic>=2.12`、`anyio>=4.9`。存储用标准库 `sqlite3`,不引入 ORM。
## 安装
```bash
uv sync
```
若尚未安装 uv:`curl -LsSf https://astral.sh/uv/install.sh | sh`
## 运行测试
```bash
uv run pytest
```
预期 `216 passed`,约 2 秒。测试不联网、不写仓库目录、**不含任何 `sleep`**——时间由可推进的假时钟驱动,所以"5 分钟过期"是秒级确定性验证的。
变异测试(可选,约 1 分钟):
```bash
uv run mutmut run
```
## 运行 Demo
```bash
uv run booking-demo
```
一条命令跑完三条路径并打印状态流转:
1. **完整快乐路径** —— 查专家 → 查时段 → 占位 → 付款 → 确认 → 查询
2. **失败路径:占位过期 → 被他人抢走 → 迟到付款 → 需退款**
3. **失败路径:支付服务查不通** —— 状态不变、时段不释放
Demo 每次使用全新临时库、固定起始时刻与确定性 ID,**连跑两次输出逐字一致**。全部调用经由真实的 MCP Client/Server,不是直接调内部函数。
## 启动 MCP Server
```bash
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` 换成本仓库的绝对路径:
```json
{
"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
```bash
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 能发现并调用工具
### 最快的一条:跑内置探针
```bash
uv run booking-probe
```
它会**把 Server 当成真正的子进程用 stdio 拉起来**(不是内存对接),列出全部工具及其入参,然后实调一次。预期输出以 `✓ 子进程启动成功,发现 6 个工具` 开头。
### 在 Inspector / Claude Desktop 里手工验证
1. **发现**:连接后应看到 6 个工具 —— `list_experts`、`search_available_slots`、`hold_slot`、`confirm_booking`、`get_booking`、`cancel_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_id`、`payment_url` 与 `expires_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](PLAN.md) 冻结,含统一返回信封(`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](TRADEOFFS.md)):
| 未做 | 一句话理由 |
|---|---|
| 鉴权 | `user_id` 由调用方提供、不校验身份,位于信任边界之外 |
| 退款闭环 | 只产出 `REFUND_REQUIRED` 标记,不执行退款 |
| 多实例部署 | SQLite + 单进程假设,横向扩展需换存储 |
| 专家 / 时段管理 | 题面明确不要求,只提供预置数据 |
| 自然语言时间解析 | 「明天下午」由 Agent 换算,Server 只收绝对时间 |
| 语义检索 | 中文子串匹配,不做同义词与向量 |
| 游标分页 | 只有 `limit` + `truncated` |
| MCP Resources / Prompts | 只暴露 Tools |
| Windows 支持 | 未验证 |
This server cannot be deployed
Maintenance
ActivityMaintained
ResponsivenessNo issues