mcp server
commerce-mcp-gateway
Policy-enforcing MCP gateway for agentic commerce and payments
Set up
Runs on your computer. Pick your agent and operating system.
This project does not publish an install method we can turn into a config. Follow the README instead.
README
synced Oct 21, 2026Commerce MCP Gateway
在 Agent 与商户 / 支付 MCP Server 之间,执行可验证的授权、预算、审批与审计。
把「一次调用是否合法」扩展为「整条交易是否合法」。
这是一个面向 Agentic Commerce / AI Pay 的治理型 MCP Gateway demo。它把 Agent Harness 的 Control Plane 做成可执行、可测试、可复算的代码,而不是停留在架构图里。它不是生产支付网关,也不声称能单独防住提示词注入。
为什么做这个
Agent 可以让每一次工具调用看起来都合法,但整条交易仍然是错的:
- 先完成订单,再扣款——用户拿到货,钱却没付;
- 只捕获商品小计,少付运费和税;
- 同一个购物车创建两个支付 intent,扣两次;
- 把一次授权拆成多笔购买;
- 用一个可拼出来的假支付令牌完成订单;
- 商户在结算时把报价从 49.99 改成 60.00。
这些不是「某一次调用参数不合法」,而是 跨调用的交易上下文 没有被绑定。本项目用五条不变量把边界写进执行点,并为每一条留下测试或明确的边界说明。
在 Agent Harness 中的位置
| 平面 | 典型职责 | 本项目 |
|---|---|---|
| Orchestration Plane | Agent Loop、模型调用、工具选择、上下文管理 | 只保留一个约 100 行的薄 driver,用来把网关放进真实回路 |
| Control Plane | 授权、预算、审批、审计、策略执行 | 本项目的主体 |
这样可以单独讨论一个经常被含糊带过的问题:谁有权替用户花钱,凭什么,花完之后拿什么证明。
架构
flowchart LR
subgraph OP["Orchestration plane"]
A["Agent / Harness<br/>thin loop · MCP client"]
end
subgraph CP["Commerce MCP Gateway · control plane"]
F["Front door<br/>Streamable HTTP + stdio"]
E["Enforce<br/>single execution point"]
P["Policy<br/>deny by default"]
B["Budget<br/>reserve / commit / release"]
AP["Approval<br/>durable queue"]
S["Sanitize<br/>injection screening · PII"]
V["Evidence<br/>hash chain · replay"]
end
M["Mock merchant MCP"]
PS["Mock PSP MCP<br/>fault injection"]
DB[("SQLite<br/>budget · approvals · ledger")]
A --> F
F --> E
E --> P
E --> B
E --> AP
E -->|"allow"| M
E -->|"allow"| PS
M --> S
PS --> S
S -->|"sanitized result"| A
E --> V
B --> DB
AP --> DB
V --> DB
所有被拦截的方法调用走同一条路径:
authorize → decide → enforce → record_pending → forward
→ sanitize → settle → record_done
record_pending 在转发之前落盘,保证 证据先于副作用。没有第二条可以绕过 enforce 的写路径。
五条不变量
| 编号 | 不变量 | 当前证据 |
|---|---|---|
| I1 | 不存在未经 Decision 就放行的方法调用 | 单一执行点、分层契约、T1–T9、139 passed / 1 xfailed |
| I2 | sum(committed) + sum(active holds) ≤ limit | 50 路并发 reserve,0 超支 |
| I3 | 篡改任一证据记录必然 verify 失败 | 4/4 篡改场景检出,干净链通过 |
| I4 | 同一证据包可以重构决策序列 | 6/6 条可重构,输入件撤下后链仍可验 |
| I5 | 跨调用交易一致性:一个 checkout 一个有效 intent、完成前必须 capture、金额与 token 绑定、mandate 限制交易笔数 | demo 版已落地;仅单进程成立 |
I5 是后来补上的架构层:P0 状态攻击证明 I1–I4 全部成立时,整条交易仍然可能免单、少扣或双扣。这个发现和修复过程保留在 评测文档 与 决策记录 中。
实际执行了什么
- 默认拒绝:工具、商户、金额、资源 URI 不在策略内就不放行;
- 两阶段预算:
reserve → commit / release,SQLite WAL + 进程内单写者 + 数据库约束; - 审批绑定动作:协议原生
input_required/ MRTR,CLI 兜底;审批绑定action_hash,老客户端 fail-closed; - 可验证证据:哈希链 + 运行证据包 + Merkle 根;链上只留摘要,可复算输入单独落盘;
- 交易守卫:同一 checkout 只允许一个有效 intent,支付金额与购物车总额绑定,
complete前必须有成功capture,支付令牌必须由 PSP 签发,mandate 可限制交易笔数; - 结果净化:同时扫描
content与structuredContent的字符串叶子,标记可疑注入并脱敏 PII; - 双时代传输:Streamable HTTP 与 stdio;现代协议与握手版客户端都能接入。
快速开始
需要 Python 3.12 和 uv。
git clone https://github.com/HugoOu/commerce-mcp-gateway.git
cd commerce-mcp-gateway
uv sync
# 不需要模型 key 的确定性演示
uv run python scripts/play.py scenario
uv run python scripts/smoke_mocks.py
# 质量门
uv run pytest -q # 139 passed, 1 xfailed
uv run ruff check
手动观察网关:
uv run python -m gateway serve
uv run python scripts/play.py tools
uv run python -m gateway approvals
uv run python -m gateway verify
接入真实模型(OpenAI 兼容端点):
$env:OPENAI_API_KEY = "your-key"
$env:OPENAI_BASE_URL = "https://opencode.ai/zen/go/v1"
$env:MODEL_NAME = "deepseek-v4.1-flash"
uv run python scripts/run_w2_demo.py
# macOS / Linux
export OPENAI_API_KEY="your-key"
export OPENAI_BASE_URL="https://opencode.ai/zen/go/v1"
export MODEL_NAME="deepseek-v4.1-flash"
uv run python scripts/run_w2_demo.py
scripts/run_w2_demo.py 会拉起 mock merchant / PSP、启动网关、运行 agent 回路,并打印网关证据日志。
Demo(纯 CLI)
一条命令演出正路、两层拒绝、F14 真实轨迹回放、审批与审计:
uv run python scripts/demo.py # 约 5 分钟;不需要模型 key
uv run python scripts/demo.py --fast # 约 70 秒
uv run python scripts/demo.py --deep # 额外复现 guard-off 的历史 bug
uv run python scripts/demo.py --json # 机器可读结果
F14 是 qwen3.8-27b 的真实轨迹回放:N=5 探索性,2/5 是尝试率,攻击成功率是 0/5,0 双扣。完整口播稿、预期输出与录制方式见 demo.md。
评测摘要
评测按 claim-first 组织:每条声称先写判定口径,再看证据能不能支撑它。
| 声称 | 证据 | 状态 |
|---|---|---|
| 每次被拦截调用都经过 Decision | T1–T9、三条越权通道、分层契约、139 passed / 1 xfailed | 支持 |
| 并发下预算不超支 | 50 路并发,0 超支,hold 回收 | 支持 |
| 审批绑定到具体动作 | form / CLI / 老客户端 fail-closed、action_hash | 支持 |
| 证据可验、决策可重构 | 4/4 篡改检出、6/6 重构 | 支持 |
| 越权尝试被端到端拒绝 | scripted adversary:金额越权 30/30、先履约后扣款 30/30、0 绕过 | 支持(compromised harness 阳性对照) |
| 并发重复 intent 被串行化 | 修复前 30/30 双 intent → 修复后 0/30,0 孤儿 | 支持(单进程) |
| 模型会尝试交易级越权 | F14:2/5 次尝试拆单,2/2 被拒,基线 0/5 | 探索性,N=5 |
| 信念层会被商户内容操纵 | P2:baseline 0/30 vs injected 30/30 | 模型层发现,不是网关声称 |
诚实的边界
- 单进程:交易守卫的状态在内存里。重启后
max_transactions计数归零;重启探针中第二笔购买 10/10 被放行。SQLite / 多副本是 future work。 - 价格漂移:checkout 报 49.99、订单变 60.00 时,5/5 少付。网关当前信任商户在 checkout 时给出的总额;签名报价或 complete 时总额校验是 future work。
- 提示词注入:关键词筛查能标记明显注入,但抓不到语义操纵。F8 的 Branded Whisper 是已知残余。
- 模型侧证据不是网关证据:信念探针说明模型会把商户声明当事实,不能拿它证明网关有效或无效。
- F14 是 N=5 探索性:不能把它写成稳定的 40% 尝试率。
- 部署边界:Agent 如果绕过网关直接调用 PSP 的 HTTP 端点,网关拦不住。这是部署问题,不是协议内控制问题。
项目结构
src/gateway/ 控制面:frontdoor / policy / enforce / budget / approval / evidence / sanitize
src/agent/ 薄循环 driver:MCP client + 可替换的 ModelClient
src/mcp_servers/ mock merchant、mock PSP、故障注入
eval/ 实验、探针、统计与红队脚本
scripts/ 演示与冒烟脚本
demo/ CLI 演示与 F14 脱敏轨迹 fixture
tests/ 单元、集成、对抗与不变量测试
docs/ 架构、决策、威胁模型、评测与复现记录
文档
| 文档 | 中文 | English |
|---|---|---|
| 架构 | architecture.md | architecture.en.md |
| 评测设计 | evaluation.md | evaluation.en.md |
| 威胁模型 | threat-model.md | threat-model.en.md |
| 风控决策 | risk-decisioning.md | risk-decisioning.en.md |
| 架构决策记录 | decisions.md | decisions.en.md(索引) |
| Demo | demo.md | demo.en.md |
| 代码导览 | code-tour.md | 中文 |
| mock 世界 | mock-world.md | 中文 |
| 红队记录 | redteam-log.md | 中文 |
| 实验设计 | w4-experiment-design.md | 中文 |
License
MIT。见 LICENSE。
如果这个项目对你有帮助,欢迎 star。