business-data-mcp
by aystzh
README.md
# Business Data MCP
**Secure, Auditable Read-only Search** · [English](README.en.md)
一个可接入 **Codex / Claude Code** 的 Python MCP 服务:查询 PostgreSQL 客户与订单、JSON Mock CRM、Markdown 业务文档,并统一执行权限检查、租户隔离、脱敏和审计。
所有内置业务数据都是虚构数据。项目定位是可复现的本地工程展示,生产边界见 [安全说明](docs/security.md)。
## 架构
```mermaid
flowchart TD
A[Codex / Claude Code / SDK Client] -->|Streamable HTTP + Bearer Token| B[HTTP 认证]
B --> C[MCP 策略层:参数校验、Scope、限流、审计]
C --> D[业务查询服务]
D --> P[PostgreSQL 只读账号]
D --> R[Mock CRM JSON]
D --> F[受控 Markdown 文档]
P --> E[脱敏 + 来源引用 + 结果大小限制]
R --> E
F --> E
E --> C
C --> L[JSON Lines 审计]
C --> A
```
- Python 3.12,uv 管理 `.venv` 和锁定依赖。
- MCP Python SDK 2.2,支持现代协议及需要初始化握手的客户端。
- Pydantic 校验,Psycopg 异步连接池,PostgreSQL 17。
- 四个有类型定义的只读工具,不提供 SQL 执行、任意文件读取、写操作或导出。
## 快速启动
前置条件:Docker Desktop / Docker Engine + Compose、[uv](https://docs.astral.sh/uv/getting-started/installation/)。命令均在仓库根目录执行;uv 会使用或下载 Python 3.12。
```bash
uv sync --locked
uv run business-data-init
docker compose up -d --wait
uv run business-data-mcp
```
首次初始化会创建权限为 `0600` 的 `.env` 和 `.local` 凭据文件,不打印密码或 Token,也不覆盖已有凭据。开发 Token 有效期为 30 天。
服务地址:`http://127.0.0.1:8000/mcp`。PostgreSQL 默认监听本机 `5433`,使用本项目独立 volume。
在另一个终端进入仓库根目录:
```bash
curl http://127.0.0.1:8000/health/ready
uv run business-data-demo
```
演示脚本依次切换四个身份,调用四个工具,不需要付费模型 API。输出包含脱敏结果、来源和 `query_id`。若设置了 `BUSINESS_DATA_MCP_TOKEN`,脚本会仅使用该 Token。
```bash
uv run business-data-demo --identity support
uv run business-data-demo --identity pii
uv run business-data-demo --identity tenant-b
uv run business-data-demo --identity restricted
```
## 工具
| 工具 | 参数 | 行为 |
|---|---|---|
| `search_customers` | `query`, `limit=10` | 客户名称字面子串或精确客户 ID |
| `get_customer` | `customer_id` | 客户资料及获授权的 CRM 摘要 |
| `search_orders` | `order_no`, `phone`, `status`, `date_from`, `date_to`, `limit=10` | 至少一个条件,多个条件使用 AND |
| `search_business_docs` | `query`, `limit=10` | 当前租户文档关键词检索,返回摘录和行号 |
- 关键词去除首尾空格后为 2–100 个字符;ID 仅允许字母、数字、下划线和短横线,最长 64 个字符。
- 条数为整数 1–20;无分页。订单手机号为完整值匹配,状态为 `pending / paid / shipped / cancelled`。
- 日期使用 `YYYY-MM-DD`,按 UTC 自然日包含起止日期。
- 文档按空白分词,全部词命中才返回;按出现次数降序、文档 ID 升序排列。
- 搜索无结果和访问不到的对象均返回空记录;错误使用 MCP `isError`,不伪装成空结果。
成功结果示意(`structuredContent`;文本内容也包含相同数据):
```json
{
"records": [{
"customer_id": "C10001",
"name": "星河科技",
"phone": "138****8000",
"source_uris": ["business://tenant-a/customers/C10001"]
}],
"sources": [{
"uri": "business://tenant-a/customers/C10001",
"source_type": "postgres",
"label": "C10001",
"updated_at": "2026-09-14T10:00:00Z"
}],
"query_id": "qry_example",
"notices": ["PII fields are masked."],
"truncated": false
}
```
逻辑 URI 是来源标识,不是可直接打开的网址。源信息以各演示数据源提供的更新时间为准。
## 身份与审计
| 身份 | 租户 | 权限 |
|---|---|---|
| `support` | tenant-a | 四个工具及 CRM,PII 脱敏 |
| `pii` | tenant-a | 同上,额外允许原始 PII |
| `tenant-b` | tenant-b | 仅乙租户数据,PII 脱敏 |
| `restricted` | tenant-a | 仅客户查询,省略 CRM,订单和文档拒绝 |
`.local/tokens.json` 保存 Token 的 SHA-256 摘要和身份映射;`.local/client-tokens.json` 保存仅供本地演示的原始 Token。服务启动时加载映射,修改后需要重启。
审计位于 `.local/audit.jsonl`,通过返回的 ID 查找:
```bash
rg 'qry_返回的ID' .local/audit.jsonl
```
记录开始、结束、身份、租户、工具、条件字段名、条数、耗时和状态;不保存完整查询值或响应正文。认证失败有单独的 `request_id`。审计不可写时不返回业务数据。
## 接入 Codex / Claude Code:CLI 与桌面端
以下步骤以 **macOS、本地客户端、本地 MCP 服务** 为例。在仓库根目录操作,先完成前面的快速启动。客户端的模型账号登录和本服务的 Token 认证彼此独立。
### 共同准备:确认服务并选择身份
```bash
curl -fsS http://127.0.0.1:8000/health/ready
```
应返回 `{"status":"ready"}`。若连接失败,在一个终端运行并保持服务不退出:
```bash
docker compose up -d --wait
uv run business-data-mcp
```
在另一个终端进入仓库根目录。CLI 使用下面的环境变量;桌面端使用下文的本地静态请求头,避免依赖终端环境:
```bash
export BUSINESS_DATA_MCP_TOKEN="$(uv run python -c 'import json; print(json.load(open(".local/client-tokens.json"))["support"])')"
```
这个命令不打印 Token。新终端需要重新执行,客户端必须在同一个终端启动。桌面端需要粘贴 Token 时,可在 macOS 执行:
```bash
uv run python -c 'import json; print(json.load(open(".local/client-tokens.json"))["support"], end="")' | pbcopy
```
下文的 `PASTE_SUPPORT_TOKEN_HERE` 均需替换为剪贴板中的 Token,并保留 `Bearer` 后的一个空格。真实凭据仅放在本机私有配置,不放入 README、示例文件或 Git。
### 1. Codex CLI
先确认 `codex --version` 能运行。如果提示 `zsh: command not found: codex`,且 macOS 应用包含下面的可执行文件,可执行:
```bash
# 先确认文件存在;应用安装位置可能不同。
test -x /Applications/ChatGPT.app/Contents/Resources/codex
export PATH="/Applications/ChatGPT.app/Contents/Resources:$PATH"
codex --version
```
希望新终端也可用,可将该 `export PATH=...` 行加入 `~/.zshrc`。若本机没有这个文件,按 [Codex CLI 官方说明](https://developers.openai.com/codex/cli)安装 CLI。
加载上面的 Token 环境变量后注册服务:
```bash
codex mcp add business-data \
--url http://127.0.0.1:8000/mcp \
--bearer-token-env-var BUSINESS_DATA_MCP_TOKEN
codex mcp get business-data
codex
```
注册只需一次。`get` 应显示 `enabled: true`、`transport: streamable_http`、正确 URL 和环境变量名称;它只验证已保存的配置。进入 Codex 后输入 `/mcp` 检查连接,再发送下文的验收提示。
### 2. Codex 桌面端
桌面端与 CLI 在同一 Codex host 上共享 `~/.codex/config.toml`,但从 Dock 打开的应用不会自动继承终端里的 `export`。本地演示采用静态请求头:
1. 用上面的 `pbcopy` 命令复制 Token。
2. 执行 `open -e ~/.codex/config.toml` 打开配置;若文件不存在,先通过桌面 **Settings → MCP servers → Add server** 添加名为 `business-data` 的 Streamable HTTP 服务。
3. 找到已有的 `business-data` 段,只替换该段,保留其他服务;不要创建重复段:
```toml
[mcp_servers.business-data]
url = "http://127.0.0.1:8000/mcp"
enabled = true
http_headers = { Authorization = "Bearer PASTE_SUPPORT_TOKEN_HERE" }
```
删除这一服务原来的 `bearer_token_env_var` 行;如有 `env_http_headers` 或 header helper 提供 Authorization,也移除冲突项。这样 CLI 也会使用相同的静态 Token,不再需要为这个服务执行 `export`。
4. 保存后在 **Settings → MCP servers** 确认已启用,点击 **Restart**;没有此按钮时完全退出应用后重开。
5. 打开新的**本地**对话,输入 `/mcp` 查看服务并发送验收提示。
参见 [Codex MCP 官方文档](https://developers.openai.com/codex/mcp)。本服务使用开发 Bearer Token,不需要点击 OAuth 登录。
### 3. Claude Code CLI
确认 `claude --version` 可用,并完成客户端登录。使用共同准备步骤中的 Token,在同一个终端执行:
```bash
claude --mcp-config examples/claude.mcp.json --strict-mcp-config
```
示例配置的内容如下,CLI 会展开环境变量:
```json
{
"mcpServers": {
"business-data": {
"type": "http",
"url": "http://127.0.0.1:8000/mcp",
"headers": {
"Authorization": "Bearer ${BUSINESS_DATA_MCP_TOKEN}"
}
}
}
}
```
`--strict-mcp-config` 让本次演示只加载指定文件里的 MCP 服务。进入后输入 `/mcp` 查看状态,按客户端提示批准工具使用,再发送验收提示。参见 [Claude Code MCP 官方文档](https://code.claude.com/docs/en/mcp)。
### 4. Claude Code 桌面端(Claude 应用的 Code 标签页)
选择 **Code → 本地会话**,打开本仓库。Code 标签页可读取项目 `.mcp.json`。为了从 Dock 启动也能读取 Token,在仓库根目录的 `.mcp.json` 中设置静态请求头:
```json
{
"mcpServers": {
"business-data": {
"type": "http",
"url": "http://127.0.0.1:8000/mcp",
"headers": {
"Authorization": "Bearer PASTE_SUPPORT_TOKEN_HERE"
}
}
}
}
```
如果已有 `.mcp.json`,只合并 `mcpServers.business-data`,不要覆盖其他服务。本项目已将 `.mcp.json` 加入 `.gitignore`;可运行 `chmod 600 .mcp.json` 限制本机读取权限。不要把这个含真实 Token 的配置复制回 `examples/claude.mcp.json`。
保存后重开本地 Code 会话,按提示信任项目并启用 MCP,发送验收提示。选择原始项目目录;新 worktree 不会自动包含这个被 Git 忽略的配置文件。CLI 的 `--mcp-config` 参数不会自动传给桌面应用。参见 [Claude Code Desktop 官方说明](https://code.claude.com/docs/en/desktop)。
### 可选:Claude Desktop 的普通 Chat 标签页
Chat 与 Code 的接入入口不同。不要直接把 `http://127.0.0.1:8000/mcp` 填进账户级 **Add custom connector**:该功能从 Anthropic 云端连接,无法访问你的本机回环地址。见 [远程连接器网络要求](https://support.claude.com/en/articles/11175166-get-started-with-custom-connectors-using-remote-mcp)。
普通 Chat 可通过本地 stdio → HTTP 桥接器接入当前服务。此方式额外依赖 Node.js / npx 和第三方 [mcp-remote](https://github.com/punkpeye/mcp-remote),不属于本项目 Python 运行依赖:
1. 确认 `node --version` 和 `npx --version` 可用。
2. 在 Claude 的 **Settings → Developer → Edit Config** 打开本地配置。macOS 常见位置为 `~/Library/Application Support/Claude/claude_desktop_config.json`。
3. 将以下服务合并到 `mcpServers`,替换 Token:
```json
{
"mcpServers": {
"business-data": {
"command": "npx",
"args": [
"-y", "mcp-remote", "http://127.0.0.1:8000/mcp",
"--transport", "http-only", "--header", "Authorization:${AUTH_HEADER}"
],
"env": {
"AUTH_HEADER": "Bearer PASTE_SUPPORT_TOKEN_HERE"
}
}
}
}
```
4. 完全退出并重开 Claude,在新 Chat 对话中启用工具并发送验收提示。首次启动需下载桥接器;桌面进程必须能找到 Node/npx。如果使用 nvm,配置 `command` 为 `command -v npx` 得到的绝对路径,并在 `env.PATH` 中配置 `command -v node` 对应目录及系统路径;更新 Node 后检查路径。
以上为配置说明,尚未实际验收 Claude 桌面 Code / Chat 或该桥接器。已验证的客户端和证据见 [验收记录](docs/verification.md)。
### 验收提示、身份切换与排错
在任一客户端发送:
> 请实际调用 business-data 的 get_customer 工具查询 C10001,展示客户资料、CRM 跟进、来源 URI 和 query_id。不要通过终端或本地文件读取数据。
`support` 的预期结果:星河科技、手机号 `138****8000`、跟进“已发送产品方案,等待技术评估”,来源包含 `business://tenant-a/customers/C10001` 与 `crm://tenant-a/customers/C10001`,以及新生成的 `qry_...`。用该 ID 在 `.local/audit.jsonl` 中查找 `query_started` / `query_finished`,确认发生了真实调用。
- **切换身份**:CLI 环境变量方案将读取命令中的 `support` 改为 `pii`、`tenant-b` 或 `restricted`,退出并重启客户端;静态配置方案替换配置中的 Token 后重新加载。不要重新初始化服务端凭据。
- **权限验证**:`restricted` 查询 `ORD-10001` 应返回 `FORBIDDEN`;`pii` 查询 `C10001` 显示完整手机号;`tenant-b` 查询同一订单返回 `cancelled`、`9900.00`。
- **401 / 环境变量缺失**:检查 Token、过期时间和认证配置来源;桌面端避免依赖终端临时环境。
- **连接拒绝**:检查健康接口和服务进程,确认客户端 URL 与 `PORT` 一致。
- **找不到工具**:重新加载 MCP 或打开新会话;确认服务启用、项目目录正确、没有同名服务覆盖配置。
- **远程会话**:云端或 SSH 会话的 `127.0.0.1` 指向执行端,不是本机;本文步骤仅针对本机会话。
## 自动化验证
```bash
make check # Ruff + 格式检查 + mypy
make test # 单元测试;数据库测试明确跳过
make test-integration # 全部测试;需要已启动的演示数据库
```
GitHub Actions 使用独立 PostgreSQL service、同一初始化脚本和锁定依赖运行全套检查。
## 配置与运行维护
默认配置见 `.env.example`。`.env` 不提交 Git;进程环境变量优先于 `.env`。
| 配置 | 默认值 / 含义 |
|---|---|
| `DATABASE_URL` | 必填,只读账号连接串 |
| `TOKEN_FILE` / `AUDIT_FILE` | `.local/tokens.json` / `.local/audit.jsonl` |
| `DATA_DIR` | `data`,必须从可信本地目录加载 |
| `HOST` / `PORT` | `127.0.0.1` / `8000` |
| `RATE_LIMIT` | 每身份每分钟 60 次工具调用 |
| `QUERY_TIMEOUT` | 工具 10 秒,数据库语句另限 5 秒 |
| `MAX_RESULT_BYTES` | MCP 工具结果最多 65536 字节,含文本和结构化内容 |
结果超限时去掉完整记录并标记 `truncated`;单条仍过大返回 `RESULT_TOO_LARGE`。请求体最多 16 KiB。`/health/live` 表示进程存活,`/health/ready` 检查数据库连接。
停止服务用 Ctrl+C,停止数据库用 `docker compose stop`。普通重启保留数据。
**重置数据库会删除本项目演示数据**,先停止 MCP 服务再执行:
```bash
bash scripts/reset-demo.sh --confirm-delete-demo-data
```
凭据不会随数据库重置。需要重新生成凭据时,先停止服务,手动备份并移走 `.env`、`.local/tokens.json`、`.local/client-tokens.json`,再运行初始化并重置数据库;新密码不会自动应用到旧 volume。
### 常见问题
- Docker 报 `docker-credential-desktop` 找不到:在 macOS 终端执行 `export PATH="/Applications/Docker.app/Contents/Resources/bin:$PATH"`,再启动 Compose。
- 5433 冲突:首次初始化使用 `POSTGRES_PORT=55433 uv run business-data-init`。已有环境需同步修改 `.env` 中端口和 `DATABASE_URL`。
- 8000 冲突:使用 `PORT=8001 uv run business-data-mcp`,并同步修改客户端地址。
- 数据库连接失败:检查 `docker compose ps`;确保应用密码与首次初始化的 volume 一致。
- 401:检查客户端是否继承 Token 环境变量、Token 是否过期;Token 变更后重启服务和客户端。
- `FORBIDDEN`:检查身份 scopes;`restricted` 的订单拒绝属于预期演示。
- 远程客户端连接不到:`127.0.0.1` 只支持同机访问;首版未配置远程 HTTPS 部署。
## 后续方向
真实 OAuth → PostgreSQL RLS → 真实 CRM → 集中审计与分布式限流 → 应用容器化和部署。
许可证:[Apache-2.0](LICENSE)。
This server cannot be deployed
Maintenance
ActivityMaintained
ResponsivenessNo issues