# Secrets MCP — AGENTS.md 本仓库为 **MCP SaaS**:`secrets-core`(业务与持久化)+ `secrets-mcp`(Streamable HTTP MCP、Web、OAuth、API Key)。对外入口见 `crates/secrets-mcp`。 ## 提交 / 发版硬规则(优先于下文) 1. 涉及 `crates/**`、根目录 `Cargo.toml`/`Cargo.lock`、`secrets-mcp` 行为变更的提交,默认视为**需要发版**,除非明确说明「本次不发版」。 2. 发版前检查 `crates/secrets-mcp/Cargo.toml` 的 `version`,再查 tag:`git tag -l 'secrets-mcp-*'`。 3. 若当前版本对应 tag 已存在,须先 bump `version`,再 `cargo 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`)、`secrets`、`entries_history`、`secrets_history`、`audit_log`、`users`、`oauth_accounts`、`api_keys`,首次连接 **auto-migrate**。 ### 表结构(摘录) ```sql 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() ) ``` ```sql 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 / api_keys ```sql users ( id UUID PRIMARY KEY DEFAULT uuidv7(), email VARCHAR(256), name VARCHAR(256) NOT NULL DEFAULT '', avatar_url TEXT, key_salt BYTEA, -- PBKDF2 salt(32B),首次设置密码短语时写入 key_check BYTEA, -- 派生密钥加密已知常量,用于验证密码短语 key_params JSONB, -- 算法参数,如 {"alg":"pbkdf2-sha256","iterations":600000} 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) ) api_keys ( id UUID PRIMARY KEY DEFAULT uuidv7(), user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE, name VARCHAR(256) NOT NULL, key_hash VARCHAR(64) NOT NULL UNIQUE, key_prefix VARCHAR(12) NOT NULL, last_used_at TIMESTAMPTZ, created_at TIMESTAMPTZ NOT NULL DEFAULT NOW() ) ``` ### audit_log / history 与迁移脚本一致:`audit_log`、`entries_history`、`secrets_history` 用于审计与时间旅行恢复;字段定义见 `crates/secrets-core/src/db.rs` 内 `migrate` SQL。 ### 字段职责 | 字段 | 含义 | 示例 | |------|------|------| | `namespace` | 隔离空间 | `refining` | | `kind` | 记录类型 | `server`, `service`, `key` | | `name` | 标识名 | `gitea`, `i-example0…` | | `tags` | 标签 | `["aliyun","prod"]` | | `metadata` | 明文描述 | `ip`、`url`、`key_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。 - SQL:`sqlx::query` / `query_as` 参数绑定;动态 WHERE 仍须用占位符绑定。 - 日志:运维用 `tracing`;面向用户的 Web 响应走 axum handler。 - 审计:写操作成功后尽量 `audit::log_tx`;失败可 `warn`,不掩盖主错误。 - 加密:密钥由用户密码短语通过 **PBKDF2-SHA256(600k 次)** 在客户端派生,服务端只存 `key_salt`/`key_check`/`key_params`,不持有原始密钥。Web 客户端在浏览器本地完成加解密;MCP 客户端通过 `X-Encryption-Key` 请求头传递密钥,服务端临时解密后返回明文。 - MCP:tools 参数与 JSON Schema(`schemars`)保持同步,鉴权以请求扩展中的用户上下文为准。 ## 提交前检查 ```bash ./scripts/release-check.sh ``` 或手动: ```bash cargo fmt -- --check cargo clippy --locked -- -D warnings cargo test --locked ``` 发版前确认未重复 tag: ```bash grep '^version' crates/secrets-mcp/Cargo.toml git tag -l 'secrets-mcp-*' ``` ## CI/CD - **触发**:`main` / `feat/mcp`(以仓库 workflow 为准);路径含 `crates/**`、`deploy/**`、`Cargo.toml`、`Cargo.lock`。 - **构建**:`x86_64-unknown-linux-musl` → `secrets-mcp`。 - **Release**:tag `secrets-mcp-`,上传 tar.gz + `.sha256`。 - **部署**:可选在仓库 Actions 中配置 `vars.DEPLOY_HOST`、`vars.DEPLOY_USER` 与 `secrets.DEPLOY_SSH_KEY`(勿写进 workflow);可用 `scripts/setup-gitea-actions.sh` 调 Gitea API 写入。Actions **secrets 须为原始值**(如 PEM 全文、PAT 明文),**不要**先 base64 再写入,否则工作流内无法识别(例如 SSH 私钥无效)。**勿**在 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`。 | | `USER` | 若写入审计 `actor`,由运行环境提供。 | > `SERVER_MASTER_KEY` 已不再需要。新架构下密钥由用户密码短语在客户端派生,服务端不持有。