Files
secrets/AGENTS.md
voson f720983328
Some checks failed
Secrets MCP — Build & Release / 部署 secrets-mcp (push) Has been cancelled
Secrets MCP — Build & Release / 检查 / 构建 / 发版 (push) Has started running
refactor(db): 移除无意义 actor,修复 history 多租户与模型
- 删除 entries_history / audit_log / secrets_history 的 actor 列及写入逻辑
- MCP secrets_history 透传当前 user_id
- Entry 增加 user_id,search 查询不再用伪 UUID
- 迁移:保留 users.api_key,从 api_keys 表回退时生成新明文 key 并删表
- 文档:audit_log auth 语义、API Key 存储说明

Made-with: Cursor
2026-03-21 16:45:50 +08:00

7.2 KiB
Raw Permalink Blame History

Secrets MCP — AGENTS.md

本仓库为 MCP SaaSsecrets-core(业务与持久化)+ secrets-mcpStreamable HTTP MCP、Web、OAuth、API Key。对外入口见 crates/secrets-mcp

提交 / 发版硬规则(优先于下文)

  1. 涉及 crates/**、根目录 Cargo.toml/Cargo.locksecrets-mcp 行为变更的提交,默认视为需要发版,除非明确说明「本次不发版」。
  2. 发版前检查 crates/secrets-mcp/Cargo.tomlversion,再查 taggit tag -l 'secrets-mcp-*'
  3. 若当前版本对应 tag 已存在,默认允许复用现有 tag 继续构建;仅在需要新的发布版本时再 bump versioncargo build 同步 Cargo.lock
  4. 提交前优先运行 ./scripts/release-check.sh(版本/tag + fmt + clippy --locked + test --locked)。

项目结构

secrets/
  Cargo.toml
  crates/
    secrets-core/       # db / crypto / models / audit / service
    secrets-mcp/        # rmcp tools、axum、OAuth、Dashboard
  scripts/
    release-check.sh
    setup-gitea-actions.sh
  .gitea/workflows/secrets.yml
  .vscode/tasks.json

数据库

  • 建议库名secrets-mcp(专用实例,与历史库名区分)。
  • 连接:环境变量 SECRETS_DATABASE_URL(本分支无本地配置文件路径)。
  • entries(含 user_id)、secretsentries_historysecrets_historyaudit_logusersoauth_accounts,首次连接 auto-migrate

表结构(摘录)

entries (
  id          UUID PRIMARY KEY DEFAULT uuidv7(),
  user_id     UUID,                                -- 多租户NULL=遗留行;非空=归属用户
  namespace   VARCHAR(64)   NOT NULL,
  kind        VARCHAR(64)   NOT NULL,
  name        VARCHAR(256)  NOT NULL,
  tags        TEXT[]        NOT NULL DEFAULT '{}',
  metadata    JSONB         NOT NULL DEFAULT '{}',
  version     BIGINT        NOT NULL DEFAULT 1,
  created_at  TIMESTAMPTZ   NOT NULL DEFAULT NOW(),
  updated_at  TIMESTAMPTZ   NOT NULL DEFAULT NOW()
)
secrets (
  id          UUID PRIMARY KEY DEFAULT uuidv7(),
  entry_id    UUID         NOT NULL REFERENCES entries(id) ON DELETE CASCADE,
  field_name  VARCHAR(256) NOT NULL,
  encrypted   BYTEA        NOT NULL DEFAULT '\x',
  version     BIGINT       NOT NULL DEFAULT 1,
  created_at  TIMESTAMPTZ  NOT NULL DEFAULT NOW(),
  updated_at  TIMESTAMPTZ  NOT NULL DEFAULT NOW(),
  UNIQUE(entry_id, field_name)
)

users / oauth_accounts

users (
  id           UUID PRIMARY KEY DEFAULT uuidv7(),
  email        VARCHAR(256),
  name         VARCHAR(256) NOT NULL DEFAULT '',
  avatar_url   TEXT,
  key_salt     BYTEA,         -- PBKDF2 salt32B首次设置密码短语时写入
  key_check    BYTEA,         -- 派生密钥加密已知常量,用于验证密码短语
  key_params   JSONB,         -- 算法参数,如 {"alg":"pbkdf2-sha256","iterations":600000}
  api_key      TEXT UNIQUE,   -- MCP Bearer token当前实现为明文存储
  created_at   TIMESTAMPTZ NOT NULL DEFAULT NOW(),
  updated_at   TIMESTAMPTZ NOT NULL DEFAULT NOW()
)

oauth_accounts (
  id           UUID PRIMARY KEY DEFAULT uuidv7(),
  user_id      UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
  provider     VARCHAR(32) NOT NULL,
  provider_id  VARCHAR(256) NOT NULL,
  ...
  UNIQUE(provider, provider_id)
)

audit_log / history

与迁移脚本一致:audit_logentries_historysecrets_history 用于审计与时间旅行恢复;字段定义见 crates/secrets-core/src/db.rsmigrate SQL。audit_log 中普通业务事件的 namespace/kind/name 对应 entry 坐标;登录类事件固定使用 namespace='auth',此时 kind/name 表示认证目标而非 entry 身份。

字段职责

字段 含义 示例
namespace 隔离空间 refining
kind 记录类型 server, service, key
name 标识名 gitea, i-example0…
tags 标签 ["aliyun","prod"]
metadata 明文描述 ipurlkey_ref
secrets.field_name 加密字段名(明文) token, ssh_key
secrets.encrypted 密文 AES-GCM

PEM 共享(key_ref

将共享 PEM 存为 kind=key 的 entry其它记录在 metadata.key_ref 指向该 key 的 name。更新 key 记录后,引用方通过服务层解析合并逻辑即可使用新密钥(实现见 secrets_core::service)。

代码规范

  • 错误:业务层 anyhow::Result,避免生产路径 unwrap()
  • 异步:tokio + sqlx async。
  • SQLsqlx::query / query_as 参数绑定;动态 WHERE 仍须用占位符绑定。
  • 日志:运维用 tracing;面向用户的 Web 响应走 axum handler。
  • 审计:写操作成功后尽量 audit::log_tx;失败可 warn,不掩盖主错误。
  • 加密:密钥由用户密码短语通过 PBKDF2-SHA256600k 次) 在客户端派生,服务端只存 key_salt/key_check/key_params不持有原始密钥。Web 客户端在浏览器本地完成加解密MCP 客户端通过 X-Encryption-Key 请求头传递密钥,服务端临时解密后返回明文。
  • MCPtools 参数与 JSON Schemaschemars)保持同步,鉴权以请求扩展中的用户上下文为准。

提交前检查

./scripts/release-check.sh

或手动:

cargo fmt -- --check
cargo clippy --locked -- -D warnings
cargo test --locked

发版前确认未重复 tag

grep '^version' crates/secrets-mcp/Cargo.toml
git tag -l 'secrets-mcp-*'

CI/CD

  • 触发:任意分支 push,且路径含 crates/**deploy/**、根目录 Cargo.tomlCargo.lock(见 .gitea/workflows/secrets.yml)。
  • 版本与 tag:从 crates/secrets-mcp/Cargo.toml 读版本;若远程已存在同名 secrets-mcp-<version> tag则复用现有 tag 继续构建;否则由 CI 创建并推送该 tag。
  • 质量与构建fmt / clippy --locked / test --lockedx86_64-unknown-linux-musl 发布构建 secrets-mcp
  • Release可选secrets.RELEASE_TOKENGitea PAT用于创建草稿 Release、上传 tar.gz + .sha256、构建成功后发布;未配置则跳过 API Release仅 tag + 构建。
  • 部署(可选):仅 mainfeat/mcpmcp 分支在构建成功时跑 deploy-mcp;需 vars.DEPLOY_HOSTvars.DEPLOY_USERsecrets.DEPLOY_SSH_KEY。勿把 OAuth/DB 等写进 workflowdeploy/.env.example 在目标机配置。
  • Secrets 写法Actions secrets 须为原始值PEM、PAT 明文), base64否则 SSH/Release 会失败。在 CI 中保存 GOOGLE_CLIENT_SECRET、DB 密码。
  • 通知vars.WEBHOOK_URL(可选,飞书)。

环境变量secrets-mcp

变量 说明
SECRETS_DATABASE_URL 必填。PostgreSQL URL。
BASE_URL 对外基址OAuth 回调 ${BASE_URL}/auth/google/callback
SECRETS_MCP_BIND 监听地址,默认 0.0.0.0:9315
GOOGLE_CLIENT_ID / GOOGLE_CLIENT_SECRET 可选;仅运行时配置。
RUST_LOG secrets_mcp=debug

SERVER_MASTER_KEY 已不再需要。新架构下密钥由用户密码短语在客户端派生,服务端不持有。