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

3.8 KiB

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.rsTokenScope 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.rspub 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.extensionsExtension<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).