Skip to content

feat(mcp): replace stub handlers with service dispatch - #102

Merged
couragehong merged 6 commits into
feat/go-migrationfrom
couragehong/feat/mcp-tools-impl
May 7, 2026
Merged

couragehong merged 6 commits into
feat/go-migrationfrom
couragehong/feat/mcp-tools-impl

Conversation

@couragehong

Copy link
Copy Markdown
Contributor

v0.4 go-migration - "not yet implemented" stub 으로만 떠있던 8개 MCP 핸들러를 실제 service dispatch 로 교체하는 작업입니다.

  • Deps 구조체에 State + 3개 service (Capture / Recall / Lifecycle) 주입하고, write tools (capture / batch_capture / recall / delete_capture) 는 CheckState 게이트 통과해야 호출되도록,
  • read/diagnostic tools (capture_history / vault_status / diagnostics) + reload_pipelines 는 게이트 우회.
  • 에러는 domain.MakeError shape 그대로 JSON 직렬화 후 TextContent 로 감싸고,
  • cmd/rune-mcp/main.go 도 같이 wiring — buildDeps() 가 단일 Manager 포인터를 3개 service 가 공유하게 만들어서 한 번의 SetState 전이가 모든 게이트에 일관되게 반영되도록 하였습니다.

중요한 한계: lifecycle/boot.go RunBootLoop 본체는 여전히 TODO 스텁. 즉, 이 PR 만 머지하면 state 가 영원히 Starting 에 머물러서 write tools 는 항상 PIPELINE_NOT_READY 로 떨어짐. read-only tools 는 정상 동작 (degraded mode 진단용). boot loop 본체 + config env 우선순위 + FHE 키 디스크 저장은 별도 후속 PR — 자세한 P0/P1/P2 분류는 docs/v04/notes/audit-production-readiness.md 참조.

base 는 wip/go-impl-runtime. #93/95/96 머지 후 retarget.


Scope

Tasks 1 + 2 of the v0.4 Go migration runtime impl: replace the Phase-A stub MCP handlers with state-gated service dispatch and wire Deps construction in cmd/rune-mcp/main.go.

5 commits, 6 files of source + 2 audit docs (~818 LoC):

  • feat(mcp): expand Deps with services + add result helpers — Deps gets State + 3 services; result.go adds errorResult / okResult
  • feat(mcp): replace stub handlers with state-gated service dispatch — handlers.go adds 8 typed ToolHandlerFor[In,Out] factories. Write tools state-gated; read/diagnostic + reload_pipelines bypass. mustAdd (renamed) accepts a typed handler. CheckState becomes nil-safe.
  • feat(mcp): wire Deps construction + boot loop in cmd/rune-mcp/main.go — buildDeps() shares one Manager pointer across services; main() spawns lifecycle.RunBootLoop in background.
  • test(mcp): read-only bypass + error result shape coverage — 2 fn / 4 subtest on top of existing catalog/gating suite
  • docs(v04): add Python parity + production-readiness audits for mcp-tools-impl — 2 audit docs (mcp-tools parity + cross-cutting production readiness)

Stacked-PR base

Base = wip/go-impl-runtime, an integration branch that local-merges
the still-under-review #93/#95/#96. With this base, the PR diff is exactly
the mcp-tools-impl work — no noise from the prerequisite ports.

When #93/#95/#96 merge upstream, rebase wip + retarget this PR's base to
feat/go-migration via GitHub UI.

Spec

  • docs/v04/spec/components/rune-mcp.md §MCP Tools + §에러 처리 + §부팅 시퀀스
  • docs/v04/overview/architecture.md §Scope (agent-delegated only)
  • 8 tool names bit-identical to Python mcp/server/server.py (with Go-specific rune_ prefix per spec code samples)
  • Error response shape from Python mcp/server/errors.py make_error
  • Full audits at docs/v04/notes/audit-mcp-tools-impl.md + docs/v04/notes/audit-production-readiness.md

Behavior matrix

Tool State gate Service method
rune_capture active only Capture.Handle
rune_batch_capture active only Capture.Batch
rune_recall active only Recall.Handle
rune_delete_capture active only Lifecycle.DeleteCapture
rune_capture_history bypass Lifecycle.CaptureHistory
rune_vault_status bypass Lifecycle.VaultStatus
rune_diagnostics bypass Lifecycle.Diagnostics
rune_reload_pipelines bypass Lifecycle.ReloadPipelines

Production-readiness

audit-production-readiness.md flags one P0 blocker: lifecycle/boot.go RunBootLoop is still a no-op stub. Until a follow-up PR implements config-load + Vault dial + adapter wiring, write tools always surface PIPELINE_NOT_READY in production. Read-only tools work degraded.

This PR ships the handler wiring; the boot-loop body is intentionally a separate concern.

@couragehong
couragehong marked this pull request as draft May 5, 2026 23:11
@couragehong couragehong self-assigned this May 5, 2026
@couragehong
couragehong force-pushed the wip/go-impl-runtime branch from 12a7b11 to 6d2a85a Compare May 6, 2026 22:33
Deps gains 4 fields: State (*lifecycle.Manager), Capture/Recall/Lifecycle
service pointers. Adapter clients live on the services, populated by the
boot loop after Vault success.

Adds internal/mcp/result.go with two helpers:
- errorResult(err): JSON-marshals domain.MakeError(err) into TextContent +
  IsError=true. Preserves the {ok,error{code,message,retryable,recovery_hint}}
  shape of Python make_error.
- okResult(payload): JSON-marshals success payload into TextContent. Marshal
  failure degrades to errorResult(INTERNAL_ERROR) instead of panicking.

stubHandler is still in place; subsequent commits replace per-tool wiring.
Adds internal/mcp/handlers.go with 8 typed handler factories — one per
tool. Each handler:
  1. (write tools) CheckState gate → PIPELINE_NOT_READY when not active
  2. (write tools) per-tool input validator (ValidateCaptureRequest /
     ValidateRecallArgs)
  3. service dispatch (Capture/Recall/Lifecycle method)
  4. errorResult / okResult shaping (result.go)

Write tools (capture, batch_capture, recall, delete_capture) are
state-gated. Read/diagnostic tools (capture_history, vault_status,
diagnostics) bypass the gate so users can troubleshoot pre-active.
reload_pipelines also bypasses (it IS the dormant→active unblocker).

CheckState becomes nil-safe: Deps.State == nil returns PIPELINE_NOT_READY
with an explanatory hint instead of panicking.

Renames mustAddTool → mustAdd; the handler is now passed in as a typed
ToolHandlerFor[In,Out] argument rather than synthesized via stubHandler
generics. stubHandler / stubResult are removed.

Phase A.5 smoke test (register_test.go) updates:
  - newSession constructs a usable Deps (NewManager + service factories,
    State wired into Capture/Lifecycle); the empty-Deps regression check
    is preserved at the catalog level
  - TestRegister_StubReturnsIsError → TestRegister_WriteToolsGated:
    asserts PIPELINE_NOT_READY for the 4 write tools instead of a stub
    marker; reload_pipelines explicitly excluded with a doc note
  - TestMustAddTool_PanicsOnInvalidName → TestMustAdd_PanicsOnInvalidName
buildDeps() constructs the State manager (lifecycle.NewManager) and the
3 services (Capture/Recall/Lifecycle), sharing the same Manager pointer
across services so a single SetState transition is visible everywhere
the gate looks. main() spawns RunBootLoop in the background.

Adapter clients are NOT wired here — RunBootLoop is the right home for
Vault.GetPublicKey + bundle parsing + envector.NewClient + embedder.New
once those gRPC paths are real. With the boot loop currently a no-op
stub (lifecycle/boot.go), the daemon stays in StateStarting forever and
write tools surface PIPELINE_NOT_READY; read-only tools (vault_status,
diagnostics, capture_history) work degraded so the operator can probe.

Phase A.5 smoke + cmd test still pass.
Adds 2 test functions / 4 subtest cases on top of the existing
catalog/gating suite:

  - TestRegister_ReadOnlyToolsBypassGate: vault_status / diagnostics /
    capture_history must respond OK (not PIPELINE_NOT_READY) even when
    State == StateStarting. Asserts:
      * vault_status with nil Vault → 'standard' mode marker
      * diagnostics → 4 of 7 sections present (environment, vault, keys,
        embedding); skips state assertion because the field reflects
        config.json (not Manager) and the test host may have a real one
      * capture_history → ok:true, no error
  - TestRegister_ErrorResultPreservesRuneError: triggers
    PIPELINE_NOT_READY via rune_capture in StateStarting and json.Unmarshal-s
    the TextContent. Asserts shape matches domain.MakeError(): ok=false,
    error.code, error.retryable=false, error.recovery_hint mentions
    'starting'.

Total mcp suite: 8 functions / 12 subtests, ~0.5s.
go test ./...  ok across all packages.
…ols-impl

audit-mcp-tools-impl.md:
  Tool catalog parity (8 names — Go uses rune_ prefix, Python bare name —
  documented intentional divergence). State gate parity (CheckState atomic
  vs Python _ensure_pipelines Event-wait). Validation parity (Python
  L1240/L910 ↔ Go state.go ValidateCaptureRequest/ValidateRecallArgs;
  2 Go-stricter empty-input checks). Error response shape bit-identical
  (domain.MakeError matches Python make_error). Agent-delegated mode
  v0.4 narrowing (drops Python's legacy 3-tier fallback per D14/D21/D28).
  6 open follow-up gaps including the largest — boot loop body stub.

audit-production-readiness.md:
  Cross-cuts the 3 task PRs + integration base. P0 blocker: boot loop body
  (lifecycle/boot.go is no-op stub → write tools never reach Active).
  P1: RUNEVAULT_GRPC_TARGET env, embedder socket path resolver, FHE key
  disk persistence. P2: request_id propagation, Tier 2 fallback hookup,
  vector_dim verification, runed typed errors. P3: doc consistency,
  integration tag tests, dormant_reason persistence. Includes recommended
  PR ordering and an end-to-end smoke target.
@couragehong
couragehong force-pushed the couragehong/feat/mcp-tools-impl branch from bde38a0 to 276f973 Compare May 6, 2026 22:35
@couragehong
couragehong changed the base branch from wip/go-impl-runtime to feat/go-migration May 6, 2026 22:40
@couragehong
couragehong marked this pull request as ready for review May 6, 2026 22:40
@couragehong
couragehong merged commit cbd9e98 into feat/go-migration May 7, 2026
1 check passed
@couragehong
couragehong deleted the couragehong/feat/mcp-tools-impl branch May 7, 2026 10:50
couragehong added a commit that referenced this pull request May 8, 2026
The Go rune-mcp at internal/* + cmd/rune-mcp/ has reached parity (PR
#102 + #110 + #117) and end-to-end verification (#118, #122, #124),
which means the Python tree is now dead weight. Carrying both
implementations is actively misleading: a fresh contributor following
the in-repo install instructions would still land in mcp/ + agents/
and try to set up a venv that no longer ships, and parity audits keep
re-discovering the Python source instead of treating the Go side as
the source of truth.

Removed
-------

  agents/common/        12 files  (config, embedding, llm, schemas)
  agents/retriever/      4 files  (query_processor, searcher, synthesizer)
  agents/scribe/        12 files  (detector, llm_extractor, handlers, server)
  agents/tests/         16 files  (pytest suite)
  agents/__init__.py
  agents/README.md            (Python agents intro — gone with the impl)
  agents/SLACK_SETUP.md       (Slack notifier setup for Python scribe)
  mcp/                  18 files  (Python adapter + server + tests)
  requirements.txt            (root Python dependency list)
  scripts/migrate_embeddings.py (one-off Python migration helper)

  Total: 67 files, 17,815 lines.

Kept (intentional)
------------------

  agents/claude/{scribe,retriever}.md   referenced by .claude-plugin/
                                        plugin.json — agent prompts that
                                        the runtime loads
  agents/codex/scribe.md                Codex-side agent prompt
  agents/gemini/{scribe,retriever}.md   Gemini-side agent prompts
  benchmark/                            deferred (separate decision —
                                        rewrite in Go vs delete entirely)
  docs/v04/spec/python-mapping.md       parity blueprint that maps Python
                                        source to Go destinations; useful
                                        as a historical record post-deletion
  docs/migration/*.md                   migration plan + audit trail —
                                        intentional history
  scripts/bootstrap-mcp.sh and other    referenced by gemini-extension.json;
    Python-era shell scripts            removal blocked on Gemini support
                                        decision (separate PR)

Not in scope
------------

  .github/workflows/pr-tests.yml + pr-comment.yml — Python pytest CI;
    handled by PR #125 (ci-drop-python).
  README.md, CLAUDE.md, SKILL.md, AGENT_INTEGRATION.md, GEMINI.md,
    CONTRIBUTING.md — top-level docs still describe the v0.3 install
    flow; rewrite scheduled separately so this commit stays focused
    on code deletion.

Verification
------------

  go build ./...   passes
  go vet ./...     passes
  go test ./...    full suite passes (no test referenced deleted paths)
  grep across remaining .{go,md,json,sh,toml,yml,yaml} for the deleted
    paths returned zero hits — no dangling references.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant