euuuuuuan/crm-mcp-agent-public
GitHub: euuuuuuan/crm-mcp-agent-public
一个探索治理边界的 CRM 营销 agent 原型,通过纯标准库治理内核在 MCP 协议上控制 agent 的读写权限和审批流程。
Stars: 0 | Forks: 0
# crm-mcp-agent
**由 agent 通过 MCP 完成 CRM/生命周期营销人员的 SQL 工作——背后有一个治理内核决定 agent 的权限边界、如何被停止,以及记录哪些内容。**
[](LICENSE)
[](#快速开始)
[](requirements.txt)
[](#开发)
[](#构建方式)
[](#这是什么)
**English** · [한국어](docs/i18n/README.ko_KR.md) · [日本語](docs/i18n/README.ja_JP.md) · [简体中文](docs/i18n/README.zh_CN.md) · [繁體中文](docs/i18n/README.zh_TW.md) · [Español](docs/i18n/README.es.md) · [Português (Brasil)](docs/i18n/README.pt_BR.md) · [Deutsch](docs/i18n/README.de_DE.md) · [Français](docs/i18n/README.fr_FR.md) · [Русский](docs/i18n/README.ru_RU.md) · [Italiano](docs/i18n/README.it_IT.md) · [Bahasa Indonesia](docs/i18n/README.id_ID.md) · [Türkçe](docs/i18n/README.tr_TR.md)
[快速开始](#quickstart) · [架构](#architecture) · [设计决策](docs/DECISIONS.md) · [治理内核](gov/README.md) · [鸣谢](CREDITS.md)
## 这是什么
这是一个针对一个特定问题的原型:一个与专为特定目的构建的小型 MCP server 通信的 agent,能否完成 CRM 营销人员通常通过针对客户数据平台手写 SQL 来进行的临时细分和营销活动查询——并且能否让写入端足够安全,从而完全暴露出来?它内置了一个确定性的合成 CDP,五个具有严格非 PII 边界的只读 MCP 工具,以及一个纯标准库的治理内核,所有写入端的调用都必须通过它。它**是一个原型,而不是产品,并且明确声明不与任何营销自动化供应商集成**:其中没有任何供应商 SDK、账户或凭证,并且 `execute_campaign` 是一个*模拟器*,它会将回执追加到本地账本中——它不会在任何里程碑节点调用外部 API。这里真正有价值的是机制:分层、绑定指纹的审批、熔断开关、源自审计日志的上限,以及旨在破坏所有这些机制的 45 个对抗性测试。
## 亮点
- **108 个测试在 0.772 秒内完成**,在此快照中测量:**53** 个单元测试加上 **55** 个对抗性和场景测试——**33** 个 PII 红队攻击,**12** 个治理红队攻击,以及 **10** 个端到端治理场景。
- **唯一一个第三方依赖**,且锁定在主版本:`mcp>=1,<2`。其他所有内容——生成器、治理内核、守卫、agent 循环、测试——都是标准库。已在 Python **3.14.6** 上针对 `mcp` **1.28.1** 进行验证。
- **可证明的确定性,而非仅仅是口头声明。** 发布门控使用相同的 seed 两次生成合成 CDP 并比较 SHA-256 摘要;它们在字节上是完全相同的。生成过程从不读取系统时钟——180 天的窗口锚定在一个固定的日历日期上。
- **合成 CDP,在 seed 为 42 时测得**:5,000 名客户、99,922 个事件、40 个营销活动、1,225 行每日营销活动结果、12 个基于规则的细分,以及跨 7 个表的 8,078 个细分成员资格。
- **五个只读 MCP 工具**(`segment_query`、`campaign_history`、`campaign_performance`、`audience_overlap`、`budget_status`)和**三个写入端工具**(`propose_campaign`、`execute_campaign`、`read_audit_log`),每个都映射到 **4** 个权限级别之一。
- **具有三条独立防线的非 PII 守卫**,因此一个 bug 不会导致泄露:默认拒绝的允许列表(PII 列不出现在任何允许列表中)、一个递归的输出端清洗器,在发现任何形状类似电话/电子邮件/RRN 的字符串时会直接中断调用并设为失败状态,并且完全没有列注入攻击面——字段和操作符名称仅作为硬编码 SQL 片段的字典键,且只有值是绑定参数。
- **位于 8 个标准库模块中的治理内核**,其防护栏按成本最低且最绝对的原则优先检查:熔断开关 → 断路器 → 速率上限 → 审批代理 → 只追加的决策日志。全程采用失败即关闭:无法读取的熔断状态被视为已触发,损坏的断路器状态被视为已跳闸。
- **无法重放的授权。** 审批绑定到 `SHA-256(tool + "\n" + canonical_json(args))`,由单个原子 UPDATE 消耗,因此并发消费者不可能同时成功,并且在 TTL 到期后失效(默认 600 秒,上限 3,600 秒)。不可逆层的上限被故意设定得很严格:每天 3 次,每周 10 次。
- **每次检查时都从审计日志重新推导上限**,因此计数器不会与记录不同步——日志*即*计数器。
- **[`docs/DECISIONS.md`](docs/DECISIONS.md) 中的 19 项编号设计决策**,包括那些反对显而易见选择的决策。
## 使用它
两个操作界面,以及一个所有者控制台:
| 界面 | 它是什么 | 权限 |
|---|---|---|
| MCP server | 一个 FastMCP stdio server。将任何 MCP client 指向它。使用 `mode=ro` 打开 SQLite,因此即使是试图写入的 bug 也会被数据库本身拒绝。 | 只读 (T0) |
| 写入工具 | `propose_campaign`(草稿,自我批准但需审计且有上限限制)、`execute_campaign`(模拟,每次调用都需要新的人工授权)、`read_audit_log`。 | T1 / T3,内核门控 |
| `gov.cli` | 人类侧的内核:10 个子命令,包括 `status`、`grant`、`deny`、`trip`、`clear`、`kill`、`phase`、`audit`、`report`、`pending`。 | 仅限所有者 |
一个 agent 循环(`python3 -m agent.loop`)运行一个受治理的周期:使用只读工具进行分析 → 提出草案 → 请求门控执行 → 在人类授权后,执行(模拟)→ 报告。诚实的标签:默认规划器是**确定性脚本化规划器,而不是 LLM**。这里展示的特性不是规划质量——而是*无论*谁来规划,写入路径都始终位于内核之后。`--auto-grant` 为测试和演示折叠了人工门控,并明确声明了这一点;`--planner ollama` 是一个可选的本地模型变体,它根据相同的封闭集验证模型的选择,并回退到脚本模式。
治理状态(审批数据库、决策日志、账本、断路器和熔断开关文件)位于代码库之外的一个 git-ignored 目录中,可通过 `CRM_MCP_GOV_DIR` 重定位。合成数据库路径通过 `CRM_MCP_DB_PATH` 设置;参见 [`.env.example`](.env.example)。不需要配置任何密钥,因为没有付费 API,运行时也没有网络调用。
## 快速开始
前提条件:**Python 3.11 或更高版本**(在 3.14.6 上测得)以及**锁定在主版本 2 以下的 `mcp` 包**——`mcp>=1,<2`。这个锁定是真正的先决条件,而不是装饰:`mcp` 在其 API 变动上处于 1.0 版本之前的风格,未锁定的安装可能会使用未来的 2.x 版本,其 `FastMCP` 接口不再与 `server/mcp_server.py` 匹配。请完全按照指定的锁定版本进行安装。
```
git clone
crm-mcp-agent
cd crm-mcp-agent
python3 -m venv .venv
.venv/bin/pip install "mcp>=1,<2" # the pin is required
python3 data/synth_cdp.py --seed 42 --out data/cdp.db # generator is stdlib-only
.venv/bin/python -W error::ResourceWarning -m unittest discover # 108 tests
.venv/bin/python -m server.mcp_server # stdio MCP server
.venv/bin/python -m gov.cli status # kill / breaker / caps / pending
```
数据库被 git-ignored,在 clone 后必须重新生成;相同的 seed 总是会重现它。请注意,生成器根本不需要虚拟环境——只有 MCP server 需要。
## 架构
```
flowchart TB
subgraph DATA["Synthetic data — stdlib only"]
GEN["data/synth_cdp.py — seeded RNG, fixed 180-day window"] -->|writes| DB[("data/cdp.db — SQLite, git-ignored")]
end
subgraph READ["Read path (T0)"]
DB -->|"mode=ro"| SRV["server/mcp_server.py — FastMCP, stdio"]
GRD["server/guards/pii_guard.py"] -.->|"allowlists + output scrubber + no injection surface"| SRV
SRV --> CLI2["MCP client / agent"]
end
subgraph GOVK["Governance kernel — gov/, 8 stdlib modules"]
K["kernel.guard(tool, args)"]
KILL["kill switch — file present = deny all"] --> K
BRK["circuit breaker — garbled reads as tripped"] --> K
CAP["rate caps — re-derived from the audit log"] --> K
BRO["approval broker — fingerprint-bound, single-use, TTL"] --> K
K -->|"allow / approval_required / deny"| W["server/tools_write.py"]
K -->|every non-T0 decision| LOG[("decision-log.jsonl — append-only")]
OWN([human owner via gov.cli]) -->|grant / deny / trip / kill / phase| BRO
W -->|simulated receipts and draft ledgers| CLI2
end
CLI2 --> K
LOG --> CAP
```
核心支撑特性:
- **分层是整个设计的核心。** T0 只读自动允许且不经审计;T1 草稿自我批准但需审计且有上限;T2(可逆,保留)需要所有者提升的阶段,否则升级到 T3 路径;T3 不可逆/外部要求每次调用都有新的人工授权。任何东西都不能自我提升。
- **顺序是一项安全特性。** 防护栏按成本最低且最绝对的原则优先检查,并且上限检查在审批代理*之前*运行——因此向受上限限制的工具发送垃圾请求不会向人类发起审批提示轰炸。被拒绝的策略调用永远不会创建待处理的审批,这意味着硬性阻断无法通过审批来解除。
- **日志即计数器。** 上限在每次检查时从只追加的决策日志中重新计算,而不是保存在单独的计数中,这以一次重新读取为代价消除了一整类不同步的 bug。
- **守卫故意有两条代码路径。** 如果粗心地添加了新字段,仅靠允许列表会失效;如果正则表达式漏掉了某种形状,仅靠清洗器也会失效。两者同时运行,并且红队测试套件包含了正向控制,证明清洗器确实触发了,而不是静默放行。
- **`mode=ro` 是纵深防御,而不是核心机制。** 主要保证是没有写入工具会触及 CDP;只读连接的存在是为了防止 bug 演变为数据突变。
## 项目布局
```
data/schema.sql 7 tables: customers, events, campaigns, campaign_results,
segments, segment_membership, customer_features
data/synth_cdp.py deterministic generator: seeded RNG, fixed calendar anchor
server/mcp_server.py FastMCP stdio server, 5 read-only tools, mode=ro connection
server/tools_write.py 3 write-side tools, every call routed through the kernel
server/guards/pii_guard.py allowlists + recursive output scrubber + injection refusal
gov/policy.py tool to tier map, risk by tier, T2 promotion phase
gov/kernel.py guard(tool, args) — the one decision point
gov/broker.py fingerprint-bound, single-use, TTL'd approval grants
gov/caps.py per-tool day/week caps, counted from the audit log
gov/breaker.py circuit breaker; unreadable state means tripped
gov/audit.py append-only decision log
gov/cli.py the owner console: 10 subcommands
agent/loop.py one governed cycle; scripted planner by default
tests/ 6 files, 53 unit tests
eval/test_pii_redteam.py 33 PII attacks incl. positive controls
eval/test_gov_redteam.py 12 governance attacks, all must be refused
eval/scenarios/ 10 end-to-end governance scenarios
docs/DECISIONS.md 19 numbered decisions with their trade-offs
```
## 构建方式
**对抗性套件是交付成果,而不是附录。** 在 108 个测试中,有 45 个是为了破坏该系统而存在的,而不是为了确认它:33 个试图从只读工具中获取个人数据,12 个试图击败治理防护栏。治理攻击是有趣的一半——授权重放、跨提案和跨工具的指纹篡改、额外参数篡改、TTL 过期滥用、超上限支出、提案垃圾请求、在持有*有效*授权时绕过熔断开关、断路器破坏、未知工具探测、审批 ID 猜测,以及通过 agent 循环报告路径进行 PII 洗钱。每一个都必须被拒绝,套件才能通过。先写攻击,再写防护栏,这就是防护栏可信的原因。
**正向控制,因为静默的无操作会通过所有负面测试。** PII 红队包含断言清洗器在已知不良输入上*触发*的测试。如果没有它们,意外禁用的守卫仍然会使整个套件变绿——这种失败模式在最关键的时候恰恰是不可见的。
**独立于守卫的真实数据。** 最后一次扫描使用良性参数调用所有五个只读工具,并对它们的 JSON 输出进行子字符串检查,与底层数据库中的实际姓名、电话和电子邮件进行对比。该检查不使用正则表达式清洗器,因此不会被清洗器的 bug 欺骗——它是与现实进行比较。
**通过重新执行验证确定性,而不是口头声明。** 生成器的承诺是,相同的 seed 在任何地方、任何时间都能重现相同的数据集。发布门控通过生成两次并比较摘要来证明这一点。有一项设计决策专门用于保护这一点:生成过程从不触及系统时钟,并且整个事件窗口锚定在一个固定的日期,因此在一月份编写的测试不能在七月份以不同的方式通过。
**在有限的赛道内进行 AI 增强,并带有确定性仲裁。** 该实现是由一个 agent 接力产生的——一个 agent 构建或审计一条赛道,第二个交叉检查行为*和*证据,由作者进行裁决。仲裁者从来不是模型的观点:它是 `unittest discover` 及其退出代码。这对于治理内核来说比通常更重要,因为一个听起来合理但实际上并不成立的防护栏比没有防护栏更糟糕。[`docs/DECISIONS.md`](docs/DECISIONS.md) 中的几个决策之所以存在,是因为最初的设计*听起来*很安全,而红队并不这么认为——代理之前的上限排序和“拒绝永远不可批准”规则都源自攻击,而不是源于规划。
**声明的偏差,而不是掩盖的偏差。** [`docs/DECISIONS.md`](docs/DECISIONS.md) 包含一个关于与建模的 daemon 合约偏差的章节,是记录下来的而不是掩盖的,还有单独的条目说明*为什么存在 `--auto-grant` 以及为什么它很危险*、为什么存在 T2 但还没有工具,以及为什么默认规划器是脚本化的而不是 LLM。诚实的标签在编写时比在后期加装更容易。
**失败即关闭的发布门控。** 这个公开快照由仓库外的清理流水线生成,它从不写入私有源代码。它归档一个 ref,应用排除规则,覆盖仅用于发布的文档,然后拒绝 commit,除非每个门控都是绿色的:密钥扫描、凭证文件检测、绝对主路径和电子邮件形状检查、通过分隔符变体扩展的固定字符串雇主/客户词表、排除验证、语法构建、完整的 108 项测试套件,以及一个声明门控,它会重新运行此处每个数字背后的命令——包括重新生成合成数据库以确认行数和摘要的一致性。
## 开发
```
.venv/bin/python -W error::ResourceWarning -m unittest discover # 108 tests
.venv/bin/python -m unittest discover -v # same, verbose
python3 -m compileall -q agent data eval gov server # syntax gate
python3 data/synth_cdp.py --seed 42 --out data/cdp.db # regenerate data
python3 -m gov.cli status # rails snapshot
python3 -m gov.cli report 7 # digest the log
python3 -m agent.loop # one governed cycle
```
`-W error::ResourceWarning` 是故意的:未关闭的 SQLite 连接将成为测试失败,而不是没人读的警告。
在添加写入端工具时:首先在 `gov/policy.py` 中为其指定一个层级,然后添加试图绕过其门控的红队攻击,最后实现它。在更改防护栏时,添加该更改旨在击败的攻击。
## 本地化
英文 README 是权威版本;12 种翻译位于 [`docs/i18n/`](docs/i18n/README-INDEX.md) 下,并列在 [`docs/i18n/README-INDEX.md`](docs/i18n/README-INDEX.md) 中。保持所有版本中的命令、路径、数字、链接和测量的声明同步——声明门控不会允许此文件与代码冲突,翻译也不能与此文件冲突。请注意,PII 守卫的正则表达式形状是针对韩国的(电话格式、居民身份证号码形状),这是该领域本身的属性,而不是文档语言的属性。
## 鸣谢
关于涵盖合成数据集和所有其他非代码文件的四层登记、未列出内容的失败即关闭规则,以及分叉(fork)必须移除的内容,请参见 [CREDITS.md](CREDITS.md)。对于这些文件,其条款优先于代码许可证。单个第三方运行时依赖项在安装的任何地方都保留其自身的上游许可证;`requirements.txt` 是其权威记录。
## 许可证
源代码在 [Apache License 2.0](LICENSE) 下授权;修改声明记录在 [NOTICE](NOTICE) 中。数据文件和其他非代码内容不受 Apache-2.0 许可,仍受 [CREDITS.md](CREDITS.md) 管理。 标签:AI风险缓解, CRM, MCP, Python, 人工智能, 数据库查询, 无后门, 智能代理, 权限治理, 用户模式Hook绕过, 逆向工具