Skip to main content
Glama
Dante-dan

expert-booking-mcp

by Dante-dan

Expert Booking MCP Server

一个面向 AI Agent、支持渐进式工具发现的专家预约 MCP Server。默认端点采用 Streamable HTTP 和 MCP 2026-07-28 modern era 的 server/discover;为保证终态响应时间可验证,旧版 stateless/SSE fallback 会被明确拒绝,不做静默降级。

一条命令验证

npm install && npm test

测试会真实启动 Streamable HTTP MCP 端点,用官方 Client 完成能力发现、工具发现与工具调用,并覆盖预约状态机。完整可重复 Demo:

npm run demo

启动持久化服务:

cp .env.example .env
set -a; source .env; set +a
npm run dev
# MCP endpoint: http://127.0.0.1:3000/mcp

生产构建使用 npm run build && npm start。服务默认仅绑定 localhost,并启用 Host/Origin 校验;远程暴露前必须另加认证。

设计动机、现实风险和未覆盖范围见 NOTES.md

给本地 Agent 的安装提示词

把下面内容复制给需要安装 MCP 的编码 Agent:

请在当前仓库安装并接入 expert-booking-mcp:

1. 确认 Node.js >= 22,运行 npm ci。
2. 运行 cp .env.example .env && npm run dev,MCP 地址是 http://127.0.0.1:3000/mcp。
3. 按当前 MCP Host 的配置方式添加这个 Streamable HTTP Server,不要改成 stdio。客户端请求需复用 src/temporal-http.ts 的 createTemporalClientFetch,以附带服务要求的动态时间字段。
4. 连接后确认 tools/list 能发现 tool_catalog.search、tool_catalog.read、tool_catalog.call,并通过目录工具搜索、读取和调用 booking 工具。
5. 运行 npm test 验证安装;需要查看完整预约流程时运行 npm run demo。付款由用户在 Agent 外完成,不要实现或代替付款。

如果失败,请保留原始错误并定位原因,不要通过关闭参数校验、时间校验或 Host/Origin 校验来绕过问题。最后告诉我 MCP 配置位置、实际运行的命令和验证结果。

Agent 使用流程

默认 tools/list 只返回三个稳定、低上下文成本的 MCP Tool:

MCP Tool

用途

tool_catalog.search

检索式预筛选;返回标题、tool:// 位置、摘要、总数和 nextCursor

tool_catalog.read

通过 offset/limit 分片读取固定的完整 schema,明确报告截断与下一段位置

tool_catalog.call

以未经转换的 arguments 调用已加载的逻辑工具

目录中的四个固定逻辑工具是:

逻辑工具

关键输入

结果

booking.search_available_slots

query、显式 limit、可选时间/专业过滤和 cursor

结构化时段候选、总数、下一页游标

booking.hold_slot

slotId、原样保存的客户信息、UUID idempotencyKey

五分钟独占预约、支付页、完整快照和 Server 签发的 confirmationIntent

booking.confirm_booking

bookingIdexpectedVersion、UUID 幂等键、两分钟内且绑定 intent 的人工批准

intent 与支付双重校验后的完整确认快照

booking.get_booking

bookingId

当前完整预约快照

推荐 Host 轨迹:

  1. server/discover:发现 MCP 能力与协议版本。

  2. tools/list:仅加载上述三个目录工具。

  3. tool_catalog.search:搜索相关逻辑工具;状态栏只保留返回的工具名。

  4. tool_catalog.read:按需读 schema;读完整后将 schema 一次追加到上下文尾部,此后留在原历史位置以命中前缀缓存。

  5. tool_catalog.call:把用户/Agent 给出的业务参数原样送入严格校验和执行。

第 3–5 步是服务提供的协议能力;“把 schema 放到对话历史的哪个位置”最终由 MCP Host 控制,Server 通过 instructions、固定 schema 和分页元数据给出可执行约定。

状态机与事务

AVAILABLE SLOT
     │ hold (SQLite IMMEDIATE transaction)
     ▼
HELD / payment=PENDING ───── expiresAt reached ────▶ EXPIRED
     │
     ├── queryPayment=PENDING ─────────────────────▶ HELD
     ├── queryPayment=FAILED  ─────────────────────▶ PAYMENT_FAILED
     ├── queryPayment=CLOSED  ─────────────────────▶ PAYMENT_CLOSED
     └── fresh human approval + exact PAID match ──▶ CONFIRMED
  • one_active_booking_per_slot 部分唯一索引从存储层保证每个时段最多一个 HELD/CONFIRMED 预约。

  • 临时预约的 expiresAt = heldAt + 5 分钟。每次相关读写前在同一事务中清理过期预约,因此进程重启后也不会丢失释放动作。

  • 本地状态变更全部位于 SQLite 事务内;异常会回滚整个本地变更。只读支付查询在事务外执行,返回后重新读取版本并在一个事务中提交最终状态,避免持锁等待外部网络。

  • 支付初始状态只能是 PENDING,确认只允许本地 PENDING → PAIDbookingId 同时是不可变、唯一的 payment.queryId,重复确认查询同一个支付对象。

  • expectedVersion 提供乐观并发控制。Hold 与 Confirm 各自要求 UUID 幂等键;同键同参返回原结果,同键异参返回 IDEMPOTENCY_CONFLICT

  • FAILED/CLOSED 会事务性释放时段。支付已经成功但预约在外部查询完成前过期时,服务拒绝确认;退款/人工对账按题目范围留给外围系统。

参数保真与错误契约

逻辑工具由 AJV 严格校验:coerceTypes=falseuseDefaults=falseremoveAdditional=false、所有对象 additionalProperties=false。例如邮箱不会转小写,数字字符串不会转换为数字,未知字段不会被丢弃。业务参数与调用上下文分开传递,日志上下文不会注入业务参数。

所有执行结果都是机器可读 envelope。写操作返回完整的变更后预约,而非空 200 OK

{
  "ok": false,
  "error": {
    "code": "PAYMENT_PENDING",
    "message": "Payment has not completed; the booking remains held until expiresAt.",
    "retryable": true,
    "details": { "bookingId": "booking-..." },
    "recovery": { "action": "Ask the user to finish payment, then retry confirmation before expiry." }
  },
  "meta": {
    "traceId": "32 lowercase hex characters",
    "spanId": "16 lowercase hex characters",
    "operationId": "UUID",
    "clientSentAt": "RFC 3339",
    "serverReceivedAt": "RFC 3339",
    "serverResultCreatedAt": "RFC 3339",
    "cache": "BYPASS"
  }
}

输入 schema 错误由 MCP SDK 或 INVALID_ARGUMENTS 返回;可预期业务错误不会泄露堆栈。主要错误包括 SLOT_UNAVAILABLEBOOKING_EXPIREDVERSION_CONFLICTPAYMENT_PENDINGPAYMENT_MISMATCHHUMAN_APPROVAL_EXPIREDAPPROVAL_INTENT_HASH_MISMATCHAPPROVAL_INTENT_MISMATCHIDEMPOTENCY_CONFLICT

Human in the loop

临时预约是可自动重试、会自动释放的动作。最终确认必须携带:

{
  "humanApproval": {
    "approvalId": "UUID",
    "decision": "APPROVE",
    "approvedAt": "2026-08-26T09:30:00.000Z",
    "intent": {
      "bookingId": "booking-...",
      "slotId": "slot-ai-001",
      "startsAt": "2030-09-02T02:00:00.000Z",
      "amount": "500.00",
      "currency": "CNY",
      "version": 1
    },
    "intentHash": "sha256:<64 lowercase hex>"
  }
}

hold_slot 和处于 HELD/PENDINGget_booking 会返回 confirmationIntent。Host 必须向用户展示对应的 booking、专家、时段、金额、币种和版本,并把其中的 intentintentHash 原样复制到批准对象;不要由 Agent 重建或修改。Server 先重新计算 hash,再把六个字段与当前 booking 精确比较,任一不一致都不会查询支付。

v1 canonical intent 是按 key 升序排列的无空白 UTF-8 JSON:amount, bookingId, currency, slotId, startsAt, versionintentHashsha256: 加该字节串的 64 位小写十六进制 SHA-256。Server 返回 hash,因此一般 Client 只需原样传递。这个无密钥 hash 绑定内容完整性,但不是对批准者身份的数字签名;需要抵御不可信 Host 时,应把 v1 binding 升级为 Server HMAC 或签名 challenge。

批准有效期为 120 秒,允许 5 秒时钟偏差;缺失、过期、未来时间、缺少 binding 或 binding 不匹配的默认行为都是 不查询支付、不确认预约。确认成功后 confirmationIntent 变为 null,避免把旧 intent 当成可再次批准的 challenge。

支付依赖 seam

预约模块只依赖下面的只读 port:

interface PaymentQueryPort {
  queryPayment(bookingId: string, trace: TraceContext): Promise<PaymentResult>;
}

生产 adapter 对 PAYMENT_QUERY_BASE_URL/payments/{bookingId} 发 GET,请求超时 3 秒并传递 traceparent。测试和 Demo 注入 Stub;仓库没有实现支付服务或支付动作。支付结果必须精确匹配 bookingId、金额字符串和币种,PAID 还必须有 paidAt

按需加载、分页与缓存

  • Tool 搜索和时段搜索都返回结构化候选,不拼接全文。

  • 游标是与原查询绑定的不透明 token;跨查询复用会返回 INVALID_CURSOR

  • tool_catalog.read 以 0-based offset 和显式 limit 读取,返回 totalLinestruncatedomittedLinesnext 和人类可读继续方式。

  • 固定 Tool 目录的 search/read 结果可永久安全复用;可预约时段查询缓存 5 秒,任何预约写入或过期释放都会使其失效。

  • SQLite WAL 允许多个只读感知调用安全并发;写入由数据库事务和唯一索引串行化。

可观测性

每个目录动作和逻辑工具动作都向 stderr 输出 JSON Lines,包含 source、RFC 3339 timestampcaller、动作、耗时、trace_idspan_idoperation_id。支付查询创建子 span 并通过 W3C traceparent 传播。

调用方显式传入 UUID requestSpanId;服务用它确定性派生 128-bit trace_id,每次动作生成新的 64-bit span_id。OpenTelemetry 标准要求 span_id 是 16 个十六进制字符而不是 UUID,因此另行生成 UUID operation_id,没有伪造不兼容的 trace context。代码同时调用 OpenTelemetry API;部署方可注册 exporter。

所有 MCP HTTP 请求必须由显式 client adapter 在真正发送边界加入 x-mcp-client-sent-at,并在 params._meta 写入同值;Server 不会替 Client 猜测或注入该时间。Server 在入口和响应边界分别加入 x-mcp-server-received-atx-mcp-server-responded-at,JSON-RPC result._meta 同样携带三段时间;Tool envelope 另以 serverResultCreatedAt 标识业务结果生成时刻。因此 Agent 可验证:

clientSentAt ≤ serverReceivedAt ≤ serverResultCreatedAt ≤ serverRespondedAt ≤ clientReceivedAt

缺失或非法 RFC 3339 客户端时间会得到 HTTP 400 / MISSING_CLIENT_TIMESTAMP,响应不会伪造客户端时间,并回显 JSON-RPC request id。支付查询这个 Server 发出的外部 GET 也携带 x-mcp-server-request-sent-at;依赖响应的发送时间(若对方提供)及本地接收时间进入结构化日志。当前工具响应均为 JSON;未来启用 SSE 进度/Server-initiated MCP request 前,需要为每个事件补独立发送时间,而不能只沿用建流 Header。

最重要的测试场景

  1. 竞争与过期释放:先验证第二位用户无法占用,再推进时钟超过五分钟并验证其可预约。这直接覆盖“同一时段仅一人”和“过期后重新开放”两个核心不变量。

  2. 先查后做的支付确认PENDING 保持预约不变,随后同一支付查询 ID 返回 PAID,只发生一次本地 PENDING → PAID/CONFIRMED。这是外部付款与本地事务交界处风险最高的路径。

  3. 金额/身份不匹配回滚:支付返回 PAID 但金额错误,本地仍保持 HELD/PENDING/version=1,证明外部查询无法造成部分提交。

  4. 幂等重放与参数漂移:同键同参得到同一预约;同键异参明确冲突,防止 Agent 重试制造重复副作用。

  5. Human approval 超时:过期批准不会调用支付依赖,验证默认拒绝行为。

  6. 真实 MCP modern discovery:官方 Client 通过 Streamable HTTP 协商到 modern,发现三项稳定工具,再检索和调用逻辑工具。

  7. 独立进程时序黑盒:测试先构建,再 spawn 生产入口,用官方 Client 验证每次 wire request/response、Tool envelope 与日志的时间顺序,并确保子进程和临时数据库被清理。

假设与范围

  • 预置三位专家和六个 2030 年 UTC 时段;不实现专家管理。

  • 所有时间通过 RFC 3339 传输,存储为 UTC ISO 字符串;服务不静默转换调用参数。

  • 跨机器时序比较假设 Client、预约服务和支付服务通过 NTP 保持时钟同步;生产告警应配置允许偏差。Server 不根据偏差静默改写调用方时间。

  • 金额用两位小数字符串,币种用三位大写代码,避免浮点误差。

  • 最小实现没有账户认证;localhost 运行且 booking ID 不可猜。生产环境必须在 MCP handler 前验证身份,并按 caller 限制预约读取。

  • 付款页 URL 仅由配置基址和 booking ID 组成;本仓库不提供页面。

  • 退款、通知、后台、已付款但过期后的自动对账,以及云部署不在范围内。

项目结构

src/
  booking-service.ts   深预约模块:状态机、幂等、事务、缓存
  database.ts          SQLite schema、约束与预置数据
  tool-catalog.ts      固定逻辑 schema、检索、分片读取、严格分发
  mcp-server.ts        三个稳定 MCP Tool 与 Streamable HTTP handler
  payment.ts           只读支付 HTTP adapter
  observability.ts     OTel hooks 与结构化日志
  temporal-http.ts     MCP Client/Server wire 时间契约
test/                  状态机与真实 HTTP MCP 集成测试
scripts/demo.ts        可重复端到端演示
NOTES.md               设计动机、现实风险、取舍与明确未覆盖范围

实现对齐官方 MCP TypeScript SDK HTTP GuideMCP Tools GuideStreamable HTTP 2026-07-28 规范MCP Pagination 规范

Related MCP Connectors