Skip to content

refactor(mcp): phase 5.3 — thread RepoProvider through server + tool handlers#30

Merged
ABB65 merged 1 commit into
next-mcpfrom
refactor/phase-5-3-provider-abstraction
Apr 17, 2026
Merged

refactor(mcp): phase 5.3 — thread RepoProvider through server + tool handlers#30
ABB65 merged 1 commit into
next-mcpfrom
refactor/phase-5-3-provider-abstraction

Conversation

@ABB65

@ABB65 ABB65 commented Apr 17, 2026

Copy link
Copy Markdown
Member

Summary

Completes the provider-abstraction plumbing started in phase 5.1. `createServer` accepts a provider option alongside `projectRoot`; tool register functions take the provider as a first-class parameter; write-path handlers gate on `projectRoot` presence and return a uniform capability error when local disk is unavailable. Read-only static tools (`contentrain_describe_format`) now work against any `ToolProvider` — the foundation for phase 5.4's remote write path.

Behaviour for the stdio flow (and for HTTP + LocalProvider) is bit-for-bit unchanged; every existing test continues to pass.

What changed

Read helpers (`core/`)

  • `readConfig` / `readVocabulary` (`core/config.ts`) and `readModel` / `listModels` (`core/model-manager.ts`) grow a `RepoReader`-based overload alongside the legacy `projectRoot` signature. Backward-compatible — every existing caller keeps working; future GitHubProvider reads go through the same helpers.

Server plumbing (`server.ts`)

  • New `ToolProvider` type alias (`RepoReader & { capabilities }`) — narrower than the full `RepoProvider` so `LocalProvider` (reader + applyPlan today) and `GitHubProvider` (full provider) both satisfy it without requiring `LocalProvider` to stub branch-ops yet.
  • `createServer({ provider, projectRoot? })` overload. The string argument continues to work via a backward-compat path that wraps `projectRoot` in a `LocalProvider`.
  • Every `registerXxxTools` call receives `(server, provider, projectRoot)`.

Tool handlers (`tools/`)

  • New `tools/guards.ts` — shared `capabilityError` helper emits a uniform JSON response with `capability_required`, a hint, and `isError: true` so agents can decide whether to retry against another transport.
  • `content`, `model`, `context`, `workflow`, `normalize`, `setup`, `bulk` — signatures updated to accept `ToolProvider` + optional `projectRoot`. Each handler adds a one-line gate: `if (!projectRoot) return capabilityError(...)`. `contentrain_describe_format` is the intentional exception — it's static and works against any provider.
  • Normalize surfaces the right capability key: `contentrain_scan` → `astScan`, `contentrain_apply` → `sourceRead` (extract) or `sourceWrite` (reuse). Everything else → `localWorktree`.

HTTP transport (`server/http/`)

  • `startHttpMcpServerWith({ provider, projectRoot? })` — new entry point that accepts a pre-built provider. Internally shares the same `StreamableHTTPServerTransport` wiring as the legacy `startHttpMcpServer({ projectRoot })` path via a single `startHttpMcpServerInternal` helper.

Tests

  • `tests/server/http.test.ts` adds two phase-5.3 cases:
    • `describe_format` over HTTP with a provider-only config (no `projectRoot`) returns the format spec.
    • `contentrain_status` over HTTP with a provider-only config returns `capability_required: localWorktree` with `isError: true`.

Test plan

  • `pnpm vitest run conformance serialization-parity core/validator providers/github server/http` → 65/65 green (including the two new Phase 5.3 HTTP cases).
  • `pnpm vitest run tools/content.test tools/model.test tools/workflow.test` → 40/40 green (~610s). The stdio path (17 content + 8 model + 15 workflow) is bit-for-bit unchanged after the provider threading.
  • `pnpm typecheck` → all 6 packages clean.
  • `oxlint` on `packages/mcp/src/` → 0 warnings.
  • Public MCP tool response JSON shape unchanged.

What phase 5.3 does NOT ship (staged for phase 5.4)

  • Write path via `GitHubProvider`. `LocalProvider.applyPlan` still runs the full worktree + workflow flow; `GitHubProvider.applyPlan` is available but not yet routed from tool handlers. Phase 5.4 picks off `content_save` / `content_delete` / `model_save` / `model_delete` once the workflow semantics are adapted (PR fallback, no selective sync, base branch resolution).
  • `validateProject` reader overload. Project-wide validation still reads via `projectRoot`, so post-save validation runs only on `LocalProvider`. A reader-backed `validateProject` lands alongside the write path in phase 5.4.

Refs

  • `.internal/refactor/01-mcp-engine-plan.md` phase 5
  • `.internal/refactor/00-principles.md` §5.3 capability system
  • `.internal/refactor/02-studio-handoff.md` phase S6 (MCP Cloud endpoint)

🤖 Generated with Claude Code

…handlers

Completes the provider-abstraction plumbing. createServer now accepts a
provider option alongside projectRoot; tool register functions take the
provider as a first-class parameter; write-path handlers gate on
projectRoot presence and return a uniform capability error when local
disk access is unavailable. Read-only static tools (describe_format)
now work against any ToolProvider — the foundation for phase 5.4's
remote write path.

Changes:

Read helpers (core/)
- readConfig / readVocabulary (core/config.ts) and readModel / listModels
  (core/model-manager.ts) add RepoReader-based overloads alongside the
  legacy projectRoot signature. Backward-compatible: every existing
  caller keeps working; GitHubProvider reads go through the same
  helpers via reader-based overload.

Server plumbing (server.ts)
- ToolProvider type alias (RepoReader + capabilities) — narrower than
  the full RepoProvider so LocalProvider (reader + applyPlan today) and
  GitHubProvider (full provider) both satisfy it without requiring
  LocalProvider to stub branch-ops yet.
- createServer({ provider, projectRoot? }) overload. String argument
  continues to work via a backward-compat path that wraps projectRoot
  in a LocalProvider. When only a provider is supplied and the provider
  is a LocalProvider, projectRoot is derived from it; otherwise stays
  undefined.
- Every registerXxxTools call receives (server, provider, projectRoot).

Tool handlers (tools/)
- guards.ts: new capabilityError helper emits a uniform JSON response
  with capability_required, a hint, and isError:true so agents can
  decide whether to retry against another transport.
- content, model, context, workflow, normalize, setup, bulk — signature
  updated to accept ToolProvider + optional projectRoot. Each handler
  adds a one-line gate at the top: if projectRoot is undefined, return
  capabilityError. contentrain_describe_format is the exception — it
  is static data and therefore works against any provider.
- Normalize surfaces the right capability key: contentrain_scan →
  astScan, contentrain_apply → sourceRead (extract) or sourceWrite
  (reuse). contentrain_bulk, contentrain_content_*, model_*, setup,
  workflow → localWorktree.

HTTP transport (server/http/)
- startHttpMcpServerWith({ provider, projectRoot? }) — new variant that
  accepts a pre-built provider. Internally shares the same
  StreamableHTTPServerTransport wiring as the legacy
  startHttpMcpServer({ projectRoot }) path. Node http server,
  auth-token guard, mount-path enforcement identical to phase 5.2.
- Both entry points now go through a single startHttpMcpServerInternal
  helper so the Bearer / mount / listen / close semantics stay in one
  place.

Tests
- tests/server/http.test.ts adds two Phase 5.3 cases:
  * describe_format over HTTP with a provider-only config (no
    projectRoot) returns the format spec.
  * contentrain_status over HTTP with a provider-only config returns
    capability_required: localWorktree with isError:true.
- Existing 4 HTTP tests, 25 conformance / parity / validator / github
  tests, and 40 tool tests (content 17, model 8, workflow 15) all stay
  green — the stdio path is bit-for-bit unchanged.

Verification
- pnpm vitest run conformance serialization-parity core/validator
  providers/github server/http → 65/65 (plus 2 new Phase 5.3 cases in
  server/http = 6/6 there).
- pnpm vitest run tools/content.test tools/model.test tools/workflow.test
  → 40/40 (~610s).
- pnpm typecheck → all 6 packages clean.
- oxlint on mcp src/ → 0 warnings.

What phase 5.3 does NOT ship (staged for phase 5.4)
- Write path via GitHubProvider. LocalProvider.applyPlan still runs the
  full worktree + workflow flow; GitHubProvider.applyPlan is available
  but not yet routed from tool handlers. Phase 5.4 picks off
  content_save / content_delete / model_save / model_delete and runs
  them against a GitHubProvider once the workflow semantics are
  adapted (PR fallback, no selective sync, base branch resolution).
- validateProject reader overload. Project-wide validation still reads
  via projectRoot, so post-save validation runs only on LocalProvider.
  A reader-backed validateProject lands alongside the write path in
  phase 5.4.

Refs:
- .internal/refactor/01-mcp-engine-plan.md phase 5
- .internal/refactor/00-principles.md §5.3 capability system
- .internal/refactor/02-studio-handoff.md phase S6 (MCP Cloud endpoint)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@ABB65 ABB65 merged commit ab283f5 into next-mcp Apr 17, 2026
1 check passed
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