yggdrasil/.scratch/mcp-server/issues/T1-tracer-bullet.md
xfy 9eac025b3e feat(mcp): 完整工具面 + token 管理 + 管理页(T2-T5 并行实现 + 合并)
T2 token 管理(src/api/mcp_tokens.rs):create/list/reveal/revoke + get_mcp_client_configs
服务端函数,均经 get_current_admin_user 鉴权;TokenLifetime 枚举(1/7/30/90天/永不过期)。
src/mcp/config.rs 生成 4 种客户端配置(Claude Code/Cursor/Cline/通用 + CLI)。
src/pages/admin/mcp.rs 令牌管理页:列表 + 新建表单 + 一次性明文展示/重查 + 撤销 +
配置片段卡片。路由 /admin/mcp,导航项加入 admin_layout。

T3 read 工具 + Resources(tools/read.rs, resources.rs):search_posts/get_post/list_tags
(read 作用域,仅已发布);published-post Resources(post://{slug},游标分页)。
search_published 提取为共享函数。

T4 write 工具(tools/{posts,comments,tags,media}.rs):create/update/publish/trash/delete_post、
评论审核、标签 CRUD、媒体 base64 上传(WebP 转码 + 去重)。复用既有 helper 的 SQL 与
缓存失效(moka + ssr_cache)。write 作用域,可读草稿。

T5 admin 工具(tools/{settings,runner}.rs):get/update_settings、run_code(沙箱执行)。
admin 作用域。

合并(server.rs):用 rmcp tool_router 的「命名路由 + 组合」模式,7 个工具组在
YggMcpServer 上 impl,combined_router 用 + 合并成单一 ServerHandler。各工具组鉴权
独立,经 Extension<Parts> 读 McpPrincipal + scope.grants 校验。

新增依赖 base64 0.22(媒体上传解码)。验证:server/web 双目标编译通过、clippy
--all-features -D warnings 干净、659 单测 + 1 集成全绿(+20 新测试)。
2026-07-28 11:53:32 +08:00

61 lines
3.8 KiB
Markdown

# T1 — Tracer bullet: end-to-end MCP skeleton (read path)
> **Type:** tracer bullet. Proves the whole stack on BOTH build targets before any
> tool-surface flesh-out. Do this FIRST and get it green end-to-end.
## Blocking edges
- **Blocks:** T2 (token mgmt+UI), T3 (read tools+resources), T4 (write tools),
T5 (admin tools), T6 (hardening).
- **Blocked by:** nothing (this is the root).
## Target files
- `Cargo.toml` — add `rmcp` + `aes-gcm` (optional, under `server` feature).
- `migrations/015_mcp_tokens.sql` — the `mcp_tokens` table (see spec §5.1).
- `src/db/migrate.rs` — register `("015", include_str!(...))` in `MIGRATIONS`.
- `src/models/mcp_token.rs``TokenScope` enum (Read/Write/Admin, `>=` ordering, serde),
`McpToken`, `McpTokenSummary`, `CreateTokenResponse`.
- `src/mcp/mod.rs`, `src/mcp/crypto.rs`, `src/mcp/auth.rs`, `src/mcp/server.rs`,
`src/mcp/router.rs` — new module (server-only impl + WASM stubs).
- `src/main.rs` — build `mcp_route` and `.merge(mcp_route)` into the app router
(alongside existing merges at ~line 332).
- `src/lib.rs` / `src/main.rs``pub mod mcp;` gated by feature.
- `.env.example` — document `MCP_TOKEN_ENC_KEY`.
## Change
1. Add deps; verify `rmcp` builds against **axum 0.8** and exposes a Tower
`StreamableHttpService` mountable via `nest_service`. If the version is incompatible,
STOP and report — do not silently hand-roll JSON-RPC (that's a spec-level decision).
2. Migration + model (spec §5). `token_hash` = SHA-256 hex for lookup;
`token_enc` = AES-GCM (nonce‖ct‖tag) hex.
3. `crypto.rs`: `encrypt_token(plain, &key) -> String` / `decrypt_token(enc, &key) ->
Option<String>` using `aes-gcm`. Read key from `MCP_TOKEN_ENC_KEY` (base64 32-byte).
`.expect()` only at LazyLock init of a parsed key is acceptable per AGENTS.md §16.
4. `auth.rs`: bearer extractor — parse `Authorization: Bearer ygg_...`, hash, DB-lookup
the active (non-revoked, non-expired) row, return `(user_id, scope)`, bump
`last_used_at`. Origin check → 403 (reuse CSRF trusted-origin allowlist helper).
5. `server.rs`: minimal rmcp `ServerHandler` exposing ONE tool — `search_posts(query)`
— wired to the existing FTS search helper in `src/api/posts/search.rs`. Return
summaries. (Full Resources/pagination is T3; here just prove dispatch works.)
6. `router.rs`: assemble `StreamableHttpService` (stateless) and return an
`axum::Router` for `/mcp`. Merge into the app router in `main.rs`.
7. WASM stubs: every public symbol gets a `#[cfg(not(feature="server"))]` stub so
`--features web` compiles.
## Acceptance — STATUS (2026-07-28)
- **rmcp version pinned: `=3.0.0-beta.3`** (NOT stable 0.2.1 — see spec §7). Axum-0.8
compatibility verified by a throwaway probe: `nest_service("/mcp", StreamableHttpService)`
mounts; auth flows via `from_fn` middleware → `request.extensions` → `Extension<Parts>`;
real MCP `tools/list` + `tools/call` round-trip confirmed; Origin→403 confirmed.
- No-token → 401, bad Origin → 403 (rmcp built-in): **verified at probe level**.
(Live-server token-seed smoke test deferred to T7 integration — needs a running DB.)
- `token_enc` column stores AES-GCM ciphertext (crypto.rs unit-tested: round-trip,
tamper-fail, wrong-key-fail, nonce-distinct — 8 tests green).
- `cargo build --no-default-features --features web` ✓ (mcp module is `#[cfg(server)]`,
so WASM build never touches rmcp/aes-gcm — no stubs needed by design).
- `cargo build --no-default-features --features server` ✓.
- `cargo clippy --all-features -- -D warnings` ✓ (added `#[allow(dead_code)]` on the
mcp module + mcp_token model: forward-public symbols consumed by T2/T3).
- `cargo test --features server` ✓ 639 unit + 1 integration, +13 new MCP tests.
- Deferred to T7: real-client end-to-end against a live DB (T1 proved the *mechanism*
via probe, not against the real DB-backed auth path).