WeCom Data Gateway MCP Server
by hkxiaoyao
README.md
# 多连接 MCP Gateway
将企业微信和受控第三方连接器的数据能力通过模型上下文协议(MCP)暴露给 WorkBuddy/CodeBuddy。每个连接使用独立的 `/mcp/{connection_id}`、Token、工具策略、凭证、缓存和审计边界。
> 解决痛点:WorkBuddy 的企业微信连接器**只支持 MCP**,且只能"新建/写入",**不支持读取**历史业务数据。本服务作为数据中转层,主动从企微 OpenAPI 拉取落库,再以 MCP Server 形态暴露给 WorkBuddy 读取。
## ✨ 功能特性
- **MCP Gateway**(Streamable HTTP + Bearer Token,远程多客户连接)
- **连接级隔离**:一个租户可创建多个连接,每个连接独立鉴权、策略、缓存和日志
- **兼容迁移**:旧 `/mcp` 在兼容期映射到默认企微连接,新客户端使用 `/mcp/{connection_id}`
- **OpenAPI 快速接入**:粘贴、上传或填写 HTTPS 文档地址,分析后选择工具、配置鉴权并探测启用
- **MCP 配置一键交付**:后台可直接复制或下载 Claude Code、Cursor、Codex 和通用 HTTP 配置
- **三类数据读取**:审批、汇报、打卡(智能表格一期搁置:见下方说明)
- **连接级数据隔离**:每个存储型企业微信连接使用后端分配的 MySQL schema,凭证 AES 加密
- **连接级同步策略**:每个企业微信连接独立选择 `report`/`approval`/`checkin` 模块与同步间隔
- **打卡自动拉通讯录**:配通讯录同步 secret → 自动调 `user/list_id` 拉全员 userid
- **增量同步**:游标驱动 + 断点续传 + 幂等 UPSERT + APScheduler 定时
- **双管理后台**:平台管理租户身份;租户使用 ID/密码登录并管理自己的连接实例、MCP Token、工具策略与调用日志
- **可观测性**:`/livez`、`/readyz`、受保护的 Prometheus 指标、Grafana 看板和告警规则
- **CI/CD**:GitHub Actions 自动构建镜像推 GHCR,服务器 `docker pull` 免本机编译
- **生产就绪**:Dockerfile + Nginx/HTTPS + systemd + 健康检查 + 日志轮转
## 🏗 架构
```
┌─ 企微 OpenAPI ──────────────────────────────────────────┐
│ 审批 oa/getapprovalinfo+getapprovaldetail │
│ 汇报 oa/journal/get_record_list+get_record_detail │
│ 打卡 checkin/getcheckindata │
│ 通讯录 user/list_id │
└──────────────────────────────────────────────────────────┘
│ ① 同步层(APScheduler 按连接调度,游标增量)
▼
┌─ MySQL(多租户分 schema 物理隔离)────────────────────────┐
│ 中心库 websysc: tenant_config / connection_instance + │
│ connection_credential / token / log │
│ 各连接 wbd_{hash}: wecom_report/approval/checkin + │
│ sync_cursor + audit_log │
└──────────────────────────────────────────────────────────┘
│
│ ┌─ 第三方 OpenAPI 文档 ─────────────┐
│ │ 安全分析 → 不可变修订 → 受控调用 │
│ └───────────────────────────────────┘
│ │
▼ ▼
┌─ 连接级 MCP Gateway(Streamable HTTP)────────────────────┐
│ Token / 工具策略 / 凭证 / 缓存 / 限流 / 审计 │
└───────────────────────────────────────────────────────────┘
│ ② MCP 暴露层
▼
WorkBuddy / CodeBuddy
(Bearer Token → 租户与连接强绑定 → 访问该连接能力)
▲ ③ 管理后台(独立 session 鉴权)
│
浏览器 http://server/admin/ui/ (React+AntD)
```
- **生产 transport**:HTTP(Streamable HTTP),新连接路径 `/mcp/{connection_id}`;旧 `/mcp` 仅作默认企微连接兼容入口
- **鉴权**:MCP 默认使用连接实例 Bearer Token;平台后台使用管理员会话;租户后台使用租户 ID + 密码会话
- **同步策略**:一期定时轮询(增量游标),预留 Webhook 位,不引 MQ
### 数据读取模式
每个连接实例可选择 `stored` 或 `direct`。`stored` 定时把业务数据同步到该连接的 MySQL schema,MCP 查询本地表。`direct` 在每次 MCP 调用时请求企微 API,不写入审批、汇报或打卡业务表,也不参加后台同步。
两种模式都在 MySQL 保存连接配置、加密凭证、连接 MCP Token 和审计日志。直连请求失败时会返回企微错误,不读取历史缓存。现有租户级企业微信配置会兼容回填为默认连接并保持 `stored`。
直连模式查询大时间窗口时,会从最新时间分段开始遍历企微列表分页,并为返回的单号逐条请求详情,API 调用成本较高。生产调用建议缩小时间窗口并设置较小的 `limit`。
### 响应缓存
`ToolSpec.cache_ttl_seconds` 现在真正生效:**只读**工具在 TTL 内复用上一次成功结果,
按 `(connection_id, tool_key, 参数哈希)` 隔离,连接配置提交变更后立即失效。
写工具、`error` / `partial` 结果、以及含疑似凭证字段的结果一律不缓存。
`direct` 模式是**刻意的缓存旁路**——该模式的语义就是每次都取实时数据,
因此缓存只作用于 `stored` 和 `hybrid`。
## 🧩 声明式连接器(OpenAPI)
除内置企业微信连接器外,租户可导入 OpenAPI 3 文档生成 `http_declarative` 连接器。
### 从 OpenAPI 到 MCP
后台的接入流程是:
1. **读取文档**:支持粘贴 YAML/JSON、上传文件或拉取 HTTPS URL;远程拉取限制为公网 443、受控重定向、256 KiB 和短超时。
2. **筛选操作**:默认只选择 `GET`。也可按 HTTP method、include path 和 exclude path 缩小范围;`*` 匹配单个 path segment 内的字符,`**` 匹配零个或多个完整 segment,exclude 优先。
3. **编译工具**:解析同一文档内有界的 `$ref`,校验受支持的 OpenAPI 3 子集,并生成类型化的 path、query、header 和 JSON body 输入。远程 URL、文件和跨文档引用始终拒绝。
4. **稳定命名**:工具键优先使用显式 `x-tool-key`。合法的 `operationId` 保持不变;其他 `operationId` 先规范化,规范化后为空或缺失时才按 method + canonical path 生成。`x-mcp-name` 可单独覆盖 MCP 名称。自动名称过长或发生碰撞时追加稳定短哈希,不依赖文档中的 path 顺序。
5. **人工确认**:启用前预览原始 method/path 到 MCP tool 的映射,选择开放的只读/写入工具并配置类型化鉴权;写工具必须显式放行。
6. **探测启用**:至少执行一个无参数只读工具探测,成功后再把凭据、策略和不可变修订原子写入连接实例。
7. **受控执行**:MCP 请求始终使用已发布修订,经过连接级鉴权、SSRF/DNS 防护、超时、响应大小、缓存、限流和审计边界访问上游。
筛选发生在写操作门禁、工具命名和 64 个 operation 上限之前。每次分析最多接受 5 个 method、32 条 include 和 32 条 exclude;每条 path 规则最多 256 个字符。筛选条件不进入 MCP URL,也不会作为连接草稿或日志内容保存。
本地 `$ref` 解析最多允许 16 层引用链和 256 次展开。解析器在构造结果时同步限制 24 层文档深度、20,000 个节点和 256 KiB 紧凑 JSON,超限文档不会进入修订存储。
它不是在每次 MCP 连接时临时下载规范并生成任意请求,也不执行 OpenAPI 生成的代码;运行时只解释已经审核并发布的声明。
### 与 mcp-link 的对照
[`automation-ai-labs/mcp-link`](https://github.com/automation-ai-labs/mcp-link) 的核心思路也是“每个 OpenAPI operation 生成一个 MCP tool”,并支持按 path/method 过滤,接入很轻。本项目会借鉴其低摩擦体验和工具筛选思路,但不会采用在 SSE URL 中传规范地址、目标地址和任意请求头的运行模型。实现对照、风险边界和可借鉴项见 [`docs/research/mcp-link-openapi-to-mcp.md`](docs/research/mcp-link-openapi-to-mcp.md)。
当前实现还支持多步编排、SSRF 边界、不可变修订版生命周期,以及:
**分页**(`x-pagination`,仅 GET 只读操作):
```yaml
x-pagination:
max_pages: 3 # ≤ 10
max_items: 500 # ≤ 1000
items_pointer: /items # 必须是已声明的数组,且被 output 映射投影
next_pointer: /next_cursor # 必须被响应 schema 声明;游标只作控制数据
next_query_param: cursor # 不得与已声明的 query 入参同名
```
翻页**只替换这一个 query 参数**,host 与 path 固定不变,绝不跟随上游返回的
任意 next 链接;游标按非法即停止处理。遍历同时受页数、条目数和输出字节数三重上限约束。
**OAuth2 token 缓存**:`client_credentials` token 按连接缓存并复用,
尊重 `expires_in` 且提前 60 秒过期(未返回则默认 300 秒)。多步工具只换一次 token。
缓存键含凭证的加盐指纹,轮换密钥即自动失效;上游返回 401 会刷新 token 并**只重试一次**。
**stored 落库**(`x-sync-spec`):
```yaml
x-sync-spec:
resource_key: people
operation_key: people.list
items_pointer: /items # 可选:设了就按数组逐项落库;不设为单记录
primary_key_pointer: /id # items_pointer 存在时相对每一项解析
field_mappings: { id: /id, name: /name }
```
同步结果写入中心库 `declarative_record` 表(`sql/011`),按
`(connection_id, resource_key, record_key)` 幂等 UPSERT。**只写 `field_mappings`
投影后的字段**,绝不写入上游原始响应体;主键缺失或非标量的记录会被跳过并计入
`partial`。`stored` 模式下该同步资源对应的工具从本地表读取,其余工具仍走直连。
## 🚀 快速开始
### 方式一:Docker(生产推荐,用 CI 构建的镜像)
```bash
git clone https://github.com/hkxiaoyao/wbsysc.git && cd wbsysc
cp .env.prod.example .env && vim .env # 填写密码和三个独立密钥;首次保持 MCP_SERVICE_ENABLED=false
# 使用 CI 产出的真实 sha 标签(7–40 位小写十六进制);也可改用经核对的 @sha256:<digest>
read -rp "DB_MIGRATION_USER: " DB_MIGRATION_USER && export DB_MIGRATION_USER
read -rsp "DB_MIGRATION_PASSWORD: " DB_MIGRATION_PASSWORD && export DB_MIGRATION_PASSWORD && echo
DEPLOY_IMAGE_REF='ghcr.io/hkxiaoyao/wbsysc:sha-<commit>' bash deploy/server_deploy.sh
curl --connect-timeout 2 --max-time 5 http://localhost:8001/livez
curl --connect-timeout 2 --max-time 5 http://localhost:8001/readyz
# 接入第一个租户:先在平台后台创建租户 ID、名称和登录密码;
# 再由平台管理员或该租户登录租户后台创建企业微信连接实例。
```
### 生产升级(先迁移再切换)
推荐执行 `deploy/server_deploy.sh`,并在每次发布命令中显式提供 `DEPLOY_IMAGE_REF`。它只接受批准仓库的 `sha-<commit>` 标签或 `@sha256:<digest>`,拒绝空值、`latest` 和其他非精确引用。脚本先校验三个生产密钥,再用独立迁移账户和宿主 `mysql` CLI 严格执行 `004` → `005` → `006` → `007` → `008` → `009` → `010` → `011` → `012` → `013` → `014` → `015`;任一迁移失败都会在拉取/启动前终止。精确镜像拉取成功后,脚本校验 OCI revision 和 image ID,把解析出的 repo digest 写入 `.env` 的 `WBSYSC_IMAGE`,保证后续 Compose 重建不会漂移。
```bash
git pull
read -rp "DB_MIGRATION_USER: " DB_MIGRATION_USER && export DB_MIGRATION_USER
read -rsp "DB_MIGRATION_PASSWORD: " DB_MIGRATION_PASSWORD && export DB_MIGRATION_PASSWORD && echo
DEPLOY_IMAGE_REF='ghcr.io/hkxiaoyao/wbsysc:sha-<commit>' bash deploy/server_deploy.sh
# 或:DEPLOY_IMAGE_REF='ghcr.io/hkxiaoyao/wbsysc@sha256:<digest>' bash deploy/server_deploy.sh
```
发布脚本不会在镜像拉取失败时静默本机构建;拉取失败会终止发布。本机构建只属于下方“本地开发”的独立手工流程,不得替代已批准的生产镜像。新镜像在关闭态或兼容启用态健康检查失败时,脚本会把 `WBSYSC_IMAGE` 恢复为发布前的旧镜像,强制写入 `MCP_SERVICE_ENABLED=false`,重建并验证旧镜像,然后以非零状态退出。已经成功执行的结构迁移会保留,不回滚或删除。
需要手动升级时,顺序必须是“备份数据库 → `004` → `005` → `006` → `007` → `008` → `009` → `010` → `011` → `012` → `013` → `014` → `015` → 拉取精确镜像 → 写入 `WBSYSC_IMAGE` → 关闭态重建/健康检查 → 只读发布 smoke → 完成发布”。`015` 增加共享调度心跳表,用于 readiness 与运维指标。密码通过 `MYSQL_PWD` 环境变量传递:
```bash
DEPLOY_IMAGE_REF='ghcr.io/hkxiaoyao/wbsysc:sha-<commit>'
DB_MIGRATION_HOST=127.0.0.1
DB_PORT=3306
DB_MIGRATION_USER=wbsysc_migrator
DB_NAME=websysc
read -rsp "DB_MIGRATION_PASSWORD: " MYSQL_PWD && export MYSQL_PWD && echo
mysql --protocol=TCP --host="$DB_MIGRATION_HOST" --port="$DB_PORT" \
--user="$DB_MIGRATION_USER" "$DB_NAME" < sql/004_gateway_hardening.sql
mysql --protocol=TCP --host="$DB_MIGRATION_HOST" --port="$DB_PORT" \
--user="$DB_MIGRATION_USER" "$DB_NAME" < sql/005_mcp_call_log.sql
mysql --protocol=TCP --host="$DB_MIGRATION_HOST" --port="$DB_PORT" \
--user="$DB_MIGRATION_USER" "$DB_NAME" < sql/006_connection_platform.sql
mysql --protocol=TCP --host="$DB_MIGRATION_HOST" --port="$DB_PORT" \
--user="$DB_MIGRATION_USER" "$DB_NAME" < sql/007_tenant_auth.sql
mysql --protocol=TCP --host="$DB_MIGRATION_HOST" --port="$DB_PORT" \
--user="$DB_MIGRATION_USER" "$DB_NAME" < sql/008_mcp_service.sql
mysql --protocol=TCP --host="$DB_MIGRATION_HOST" --port="$DB_PORT" \
--user="$DB_MIGRATION_USER" "$DB_NAME" < sql/009_tenant_identity_boundary.sql
mysql --protocol=TCP --host="$DB_MIGRATION_HOST" --port="$DB_PORT" \
--user="$DB_MIGRATION_USER" "$DB_NAME" < sql/010_connection_domain_verify.sql
mysql --protocol=TCP --host="$DB_MIGRATION_HOST" --port="$DB_PORT" \
--user="$DB_MIGRATION_USER" "$DB_NAME" < sql/011_declarative_record.sql
mysql --protocol=TCP --host="$DB_MIGRATION_HOST" --port="$DB_PORT" \
--user="$DB_MIGRATION_USER" "$DB_NAME" < sql/012_connection_token_reveal.sql
mysql --protocol=TCP --host="$DB_MIGRATION_HOST" --port="$DB_PORT" \
--user="$DB_MIGRATION_USER" "$DB_NAME" < sql/013_shared_runtime.sql
mysql --protocol=TCP --host="$DB_MIGRATION_HOST" --port="$DB_PORT" \
--user="$DB_MIGRATION_USER" "$DB_NAME" < sql/014_connection_token_lifecycle.sql
mysql --protocol=TCP --host="$DB_MIGRATION_HOST" --port="$DB_PORT" \
--user="$DB_MIGRATION_USER" "$DB_NAME" < sql/015_observability.sql
unset MYSQL_PWD
docker pull "$DEPLOY_IMAGE_REF" # 失败即终止,不执行本机构建
sed -i "s|^WBSYSC_IMAGE=.*|WBSYSC_IMAGE=$DEPLOY_IMAGE_REF|" .env
# 先写 MCP_SERVICE_ENABLED=false,再 docker compose up -d --force-recreate 并核对 health
# 仅经批准后改 true,再次 --force-recreate 并核对 health
```
> `004_gateway_hardening.sql` 包含 `DELIMITER` 和存储过程语句,必须使用 MySQL `mysql` CLI 执行。`006_connection_platform.sql` 会幂等地将旧库的声明式文档列从 `TEXT` 扩容为 `MEDIUMTEXT`,以支持运行时允许的 256 KiB 文档。任一迁移失败时都不要启动新版本。
生产环境的 `RUNTIME_STATE_BACKEND=auto` 会解析为 `database`,使管理会话、连接级限流、定时任务租约和审计投递在多个应用副本间共享;生产显式配置 `memory` 会拒绝启动。开发环境的 `auto` 仍使用内存实现。连接结果缓存仍是本地短缓存,但缓存键包含连接 `config_version`,配置变更不会继续命中旧版本数据。
### 方式二:本地开发
```bash
python -m venv .venv && .venv/Scripts/activate # Windows
pip install -r requirements.txt
cp .env.example .env # 默认 mock 模式
python -m app.main # 启动 (http://localhost:8001)
# 另开终端:MCP 客户端真实协议冒烟测试
python tests/test_smoke_client.py
```
需要验证容器构建时,可在开发机单独手工执行 `docker compose build wbsysc`。该流程不复用生产发布标签,也不会在生产镜像拉取失败后自动触发。
## 📋 配置(.env)
| 变量 | 说明 | 必填 |
|------|------|------|
| `DB_HOST` | 同台部署填 `host.docker.internal`;跨机填 MySQL IP | ✓ |
| `DB_PASSWORD` | MySQL `websysc` 账户密码 | ✓ |
| `ADMIN_PASSWORD` | 管理后台登录密码 | ✓ |
| `CREDENTIAL_KEY` | 凭证加密主密钥(开发可留空;生产必配强随机) | 生产必填 |
| `MCP_TOKEN_HMAC_KEY` | MCP Token HMAC 密钥,至少 32 个 UTF-8 字节且与 `CREDENTIAL_KEY` 独立 | 生产必填 |
| `MCP_TOKEN_PLAINTEXT_KEY` | 未撤销服务 Token 密文密钥,至少 32 个 UTF-8 字节且与另两个密钥独立 | 生产必填 |
| `METRICS_BEARER_TOKEN` | 独立 Prometheus Bearer;留空时 `/metrics` 返回 404,启用时至少 32 UTF-8 字节且与所有其他密钥/密码不同 | 按需 |
| `MCP_SERVICE_ENABLED` | 仅启用存量 `/mcp/service/{id}` 兼容运行时;不恢复服务管理 UI、租户 API 或默认服务回填 | 生产必填 |
| `WBSYSC_IMAGE` | 当前已发布的 `@sha256:<digest>` 镜像引用;由发布脚本从输入 tag/digest 校验后持久化,禁止 `latest` | 生产必填 |
| `CONNECTOR_ALLOWLIST` | 已审核 `wbsysc.connectors` 入口名的归一化精确列表 | - |
| `WECOM_USE_MOCK` | `true`=脱敏 mock;生产必须为 `false` 并配置租户凭证 | 生产必填 |
| `SYNC_INTERVAL_*_MIN` | 同步间隔(report/approval/smarttable) | - |
> 企业微信凭证(CorpID、应用 Secret、通讯录 Secret)**不进 `.env`,也不属于租户资料**;它们通过连接实例页面写入 `connection_instance` / `connection_credential`,Secret 使用 AES 加密。
>
> 生产启动必须同时设置 `WECOM_USE_MOCK=false`、三个两两不同的密钥、`ADMIN_PASSWORD` 和 `DB_PASSWORD`;缺失、密钥少于 32 个 UTF-8 字节或使用示例值时应用会拒绝启动。
有效且由当前密钥体系签发的连接 Token,不会出现在列表或配置预览中;当前平台管理员或所属租户只有在复制/下载完整 MCP 配置时,才能通过限流、审计且 `no-store` 的端点即时揭示。历史上未保存密文、已撤销或已过期的 Token 不可揭示。轮换 `CREDENTIAL_KEY` 前重加密凭证;当前 HMAC 仅支持单 key,须先盘点旧 token ID,在维护窗口切 key/重启后再用新 key 签发、分发、验证并核对旧 ID 全部失效(旧 key 下预签发不能保持可用);轮换 plaintext 密钥前重加密全部未撤销连接/服务 Token 密文。完整步骤见 [`docs/connection-platform-operations.md`](docs/connection-platform-operations.md)。
## 🔧 内置企业微信 MCP 工具(6 个)
| 工具 | 说明 | 对应企微 API |
|------|------|----|
| `wecom_list_reports` | 汇报单号列表 | `oa/journal/get_record_list` |
| `wecom_get_report` | 汇报详情 | `oa/journal/get_record_detail` |
| `wecom_list_approvals` | 审批单号列表 | `oa/getapprovalinfo` |
| `wecom_get_approval_detail` | 审批详情 | `oa/getapprovaldetail` |
| `wecom_list_checkins` | 打卡记录 | `checkin/getcheckindata` |
| `wecom_list_smart_table_records` | 智能表格记录(一期搁置)| `wedoc/smartsheet/get_records` |
### WorkBuddy 连接配置
连接实例的 Token 页面可选择客户端和一个有效 Token,一键复制或下载完整配置;页面预览始终遮罩 Token,执行交付动作时才即时揭示。Claude Code、Cursor、Codex 和通用 Streamable HTTP 模板均使用同一个连接入口。手工配置示例:
```json
{
"mcpServers": {
"wecom-gateway": {
"type": "http",
"url": "https://mcp_host_name/mcp/connection_id_here",
"headers": { "Authorization": "Bearer ${WORKBUDDY_MCP_TOKEN}" }
}
}
}
```
> **代理坑点(必读)**:httpx 在 Windows 会读系统级代理,localhost/内网可能被错误代理致 502。WorkBuddy 机器若有系统代理,需 `NO_PROXY=mcp.example.com`。
## 🎛 管理后台
生产环境通过 `https://your-domain/admin/ui/` 访问,单密码登录(`.env` 的
`ADMIN_PASSWORD`)。应用端口 8001 仅绑定宿主机回环地址,不支持绕过 Nginx
直接访问。
| API | 说明 |
|-----|------|
| `POST /admin/login` | 密码登录 → session token(Cookie + Bearer 双支持) |
| `GET /admin/tenants` | 列出租户身份与登录状态 |
| `POST /admin/tenants` | 新增租户身份并设置必填初始密码 |
| `PUT /admin/tenants/{id}` | 编辑租户名称、状态或显式重置密码 |
| `DELETE /admin/tenants/{id}` | 删除无连接/服务保留历史的租户身份 |
| `/admin/tenants/{id}/connections` | 管理该租户的连接实例、凭据、同步策略与连接 Token |
创建租户时必须设置初始密码;管理员可重置密码或启用/禁用登录,但后台从不读取、回填既有密码。租户登录后在连接实例内管理连接配置、MCP Token、工具策略、可信域名、校验文件和调用日志。重置密码、租户自行改密或禁用登录都会撤销该租户现有会话。`MCP_SERVICE_ENABLED=true` 只用于存量服务端点的运行时兼容,不再开放租户服务管理入口,也不再回填默认服务;平台只保留隐藏的存量服务清理 API。
前端开发:`cd admin-ui && pnpm install && pnpm dev`(:5178 跨域代理后端);`pnpm build` 产出 `app/static/dist`。
## 🏢 多租户
**身份与连接分离**:租户记录只保存名称、ID、登录账户与状态;企业微信连接实例保存 CorpID、数据模式、同步模块/间隔、可信域名、校验文件以及加密凭据。校验文件可在连接工作台上传、查看公网 URL 和删除;同租户不同连接互不覆盖。新存储型连接的 schema 由后端分配;旧租户 schema 和历史校验文件在兼容期由默认连接继续引用。
**模块开关**:在企业微信连接实例中选择 `report`/`approval`/`checkin`。
**打卡 userid**:连接实例可配置通讯录 Secret 自动拉取,或填写用户 ID。
**隔离保证**:Token → 租户 → 连接 → schema 由服务端绑定,SQL 带 schema 前缀防连接池竞态,审计日志以租户和连接实例为主要维度。
## 🔄 同步任务
- APScheduler 启动立即首同步 + 周期遍历所有可同步连接
- 增量游标存各连接 schema 的 `sync_cursor`,断点续传
- 线程池执行不阻塞 MCP 事件循环
- 单租户/单条失败不中断整体;`MAX_DETAIL_PER_RUN=500` 防爆
- 打卡逐人拉取 + 容错:越权人员(301021)静默跳过不影响他人
## 📦 CI/CD
pull request 和 push 到 `main` 都会运行 test + security 门禁(`.github/workflows/build-push.yml`)。security 使用完整 Git 历史扫描 secret,审计现有 Python/Node 依赖契约,构建当前 commit 的本地镜像并扫描 HIGH/CRITICAL 漏洞,同时上传 SBOM。PR 不获得 `packages: write`,不登录也不推送 GHCR;只有非 PR 事件且两个门禁都通过后才发布 commit sha + `latest` 标签。
Python 依赖当前是 `requirements.txt` 范围契约,不是带哈希的完整锁定,因此 audit 反映当次解析结果,不代表可重现的 Python 依赖物料单。本阶段不新增锁文件或扩大依赖升级;Node 使用现有 `pnpm-lock.yaml` 与 `--frozen-lockfile`。
`latest` 仍由 CI 产生,供镜像浏览和旧手工流程兼容;生产发布脚本明确拒绝 `latest`。服务器只拉取选定的精确引用,无需本机 Node/Python 编译。
GHCR 私有 package 访问:公开镜像(GitHub package 页 → Package settings → Public,匿名拉取)或服务器 `docker login ghcr.io`。
## 🔐 企微接入前置(真实模式必看)
切 `WECOM_USE_MOCK=false` 前需在企微管理后台配置(详见 `docs/企微接入配置清单.md`):
| 接口类 | 需配置 | 错误码对照 |
|--------|--------|-----------|
| 全部 | **企业可信IP白名单**(加服务器公网IP)| `60020` = IP未加白 |
| 审批/汇报 | 审批/汇报应用 → API → 可调用接口的应用 加自建应用 | `301055` = 未授权 |
| 打卡 | 同上 + 应用可见范围含目标员工 | `301021` = 人员不在可见范围 |
| 通讯录 | 通讯录同步 secret(独立于自建应用 secret)| `40001` = secret错误 |
| 智能表格 | 应用开文档/智能表格权限 + 真实 docid | `48002` = 权限未开 |
⚠️ **智能表格读取一期搁置**:企微 docid 仅通过 API 创建文档可得,成员手工存量表无法获取 docid,故读取受限。
## 🛡 安全红线
- 凭证(corpid/secret/DB密码/ADMIN_PASSWORD)**禁止硬编码**,全走 `.env` 或 AES 加密入 DB
- `.env` 在 `.gitignore`,永不提交(仓库内只有 `.env.example`/`.env.prod.example` 模板)
- 客户授权完成前:仅 mock/脱敏数据,不长期保存客户原始数据,不接生产库
- 上线必做:DB 强密码 / `CREDENTIAL_KEY` 强随机 / 企微 secret 重签 / `ADMIN_PASSWORD` 改强
## 📁 项目结构
```
app/
config.py / auth.py / db.py / main.py # 配置/鉴权/数据层/入口
admin.py # 管理后台 API
connections/ / tenant_init.py # 连接领域;旧接入命令仅保留拒绝提示
openapi_source.py # OpenAPI URL 安全拉取
connectors/declarative/ # OpenAPI 分析、校验与受控 HTTP 运行时
mcp_gateway.py / mcp_server.py # 多连接网关 / 6 个内置企微工具
wecom/
client.py # 企微API客户端(token缓存隔离双secret)
sync.py / approval_sync.py / checkin_sync.py # 三类同步(分段+游标+去重)
contact.py # 通讯录userid自动拉
dispatch.py # 多租户调度遍历
mock.py # 脱敏mock数据
admin-ui/ # React+Vite+Ant Design 管理前端
sql/ # 建表脚本
deploy/ # nginx.conf / server_deploy.sh / wbsysc.service
.github/workflows/ # CI 构建镜像
docs/ # 架构计划/企微接入清单/部署指南
tests/test_smoke_client.py # MCP 客户端冒烟测试
```
## 📚 文档
- 多连接平台运维:[`docs/connection-platform-operations.md`](docs/connection-platform-operations.md)
- mcp-link 的 OpenAPI → MCP 实现对照:[`docs/research/mcp-link-openapi-to-mcp.md`](docs/research/mcp-link-openapi-to-mcp.md)
- 完整架构计划:[`docs/PLAN-wecom-mcp-gateway.md`](docs/PLAN-wecom-mcp-gateway.md)
- 企微接入配置清单:[`docs/企微接入配置清单.md`](docs/企微接入配置清单.md)
- 部署指南:[`docs/部署指南.md`](docs/部署指南.md)
## 🔧 技术栈
- **后端**:Python 3.11 + FastAPI + 官方 MCP Python SDK + SQLAlchemy + APScheduler + httpx
- **前端**:React 18 + Vite + Ant Design 5
- **存储**:MySQL 5.7+(多租户分 schema)+ 可选 Redis(token缓存)
- **部署**:Docker + docker-compose + Nginx/HTTPS + systemd
- **CI**:GitHub Actions → GHCR
This server cannot be deployed
Maintenance
ActivitySlowing
ResponsivenessNo issues