个人 MCP:管理员指南
谁该看这一篇
- IT / 部署管理员:你负责 Sira 的 sandrpod 集成、license 配置、合规边界
- 员工:去看 个人 MCP:员工指南
这是什么
让员工在自己电脑上通过 ~/.sandrpod/mcp.json 自助安装 MCP server,Sira AI 在任一渠道(企业微信 / 飞书 / Web)找到该员工时都能调用这些工具——凭据永不进 Sira 服务器。
设计意图:把"WorkBuddy 式员工桌面 AI"的差异化体验补齐,员工无需 IT 审批就能让 AI 用上自己的 GitHub / Notion / Jira 账号。
详细架构见 devdocs/SIRA_PERSONAL_MCP_INTEGRATION.md。
架构总览
┌────────────── 员工 PC ──────────────┐
│ │
│ ~/.sandrpod/mcp.json │
│ (Claude Desktop 兼容格式 + 个人凭据) │
│ │ │
│ ▼ │
│ sandrpod-agent (LaunchAgent) │
│ ├ fsnotify 监听 mcp.json │
│ ├ 拉起 N 个 stdio MCP 子进程 │
│ └ 聚合成单一 HTTP MCP endpoint │
│ │ │
│ ▼ yamux reverse tunnel │
└──────────────────────────────────────┘
│
▼
┌──────── sandrpod API Server ────────┐
│ 反向隧道转发 + corp 鉴权 │
│ /api/v1/sandboxes/{name}/mcp/* │
└──────────────────────────────────────┘
│
▼ HTTPS
┌──────── Sira Backend ───────────────┐
│ 心跳 (30s) 拉 manifest 写 mcp_tools │
│ Orchestrator 加载工具时 union 进来 │
│ 「我的 MCP」UI 展示员工自己的清单 │
└──────────────────────────────────────┘启用前置条件
1. sandrpod 部署
需要 sandrpod ≥ 含完整 personal MCP 链路的版本(5/28 11:11+ 打包):
pkg/mcpbridge包齐全(commit4d3d2c6起)cmd/agent支持--mcp-tokenBearer(commit4d3d2c6)cmd/server含authMiddleware X-Sandrpod-Token修复(commit5bbeaf5,见MCP_AUTH_HEADER_CONFLICT_FIX.md)cmd/agent+pkg/mcpbridge容忍 mcp.json 启动时不存在 +rm软停 + 父目录 watch(commit7794c8b,体验关键修复)
检查员工 agent 版本
让员工跑 ~/.sira/bin/sandrpod-agent -version,或直接看日志里有没有 no servers loaded yet (hot_reload=true will pick up a later mcp.json create) 字样。出现这行 = 含 7794c8b;出现旧的 MCP bridge: no config at ... bridge disabled = 老版本,让员工重装 installer。
校验 sandrpod 服务端版本:
curl -sS http://your-sandrpod:18089/health
# 期望 {"mode":"control-plane+tunnel", ...}2. Sira 数据库 migration
确保已应用:
| Migration | 作用 |
|---|---|
055_mcp_tools_personal_fields.py | mcp_tools 表加 provenance / sandbox_id / owner_user_id / last_manifest_at / manifest_state + 索引 |
056_sandboxes_mcp_token.py | sandboxes 表加 mcp_token_encrypted |
docker exec wxwork-backend bash -c '
for f in scripts/migrations/055_*.py scripts/migrations/056_*.py; do
python "$f"
done
'幂等。重跑无副作用。
3. License v2 启用 mcp.personal
mcp.personal 默认 minimum_tier=STANDARD(付费 org 自动享有)。
生产部署:通过正常 license 文件签发(license-server/ 那套)让目标 org 的 feature_set 包含 mcp.personal。
dev 环境 临时启用:
# 在 backend 容器里执行:
docker exec wxwork-backend python -c "
import json, uuid
from datetime import datetime, timezone, timedelta
from app.database import SessionLocal
from sqlalchemy import text
from app.services.license_v2.features import FEATURES
with SessionLocal() as db:
db.execute(text('DELETE FROM license_v2_org_grants WHERE organization_id = :o'),
{'o': 'org_default'})
db.execute(text('''
INSERT INTO license_v2_org_grants
(id, organization_id, license_id, license_type, feature_set,
limits, deployment_fingerprint, valid_until, created_at, updated_at)
VALUES (:id, :org, :lic, :ltype, CAST(:fs AS jsonb),
CAST(:lim AS jsonb), :fp, :vu, NOW(), NOW())
'''), {
'id': str(uuid.uuid4()), 'org': 'org_default',
'lic': 'dev-local', 'ltype': 'enterprise',
'fs': json.dumps(sorted(FEATURES.keys())),
'lim': json.dumps({}),
'fp': 'dev-local-fingerprint',
'vu': datetime.now(timezone.utc) + timedelta(days=3650),
})
db.commit()
"或者 dev 完全关 license:
# docker-compose.dev.yml
backend:
environment:
LICENSE_V2_MODE: off # 默认 enforce, dev 临时关4. Sandrpod corp 配置
/agent-system/sandboxes/config 页面(管理员) 填:
- API URL:sandrpod API Server 公网地址(员工 PC 拨号到这里)
- Agent-facing URL:员工 PC 可达的 URL(可与 API URL 同,DMZ 部署时不同)
- API Token:sandrpod 启动时
-token设的 corp 级 token
-- 直接看 DB 配置:
SELECT organization_id, api_url, agent_facing_url, enabled
FROM corp_sandrpod_config;DB schema 速查
mcp_tools (provenance='personal' 新增字段)
| 字段 | 类型 | 含义 |
|---|---|---|
provenance | VARCHAR(16) NOT NULL DEFAULT 'corp' | 'corp' 或 'personal' |
sandbox_id | VARCHAR(36) NULL | provenance='personal' 时必填,指向 sandboxes.id |
owner_user_id | VARCHAR(50) NULL | 冗余存一份方便按 user 索引 |
last_manifest_at | TIMESTAMP NULL | 上次 sandrpod /mcp/manifest 拉到数据时间 |
manifest_state | VARCHAR(16) NULL | 'online' / 'partial' / 'offline' / 'no_config' |
source_config JSON 内嵌 manifest 全文(schema 与 sandrpod /mcp/manifest 一致)。
sandboxes.mcp_token_encrypted (TEXT NULL)
Fernet 加密的 sandrpod --mcp-token Bearer。每 sandbox 独立。
License v2
| 字段 | 值 |
|---|---|
| feature key | mcp.personal |
| minimum_tier | STANDARD |
| 注册位置 | app/services/license_v2/features.py |
索引
ix_mcp_tools_personal ON mcp_tools(owner_user_id, sandbox_id)
WHERE provenance = 'personal' -- PG 部分索引
ix_mcp_tools_provenance ON mcp_tools(organization_id, provenance)监控指标 / 日志
当前现状
项目没有暴露 Prometheus /metrics scrape endpoint(依赖里有 prometheus-client,但未 wire 到 HTTP)。Sprint 5 阶段用结构化日志 + 内部 metrics_collector 双轨。生产真要建看板,按下述字段写 ELK / Loki / SigNoz query。
关键 logger 名
app.extensions.agent_system.services.sandbox_heartbeat_service
app.extensions.agent_system.services.sandrpod_client
app.extensions.agent_system.services.personal_mcp_loader
app.extensions.agent_system.api.sandboxes (refresh endpoint)关键日志事件
| 日志 grep | 含义 | 看板建议 |
|---|---|---|
[heartbeat] %d corp / %d sandboxes synced | 心跳一次的全局统计 | 时序:每 30s 一条;丢失 = 心跳挂了 |
[heartbeat] personal MCP 新建/更新 | upsert 成功 | 计数:personal_mcp_upsert_total |
[heartbeat] sandbox=%s manifest fetch 失败 | 单 sandbox 拉 manifest 失败 | 按 sandbox / org 分布 |
[heartbeat] corp=%s license mcp.personal not allowed, skip | License gate 关 | 显示哪些 org 没买 |
[personal_mcp] sandbox=%s 加载工具失败 | session 启动时拉 personal MCP 失败 | 影响真实 AI 调用 |
mcp.bridge:<tool_name> (caller 字段, sandbox_decision_events 表) | personal MCP 工具被调用 | 调用量看板(详见审计章节) |
用现有 metrics_collector(可选)
app/core/metrics/metrics_collector.py 提供进程内 deque 储存。Sprint 5 不强制接入,需要时按 metrics.store_counter("personal_mcp_refresh_total", labels={"result": "ok"}) 调用。当前 /metrics HTTP endpoint 不存在;要 scrape 需先 wire prometheus_client.make_asgi_app() 到 FastAPI。
故障排查(按现象)
UI 整体「在线」但 server 列表空
根因:refresh API live fetch 偶发失败,前后端都有回退到 DB cache,但极端情况下还是会出现。
查 DB 看是否有缓存:
SELECT name, manifest_state, last_manifest_at,
source_config->'manifest'->'total_tools' AS total_tools,
jsonb_array_length(source_config->'manifest'->'servers') AS server_count
FROM mcp_tools
WHERE provenance = 'personal' AND owner_user_id = '<user_id>';server_count > 0 说明 DB 缓存还在,前端拿到完整数据;server_count = 0 说明心跳从未成功过。
sandbox status=online 但 manifest_state=offline
根因:tunnel 通了(sandrpod 看到 sandbox online),但 /mcp/manifest 调用失败。
典型原因:
- MCP child 全 failed:mcp.json 里所有 server 都启不起来(npx 找不到 / 凭据缺失 /
startup_timeout_sec太短)。查员工电脑上~/.sira/log/sandbox.err看具体错误,或拉/admin/manifest看last_error字段 - mcp_token mismatch:员工电脑上 keychain 里的
SANDRPOD_MCP_TOKEN与 DB 里的不一致(重新 enroll 但 keychain 没更新)。让员工重装 - 老版 sandrpod-agent(< 5/28 fix 7794c8b)+ mcp.json 启动时不存在 → bridge
disabled。让员工重装 installer 把 agent 升上来,新版自动接住后续 mcp.json 创建,无需再重启
手动复现 manifest fetch:
# 拿 corp api_token + mcp_token
docker exec wxwork-backend python -c "
import asyncio
from app.extensions.agent_system.services.sandbox_registry import sandbox_registry
async def m():
cfg = await sandbox_registry.get_corp_config('<org_id>')
tok = await sandbox_registry.get_mcp_token('<sandbox_id>')
print('CORP=' + cfg.api_token)
print('MCP=' + tok)
print('URL=' + cfg.api_url)
asyncio.run(m())
"
# 直接 curl
curl -sS -H "X-Sandrpod-Token: <CORP>" -H "Authorization: Bearer <MCP>" \
"<URL>/api/v1/sandboxes/<sandbox_name>/mcp/manifest" | jqtransport closed 频繁出现
根因:员工 PC 上 MCP child 进程不稳定,启动后立刻退出。
最常见原因:
| 报错 | 解释 | 修复 |
|---|---|---|
exec: "npx": file not found | LaunchAgent PATH 缺 Homebrew | installer 模板已修,重装即可 |
| MCP server 启动需要凭据但缺失 | env 块没写对 | 让员工补 |
| npm 包还没缓存,下载慢 | 冷启动 30+ 秒 | 加 sandrpod.startup_timeout_sec: 120 |
心跳完全没跑
docker exec wxwork-backend python -c "
from app.extensions.agent_system.services.sandbox_heartbeat_service import get_global_heartbeat_service
svc = get_global_heartbeat_service()
print('running:', svc._task is not None and not svc._task.done())
print('interval:', svc.interval)
"如果 running=False,看 backend 启动日志找 SandboxHeartbeatService 启动错误。
License v2 拒了
docker exec wxwork-backend python -c "
from app.services.license_v2 import get_gate
d = get_gate().check('mcp.personal', organization_id='<org_id>')
print(f'allowed={d.allowed} source={d.source} reason={d.reason}')
"source='deny' + reason='feature not in v2 grant feature_set' = org 没买这个 feature。生产环境联系销售签发新 license;dev 环境按上面 §3 插 grant。
凭据轮换
单 sandbox 的 mcp_token
docker exec wxwork-backend python -c "
import asyncio
from app.extensions.agent_system.services.sandbox_registry import sandbox_registry
async def m():
new_token = await sandbox_registry.rotate_mcp_token('<sandbox_id>')
print('new mcp_token:', new_token)
asyncio.run(m())
"老 token 立即失效
DB 旧值被覆盖。员工 PC 上 sandrpod-agent 还在用旧 token,Sira 后端调用立即 401。员工需要:
- 在 tray 里手动把新 token 复制进 keychain(如果 tray 支持),或
- 重新跑一遍 installer,新 mcp_token 通过 enrollment JWT 下发到 keychain
sandrpod 当前没有 server-push token 更新的通道,未来如果加,rotate 才能做到无感。
Corp api_token 轮换
/agent-system/sandboxes/config 页面填新 token,DB 自动 Fernet 加密。sandrpod_client 会复用同实例 client 直到 cache 失效(5 分钟),新调用自动用新值。
审计:查 personal MCP 调用流
personal MCP 调用走的是 sandrpod 决策审计管道,存在 sandbox_decision_events 表,与 platform action audit 是两条独立流。
SELECT timestamp, decision, source, path, caller, reason
FROM sandbox_decision_events
WHERE source IN ('mcp.spawn', 'mcp.call', 'mcp.restart')
AND organization_id = '<org_id>'
AND timestamp > NOW() - INTERVAL '24 hours'
ORDER BY timestamp DESC
LIMIT 100;字段约定:
| source | path | caller | 含义 |
|---|---|---|---|
mcp.spawn | mcp:<server_name> | mcp.bridge | 子进程被启动 |
mcp.call | mcp:<server_name> | mcp.bridge:<tool_name> | AI 调用了某工具 |
mcp.restart | mcp:<server_name> | mcp.bridge | child 崩了自动重启 |
不记录的
绝不记录 args / result / env 值(sandrpod 设计意图)。审计只回答"何时何工具被调用,成功/失败",不回答"调了什么"。
UI 视图:/agent-system/sandboxes → 「AI 决策审计」tab(管理员可见),按 source LIKE 'mcp.%' 过滤。
合规边界
| 数据 | 留在哪 | Sira 后端可见 | IT 管理员可见 |
|---|---|---|---|
mcp.json 内容 | 员工 PC | ❌ | ❌ |
mcp.json env 块凭据 (token / API key) | 员工 PC 进程 env | ❌ | ❌ |
mcp_token (sandrpod /mcp 端点 Bearer) | 员工 keychain + Sira DB (Fernet) | ✅ 加密 | ⚠ 可解密但不该看 |
| manifest 摘要(server 名 / 状态 / 工具数) | mcp_tools.source_config | ✅ | ✅ |
| 工具调用事实 | sandbox_decision_events | ✅ | ✅ |
| 工具调用参数 / 结果 | 任何地方都不存 | ❌ | ❌ |
法务沟通点:sandrpod 的 mcp bridge 设计上不让任何工具参数 / 返回值离开员工 PC(只走 stdio child → 隧道 → orchestrator runtime → 立即 LLM 决策),落盘的仅 audit 元数据。
参考
- 设计文档:
SIRA_PERSONAL_MCP_INTEGRATION.md - sandrpod 侧:
MCP_BRIDGE.md/MCP_AUTH_HEADER_CONFLICT_FIX.md - 员工视角:
personal-mcp.md - 决策审计:
decision-audit.md - 沙箱安装:
install-guide.md
