feat(mcp-setup): extend client detection to 6 clients + monorepo dev workflow + GUI-PATH fix#394
Conversation
| } | ||
| return { context: 'monorepo', monorepoRoot: root }; | ||
| } | ||
| const root = findRoot(); |
There was a problem hiding this comment.
🔴 Bug: findDkgMonorepoRoot() is called without a start directory, so it walks up from the installed @origintrail-official/dkg-core package location rather than from the user's current checkout. A globally-installed dkg run inside dkg-v9 will therefore stay in installed mode and never pick up the local build, which breaks the new contributor workflow. Please plumb process.cwd() (or another explicit invocation dir) into the lookup.
| ): Record<string, unknown> { | ||
| if (context === 'monorepo' && monorepoRoot) { | ||
| return { | ||
| command: 'node', |
There was a problem hiding this comment.
🔴 Bug: monorepo mode hard-codes command: "node", but this PR's own installed-mode change exists because GUI MCP clients often do not inherit the shell PATH. In that environment the generated monorepo entry will still fail with spawn node ENOENT. Use an absolute Node executable such as process.execPath when writing this form.
| if (context === 'monorepo' && monorepoRoot) { | ||
| return { | ||
| command: 'node', | ||
| args: [join(monorepoRoot, 'packages', 'cli', 'dist', 'cli.js'), 'mcp', 'serve'], |
There was a problem hiding this comment.
🔴 Bug: this always points clients at packages/cli/dist/cli.js without checking that the checkout has actually been built. On a fresh or cleaned monorepo checkout, dkg mcp setup will overwrite a working installed registration with a path every client fails to spawn. Validate the built CLI exists before selecting monorepo mode, and fail with a rebuild hint or fall back to installed mode.
| .option('--force', 'Refresh every detected client regardless of current registration state') | ||
| .option('--print-only', 'Print the canonical JSON to stdout; skip every other step') | ||
| .option('--yes', 'Auto-confirm all registrations (default; reserved for future interactive prompts)') | ||
| .option('--installed', 'Force the installed-mode command form ({ command: "dkg", args: ["mcp", "serve"] }) even when invoked from inside a monorepo dev checkout. Mutually exclusive with --monorepo.') |
There was a problem hiding this comment.
🟡 Issue: this help text no longer matches the implementation. In installed mode mcp-setup.ts now prefers resolveDkgBin() and will usually emit an absolute path, not the literal { command: "dkg", ... } shape promised here. Please update the flag description (and matching docs) so operators know what config they will actually get.
…ath dispatch (phase 1 of follow-up)
Phase 1 of the follow-up plan at
`C:\Users\jurij\.claude\plans\ethereal-dazzling-scott.md` (§Part 3).
Architecture refactor only — no behaviour change for the existing
two clients (Cursor + Claude Code). Subsequent phases extend
`detectClients()` with the 6 new clients (Claude Desktop, VSCode +
Copilot, Windsurf, Continue, Cline, Codex CLI) and the monorepo
context detection — all of which need this generalised dispatch
to land safely.
This commit:
- `ClientTarget` interface gains two optional fields:
- `format?: 'json' | 'toml' | 'yaml'` — defaults to 'json'
- `entryPath?: string` — defaults to 'mcpServers.dkg'
Existing entries don't need to declare either; their behaviour is
byte-identical post-refactor.
- New helpers:
- `splitEntryPath` — split a dotted path ('mcpServers.dkg' /
'servers.dkg' / 'mcp_servers.dkg') into head segments + leaf.
- `ensurePathContainer` — write-side traversal that lazy-creates
intermediate `Record` containers.
- `readEntryAt` — read-side traversal that returns `undefined` for
any missing intermediate.
- `readConfigBody` / `writeConfigBody` — per-format dispatch shape
(JSON wired today; TOML / YAML branches throw clean
"land phase 4/5 first" errors so a target declaring those
formats trips fast at registration time, not at runtime).
- `classify(target)` and `writeRegistration(target)` refactored to
use the helpers. JSON output preserves the pre-refactor formatting
(2-space indent, trailing newline) byte-for-byte.
- `mcp-setup.test.ts:324` — pre-existing post-F14+F26 test
(`faucet failure logs manual instructions`) now stubs `fetch` so
the daemon-reachability probe succeeds and the throwing-faucet
mock is actually reached. Without this stub the funding step
short-circuited on the unreachable-path log line and the test
asserted on a code path that never ran. Confirmed pre-existing
by running `vitest mcp-setup.test.ts` against `fc2b375d` (the
branch base, pre-phase-1) — same single failure.
Verification:
- `pnpm --filter @origintrail-official/dkg-mcp test` → 88/88 ✓
- `pnpm --filter @origintrail-official/dkg exec vitest run mcp-setup.test.ts integrations.test.ts` → 54/54 ✓
- `pnpm --filter @origintrail-official/dkg build` → clean
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…epo flags (phase 2 of follow-up)
Phase 2 of the follow-up plan
(`C:\Users\jurij\.claude\plans\ethereal-dazzling-scott.md` §Part 1).
Adds setup-context detection so contributors running `dkg mcp setup`
from inside a dkg-v9 dev checkout get an absolute-path command form
that targets THEIR local CLI dist, not a stale globally-installed
`dkg`. npm-installed CLIs continue to write the standard
`{ command: "dkg", args: ["mcp", "serve"] }` shape.
Reuses `findDkgMonorepoRoot()` from `@origintrail-official/dkg-core`
byte-for-byte — same primitive `resolveDkgConfigHome()` already
uses to switch `~/.dkg` ↔ `~/.dkg-dev`. Injectable via the deps
surface so tests can stub without touching the real filesystem.
This commit:
- `packages/cli/src/mcp-setup.ts`:
- New `SetupContext` type ('installed' | 'monorepo') + new
`detectContext(findRoot, { force? })` helper.
- `canonicalEntry(context, monorepoRoot)` is now context-aware:
monorepo + a real root produces
`{ command: "node", args: ["<repo>/packages/cli/dist/cli.js",
"mcp", "serve"] }`; installed (or monorepo with no root, which
can only happen on the force path and surfaces a clear error)
produces the standard `{ command: "dkg", args: ["mcp", "serve"] }`.
- `classify(target, expectedEntry)` and
`writeRegistration(target, entry)` now take the context-aware
expected entry as a parameter so staleness comparisons compare
apples-to-apples (an installed-form entry in a monorepo
invocation classifies as `stale`, gets refreshed under
`--force` to the local-bin form).
- `mcpSetupAction` validates the `--installed` / `--monorepo`
mutual-exclusion at the boundary and threads the resolved
context through every downstream call site.
- `McpSetupActionDeps` gains `findDkgMonorepoRoot` for testability.
- `packages/cli/src/cli.ts`:
- Two new commander flags on `dkg mcp setup`:
`--installed` (force installed-mode form even from inside a
monorepo cwd; escape hatch for testing the published-CLI shape)
and `--monorepo` (force monorepo-mode form; errors if no
monorepo root locatable). Mutually exclusive.
- `findDkgMonorepoRoot` now passed into `mcpSetupAction`'s deps.
- `packages/cli/test/mcp-setup.test.ts`:
- `makeDeps()` extended with a stub `findDkgMonorepoRoot` that
defaults to returning `null` (installed-mode); tests that
exercise the monorepo path override.
- 6 new phase-2 tests:
* monorepo auto-detect → local-cli-dist absolute-path form
* no monorepo detected → standard `dkg` installed form
* `--installed` from inside a monorepo forces standard form
* `--monorepo` from outside any monorepo throws the canonical error
* `--installed` + `--monorepo` together throws mutual-exclusion error
* Pre-existing installed-form entry classifies as `stale` when
run in monorepo mode, gets rewritten to monorepo form
- New `parseStdoutJson` helper extracts the JSON object emitted
by `--print-only` from the spied writes (vitest's progress
reporter can interleave non-JSON writes; first-`{` to last-`}`
is the tight bracket).
Verification:
- `pnpm --filter @origintrail-official/dkg build` → clean
- `pnpm --filter @origintrail-official/dkg-mcp test` → 88/88 ✓
- `pnpm --filter @origintrail-official/dkg exec vitest run mcp-setup.test.ts integrations.test.ts` → 60/60 ✓ (was 54; +6 phase-2)
- `dkg mcp setup --help` shows both new flags
- Live smoke from inside this monorepo: `node packages/cli/dist/cli.js mcp setup --print-only`
emits the local-cli-dist `node /abs/path/cli.js mcp serve` form. Confirmed
monorepo auto-detect works end-to-end.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…llow-up)
Phase 3 of the follow-up plan
(`C:\Users\jurij\.claude\plans\ethereal-dazzling-scott.md` §Part 2,
"Easy clients" tier). Both new clients use the canonical
`mcpServers.dkg` JSON shape, so only `detectClients()` needs to
extend — the phase-1 dispatch handles writes uniformly.
This commit:
- `packages/cli/src/mcp-setup.ts`:
- New `claudeDesktopPaths(home)` resolver. macOS:
`~/Library/Application Support/Claude/claude_desktop_config.json`;
Windows: `%APPDATA%\Claude\claude_desktop_config.json` (with a
`~/AppData/Roaming` fallback when APPDATA isn't set); Linux +
other: `~/.config/Claude/claude_desktop_config.json`. Per-platform
`displayPath` tildifies the home prefix so the operator log
reads consistently across platforms.
- `detectClients()` extended with two new candidates: Claude
Desktop (per-platform via the resolver above) and Windsurf
(`~/.codeium/windsurf/mcp_config.json`). Detection-permissive
heuristic unchanged: parent-dir existence is sufficient.
- `packages/cli/test/mcp-setup.test.ts`:
- `beforeEach` / `afterEach` extended with `process.env.APPDATA`
override so the Claude Desktop Win32 path resolves inside the
`tmpHome` sandbox. macOS + Linux ignore APPDATA so this is a
no-op there.
- 4 new phase-3 tests:
* Claude Desktop detected when its config dir exists; canonical
entry written at the per-platform path
* Windsurf detected at `~/.codeium/windsurf/`; canonical entry
written
* Clients without their config dir are not detected — test
pre-creates only `.cursor/` and asserts Claude Desktop +
Windsurf paths stay absent
* Pre-existing Claude Desktop entry on a sibling key is
preserved on merge (real-world shape: a user has other MCP
servers; setup must not clobber)
Verification:
- `pnpm --filter @origintrail-official/dkg build` → clean
- `pnpm --filter @origintrail-official/dkg-mcp test` → 88/88 ✓
- `pnpm --filter @origintrail-official/dkg exec vitest run mcp-setup.test.ts integrations.test.ts` → 64/64 ✓ (was 60; +4 phase-3)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…-up)
Phase 4 of the follow-up plan
(`C:\Users\jurij\.claude\plans\ethereal-dazzling-scott.md` §Part 2,
"Medium clients" tier). Ships VSCode + Copilot Chat in this commit.
**Continue deferred to a follow-up PR per the plan's defer rule.**
The plan calls out Continue's `~/.continue/config.yaml` (newer) /
`config.json` (legacy) detection plus uncertainty about the
in-YAML MCP entry shape ("if shape diverges, add a per-client
adapter"). Two open questions there:
1. The exact in-YAML shape Continue accepts isn't pinned by
the standard MCP spec and would need verification against
a live Continue install.
2. We don't yet have a YAML serializer wired (phase 1's
`writeConfigBody` throws "phase 4 first" for `format:
'yaml'`). Adding `js-yaml` is on-pattern but pulls in a
test-shape question (YAML round-trip equivalence) that's
out of scope for the "medium clients" tier as the plan
framed it.
The architecture from phase 1 makes Continue cheap to add
incrementally once both questions are resolved.
VSCode + Copilot Chat is the load-bearing phase-4 add: it's the
first non-`mcpServers.dkg`-shape client (entries key under
`servers.dkg`), so it actually exercises the phase-1 entryPath
dispatch end-to-end.
This commit:
- `packages/cli/src/mcp-setup.ts`:
- New `vscodeMcpPaths(home)` resolver. macOS:
`~/Library/Application Support/Code/User/mcp.json`; Windows:
`%APPDATA%\Code\User\mcp.json`; Linux:
`~/.config/Code/User/mcp.json`. User-scoped (cross-workspace),
not the per-workspace `.vscode/mcp.json`.
- `detectClients()` extended with the VSCode candidate. Uses
`entryPath: 'servers.dkg'` instead of the canonical default —
Copilot Chat keys under `servers`, not `mcpServers`. Phase-1
entryPath dispatch handles this without per-client write logic.
- `packages/cli/test/mcp-setup.test.ts`:
- 3 new phase-4 tests:
* VSCode detected; canonical entry written under `servers.dkg`,
not `mcpServers.dkg` — pins the alternate-shape contract.
* Pre-existing VSCode `servers.<other>` siblings preserved on
merge (real-world shape: a user has other MCPs already wired).
* VSCode staleness across context flip — pre-existing
installed-form `servers.dkg` reclassifies as `stale` when run
in monorepo mode, gets rewritten to the local-cli-dist form.
Pins that the cross-shape staleness comparison works.
Verification:
- `pnpm --filter @origintrail-official/dkg build` → clean
- `pnpm --filter @origintrail-official/dkg-mcp test` → 88/88 ✓
- `pnpm --filter @origintrail-official/dkg exec vitest run mcp-setup.test.ts` → 26/26 ✓ (was 23; +3 phase-4)
- `pnpm --filter @origintrail-official/dkg exec vitest run integrations.test.ts` → 41/41 ✓
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…f follow-up, partial)
Phase 5 of the follow-up plan
(`C:\Users\jurij\.claude\plans\ethereal-dazzling-scott.md` §Part 2,
"Hard clients" tier). Ships Cline; **defers Codex CLI to a
follow-up PR per the plan's defer rule.**
Codex CLI deferred because:
1. Adding `@iarna/toml` (or any TOML serializer) is a workspace-
level dependency change that warrants its own commit/PR for
review focus on the dependency choice + transitive surface.
2. TOML round-trip equivalence testing (parse → write → parse
→ assert structural equality) is a meaningfully different
test shape than the JSON-roundtrip pattern this PR's other
fixtures use.
3. The `format: 'toml'` dispatch branch in phase-1's
`readConfigBody` / `writeConfigBody` already throws a clean
"land phase 5 first" error if a `format: 'toml'` candidate
ever gets added pre-implementation, so deferring won't
silently misbehave.
The architecture from phase 1 makes Codex CLI a pure additive
change once the TOML dep lands.
Cline ships now: it's the canonical `mcpServers.dkg` JSON shape,
deeply nested under VSCode's per-extension globalStorage path
(`<userDataDir>/User/globalStorage/saoudrizwan.claude-dev/settings/cline_mcp_settings.json`).
The phase-1 dispatch already handles JSON; the new work is a
per-platform path resolver that mirrors `vscodeMcpPaths` for
the Code-user-data root, with the per-extension suffix appended.
This commit:
- `packages/cli/src/mcp-setup.ts`:
- New `clineMcpPaths(home)` resolver. Same Mac / Win32 / Linux
triad as Claude Desktop / VSCode but with the deeply-nested
`globalStorage/saoudrizwan.claude-dev/settings/cline_mcp_settings.json`
suffix appended.
- `detectClients()` extended with the Cline candidate. Uses an
IIFE to bind the resolver result without polluting the outer
scope (single-use; would otherwise force three locals for one
candidate). Default `entryPath` (`mcpServers.dkg`) wins, no
override needed.
- `packages/cli/test/mcp-setup.test.ts`:
- 2 new phase-5 tests:
* Cline detected at the per-platform globalStorage path;
canonical entry written under `mcpServers.dkg`.
* Cline siblings preserved — pre-existing `mcpServers.<other>`
entries (e.g. a `github` MCP) survive the merge unchanged.
Verification:
- `pnpm --filter @origintrail-official/dkg build` → clean
- `pnpm --filter @origintrail-official/dkg-mcp test` → 88/88 ✓
- `pnpm --filter @origintrail-official/dkg exec vitest run mcp-setup.test.ts` → 28/28 ✓ (was 26; +2 phase-5)
- Live smoke: `node packages/cli/dist/cli.js mcp setup --print-only --installed`
emits the standard `dkg` form even from inside this monorepo.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…hase 6)
Phase 6 of the follow-up plan
(`C:\Users\jurij\.claude\plans\ethereal-dazzling-scott.md` §Part 6).
Code-comment / JSDoc updates only — docs-lead handles the repo-root
README + mcp-dkg README in their parallel cycle.
The pre-phase-6 module header at `mcp-setup.ts:1-39` was written
when only Cursor + Claude Code were detected and the canonical
entry was hardcoded `{ command: "dkg", args: ["mcp", "serve"] }`.
Phases 1-5 added six more clients (one deferred), per-client
format / entryPath dispatch, monorepo context detection, and two
new CLI flags. The header now describes that surface end-to-end:
- Step 4 of the bundled flow now enumerates all 6 detected
clients (Cursor, Claude Code, Claude Desktop, Windsurf,
VSCode + Copilot Chat, Cline) plus the deferred ones
(Continue, Codex CLI) with a pointer to the phase-4 / phase-5
commit bodies for the defer rationale.
- New "Context-awareness (phase 2)" paragraph documents the
installed-vs-monorepo command form selection + the
`--installed` / `--monorepo` overrides.
- New "Per-client format / entry-shape dispatch (phase 1)"
paragraph explains why VSCode keys under `servers.dkg` while
the rest use canonical `mcpServers.dkg`, and how phase-1's
ClientTarget fields handle that without per-client write logic.
- Flags block extended with `--installed` + `--monorepo`.
JSDoc-only; zero runtime behaviour change.
Verification:
- `pnpm --filter @origintrail-official/dkg build` → clean
- `pnpm --filter @origintrail-official/dkg exec vitest run mcp-setup.test.ts` → 28/28 ✓
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…rt 6) mcp-lead's `feat/mcp-setup-extend-clients-monorepo` phases 1-6 ship detection for Cursor, Claude Code, Claude Desktop, Windsurf, VSCode + GitHub Copilot Chat, and Cline. Continue and Codex CLI are deferred to a follow-up (Continue's YAML shape needs a live install verify; Codex CLI's TOML format requires a workspace dep). README references to "Cursor, Claude Code, Continue, or Cline" were stale before phase 1 and triply wrong after — updated all five sites: - Quick Start routing table (line 70) - MCP section opener (line 81) - Bundled flow step 4 (line 93) — full per-platform path list with the VSCode `servers.dkg` shape carve-out - Round-trip step 4 (line 121) - Troubleshooting "no clients detected" (line 132) — Continue + Codex CLI carved out as `--print-only` paste-manually candidates - CLI cheat-sheet (line 306) - Setup-guides table (line 347) New "Contributor (monorepo dev) workflow" subsection under MCP Setup. Covers `findDkgMonorepoRoot()` auto-detect, the local `node /abs/path/cli.js mcp serve` written form, the rebuild prereq (`pnpm --filter @origintrail-official/dkg build`), and the two flag overrides (`--installed` to dogfood the published CLI from a monorepo cwd; `--monorepo` to fail loudly if the workspace lookup goes sideways). Includes the moved-checkout caveat: absolute paths mean `dkg mcp setup --force` is required after a checkout move. Source-of-truth check: `detectClients()` array at `packages/cli/src/mcp-setup.ts:404-446` — 6 clients verified verbatim. The plan's table was best-knowledge; the COMMITTED list is what these edits reflect. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…Part 6) Parallel to `2298d22f` in the repo-root README. Updates the `packages/mcp-dkg/README.md` Install + Manual config + Troubleshooting sections to reflect the actual `detectClients()` array landed by mcp-lead's phases 1-6 on `feat/mcp-setup-extend-clients-monorepo`. - Opening sentence (line 3) — bolded client list expanded to the 6 auto-detected clients. - Install bundled-flow step 4 — full per-platform path list, with the VSCode + Copilot `servers.dkg` shape carve-out. - Manual config block (was 3 generic bullets, now 7 explicit per-client entries) — each carries config path + entry shape. - Continue + Codex CLI carved out as `--print-only` paste-manually candidates, since they're not auto-detected today. - Troubleshooting "no clients detected" bullet — same enumeration. - "Contributor (monorepo dev) workflow" subsection replaces the prior `pnpm exec tsx` workspace-relative form (which was a pre-W6-pre workaround). New form is the actual auto-detected `node /abs/path/ cli.js mcp serve` shape with rebuild prereq, `--installed` / `--monorepo` mode overrides, and the moved-checkout caveat. Source-of-truth: `detectClients()` array at `packages/cli/src/mcp-setup.ts:404-446`. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…t PATH inheritance (F30)
User real-world signal: a Claude Desktop user got
`MCP dkg: spawn dkg ENOENT` after running `dkg mcp setup`. Root
cause: GUI MCP clients (Claude Desktop, Windsurf, etc.) don't
inherit the shell PATH that `npm install -g` writes the `dkg` bin
into. The bare-`"dkg"` command in the registered config can only
be spawned by clients that DO inherit shell PATH (Cursor, Claude
Code CLI, terminal-launched VSCode + Copilot Chat). For everyone
else, the entry is dead on arrival.
Fix: in installed mode, resolve the absolute path of the `dkg`
bin at setup time via `which dkg` (POSIX) / `where.exe dkg`
(Windows) and write THAT into the client config. GUI clients can
spawn an absolute path without PATH inheritance. Monorepo mode
unchanged — already absolute (`node <repo>/packages/cli/dist/cli.js`).
This commit:
- `packages/cli/src/mcp-setup.ts`:
- New `resolveDkgBin()` helper. Uses `execSync` + platform-
specific `which`/`where.exe`, returns the first line of output
(matches shell PATH-precedence on Windows, where `where.exe`
can return multiple shadowed hits). Returns `null` on any
failure (`dkg` not on PATH, exec error, empty output) so
callers fall back to the bare-`"dkg"` form without crashing.
`stdio: ['ignore', 'pipe', 'ignore']` silences `which`'s
not-found stderr so the operator-facing setup log stays clean.
Exported for the cli.ts wiring.
- `canonicalEntry(context, monorepoRoot, resolvedBin)` extended
with the new third parameter. Installed mode prefers the
resolved-path form when available; falls back to bare-`"dkg"`
when the resolver returns null. Monorepo mode unchanged.
- `mcpSetupAction` calls `deps.resolveDkgBin()` once at the top,
only in installed mode (monorepo mode hard-codes its own
absolute path; no need to spawn a child process).
- `McpSetupActionDeps` extended with `resolveDkgBin: () => string | null`
so tests can inject deterministic stubs.
- `classify()` staleness contract updated: bare-`"dkg"` and the
currently-resolved absolute path are equivalent for the
currently-installed bin (both spawn the same process). A
pre-existing bare-`"dkg"` entry classifies as `registered`
against a resolved-path canonical when args match — avoids
spurious `--force` prompts on re-runs after PATH-state
changes or after upgrading from pre-F30 setup. Asymmetric:
a pre-existing DIFFERENT absolute path (e.g. `/old/path/dkg`
vs current `/usr/local/bin/dkg`) IS real divergence and
classifies as `stale`.
- `packages/cli/src/cli.ts`:
- Pass `resolveDkgBin` through to `mcpSetupAction`'s deps.
- `packages/cli/test/mcp-setup.test.ts`:
- `makeDeps()` extended with a `resolveDkgBin` stub that
defaults to returning null (preserves pre-F30 test behaviour
for the existing 28 tests).
- 6 new F30 tests:
* Installed mode + resolved bin → canonical entry uses
absolute path; `resolveDkgBin` called exactly once
* `resolveDkgBin` → null falls back to bare `"dkg"`
* Monorepo mode does NOT call `resolveDkgBin` (no spurious
child-process spawn during dev setup)
* Pre-existing bare-`"dkg"` entry stays `registered` against
a resolved-path canonical (no rewrite, mtime unchanged) —
pins the re-run resilience contract
* Pre-existing different absolute path classifies as `stale`
and gets refreshed — pins the asymmetric divergence contract
* `--print-only` with resolved bin emits the absolute path
so the manual-paste form matches what setup actually writes
Verification:
- `pnpm --filter @origintrail-official/dkg build` → clean
- `pnpm --filter @origintrail-official/dkg-mcp test` → 88/88 ✓
- `pnpm --filter @origintrail-official/dkg exec vitest run mcp-setup.test.ts` → 34/34 ✓ (was 28; +6 F30)
Live smoke (qa-engineer's verification asks):
- `dkg mcp setup --print-only` from inside this monorepo → emits
`node /abs/path/cli.js mcp serve` (monorepo form, unchanged)
- `dkg mcp setup --print-only --installed` from same cwd →
emits the resolved absolute `dkg` bin path with `["mcp", "serve"]`,
not the bare-`"dkg"` form.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
fc2b375 to
1190795
Compare
f22b387 to
14d2992
Compare
Summary
dkg mcp setup— Claude Desktop (per-platform: macOS~/Library/Application Support/Claude/..., Windows%APPDATA%\Claude\..., Linux~/.config/Claude/...), Windsurf (~/.codeium/windsurf/mcp_config.json), VSCode + GitHub Copilot Chat (per-platform Code user-settings dir +mcp.json; usesservers.dkgshape), and Cline (deep-nested under VSCode's per-extension globalStorage). The auto-detect set goes from 2 (Cursor + Claude Code) to 6 clients. Continue (YAML) and Codex CLI (TOML) are deferred to a follow-up — phase-1's architecture supports both formats but their config shapes need a live-install verify pass.dkg mcp setupruns from inside adkg-v9checkout, it auto-detects the workspace viafindDkgMonorepoRoot()and writes a different entry that points at the local build ({ command: "node", args: ["<repo>/packages/cli/dist/cli.js", "mcp", "serve"] }) instead of the globally-installeddkg. Two mutually-exclusive overrides —--installed(force the published-CLI form even from a monorepo cwd, for dogfooding) and--monorepo(force the local-build form, errors if no monorepo root is locatable).MCP dkg: spawn dkg ENOENTfor GUI MCP clients (F30). Claude Desktop and other GUI-spawned MCP clients don't inherit the shellPATHwherenpm install -gwrites thedkgbin, so the bare-"dkg"form crashed at spawn time.dkg mcp setupnow resolves the absolute bin path viawhich dkg(POSIX) /where.exe dkg(Windows) at registration time and writes that absolute path into the client config — bypassingPATHinheritance entirely. Best-effort: anullresolution falls back silently to the bare-"dkg"form, so setup never crashes on awhich-missing or non-globally-installed CLI.Related
chore/v10-mcp-consolidation— V10 MCP consolidation) — this PR is branched offchore/v10-mcp-consolidationand inherits its keeper, the bundleddkg mcp setupflow, and the 21-tool surface. PR base ischore/v10-mcp-consolidationuntil feat(mcp): V10 MCP consolidation — single keeper, 21-tool surface, 2-command setup #381 merges tomain, after which this branch rebases ontomain.agent-docs/dkg-v10-mcp-consolidation/ethereal-dazzling-scott.md— the user's plan file driving this scope (extend client detection + monorepo dev workflow + the F30 spawn-ENOENT fix).MCP dkg: spawn dkg ENOENTin Claude Desktop after runningdkg mcp setupagainst the canonical bare-"dkg"block. The same failure mode would surface in any GUI-spawned MCP client; the absolute-bin-path resolution closes it across the board.Files changed (by area)
The 9-commit history walks phases 1 → 7. The groupings below regroup by logical scope so reviewers can read the changes without committing to chronological order.
Phase 1 —
ClientTargetarchitecture refactorCommit
62a8bcab(refactor(cli): generalise mcp-setup ClientTarget with format + entryPath dispatch).packages/cli/src/mcp-setup.ts—ClientTargetinterface gains two optional fields:format?: 'json' | 'toml' | 'yaml'(defaults to'json') — per-client config-file format. Phase 1 only wires'json'; the'toml'and'yaml'branches are guards that throw a clean error so phase-1 commits don't silently pass for clients that need them. Codex CLI's TOML format and Continue's YAML format land via this hook in deferred follow-ups.entryPath?: string(defaults to'mcpServers.dkg') — dot-path to the entry under the config root. Lets VSCode + Copilot'sservers.dkgshape register without per-client write logic.readConfigBody/writeConfigBodydispatch helpers added so the per-format branching is one site instead of being repeated acrossclassify/register/refreshpaths.Phase 2 — Monorepo context detection + flag overrides
Commit
508fced7(feat(cli): mcp-setup monorepo context detection + --installed/--monorepo flags).packages/cli/src/mcp-setup.ts—detectContext()walks ancestors from the CLI's compiled location viafindDkgMonorepoRoot()(re-exported from@origintrail-official/dkg-core). A hit returns{ context: 'monorepo', monorepoRoot }; a miss returns{ context: 'installed', monorepoRoot: null }.canonicalEntry(context, monorepoRoot, resolvedBin)takes the context-tagged result and emits the right shape:installed:{ command: "<resolved-bin-or-dkg>", args: ["mcp", "serve"] }monorepo:{ command: "node", args: ["<root>/packages/cli/dist/cli.js", "mcp", "serve"] }--installed/--monorepoflags wired incli.tsand validated inmcpSetupActionas mutually exclusive (commander'soption()doesn't enforce exclusivity, so we throw at runtime if both are passed).--monorepoerrors iffindDkgMonorepoRoot()returnsnull— the user explicitly asked for monorepo mode, so we fail loudly rather than silently fall back.Phase 3 — Claude Desktop + Windsurf
Commit
6cd3a1e5(feat(cli): mcp-setup detects Claude Desktop + Windsurf).packages/cli/src/mcp-setup.ts—claudeDesktopPaths(home)returns the per-platform path: macOS~/Library/Application Support/Claude/claude_desktop_config.json, Windows%APPDATA%\Claude\claude_desktop_config.json, Linux~/.config/Claude/claude_desktop_config.json. Both clients use the canonicalmcpServers.dkgshape, so noentryPathoverride.Phase 4 — VSCode + GitHub Copilot Chat
Commit
17944d86(feat(cli): mcp-setup detects VSCode + Copilot Chat).packages/cli/src/mcp-setup.ts—vscodeMcpPaths(home)returns the per-platform Code user-settings dir +mcp.json.entryPath: 'servers.dkg'override because Copilot Chat keys MCP servers underservers, not the canonicalmcpServers. The phase-1 dispatch helpers route the write to the right key without per-client logic.mcpServers.dkgentry path. Reviewers comparing JSON shapes across clients will spot the asymmetry; it's intentional and documented inline.Phase 5 — Cline
Commit
3afba8a7(feat(cli): mcp-setup detects Cline at VSCode globalStorage).packages/cli/src/mcp-setup.ts—clineMcpPaths(home)returns the per-platform Code user-settings dir + the Cline-extension-specific suffix:globalStorage/saoudrizwan.claude-dev/settings/cline_mcp_settings.json. Path mirrorsvscodeMcpPathsfor the Code-user-data root with the per-extension globalStorage append. Uses canonicalmcpServers.dkgshape (only the path is unusual; the entry shape is standard).Phase 6 — JSDoc refresh + READMEs
Commits
eb698b2c(docs(cli): refresh mcp-setup JSDoc header for post-phase-5 surface),2298d22f(docs(readme): expand client list to 6 + add monorepo dev workflow (Part 6)),dde551f7(docs(mcp-dkg): mirror client-list expansion + monorepo dev workflow (Part 6)).packages/cli/src/mcp-setup.ts— file-level JSDoc rewritten to enumerate the 6 detection targets, the monorepo-vs-installed dispatch, and the--installed/--monorepoflag semantics. Authoritative reference for any future contributor adding a 7th client.README.md(+38 / -7) — 7 stale sites updated (routing table, MCP section opener, bundled-flow step 4, round-trip step 4, troubleshooting "no clients detected", CLI cheat-sheet, Setup-guides table) plus a new "Contributor (monorepo dev) workflow" subsection covering the auto-detect, the rebuild prereq, the flag overrides, and the moved-checkout--forcecaveat.packages/mcp-dkg/README.md(+31 / -10) — opening sentence's bolded client list expanded; bundled-flow step 4 carries the full per-platform path list with the VSCode + Copilotservers.dkgshape carve-out; Manual config block expanded from 3 generic bullets to 7 explicit per-client entries; Troubleshooting bullet enumerates the actual 6; "Contributor (monorepo dev) workflow" subsection replaces the priorpnpm exec tsxworkspace-relative form (which was a pre-W6-pre workaround).Phase 7 — F30 absolute-bin-path resolution
Commit
f22b3877(fix(cli): resolve absolute dkg bin path on mcp setup to fix GUI-client PATH inheritance (F30)).packages/cli/src/mcp-setup.ts— exportedresolveDkgBin()shells out towhich dkg(POSIX) /where.exe dkg(Windows), takes the first match (Windows can return multiple hits when shadowed across PATH; first-wins matches shell behaviour), and returns the absolute path. Returnsnullon any failure (dkgnot on PATH, exec error, empty output) so callers fall back to the bare-"dkg"form silently.stdio: ['ignore', 'pipe', 'ignore']keepswhich's stderr out of the operator-facing setup log.canonicalEntry(context, monorepoRoot, resolvedBin)— third parameter; wheninstalledcontext andresolvedBinis non-null,commandis the absolute path. Bare-"dkg"falls back when resolution fails, so setup never crashes.mcpSetupAction— callsdeps.resolveDkgBin()only ininstalledcontext (monorepo mode hard-codes the local CLI dist absolute path; no resolution needed). Tests inject their own stub.cli.ts(+6 / -…) — wiresresolveDkgBinthroughmcpSetupAction's deps surface in production.Test fixtures (full coverage of the new surface)
Commit
62a8bcab(phase 1 baseline) plus per-phase additions across508fced7,6cd3a1e5,17944d86,3afba8a7,f22b3877.packages/cli/test/mcp-setup.test.ts(+552 / -1) — extends the branch-base 13-test surface (W6-pree78d8027baseline 9 + F14ab380b63+2 + F26 polish +2, settling at the PR feat(mcp): V10 MCP consolidation — single keeper, 21-tool surface, 2-command setup #381 branch-tipfc2b375dwith 13) to 34 tests atf22b3877(intermediate datapoint: 28 atdde551f7after phases 1–5 docs-mirror; +6 F30 cases land in phase 7). Coverage:ClientTargetformat+entryPathdispatch helpers (phase 1)--installedfrom monorepo,--monorepofrom non-monorepo (errors),--installed+--monorepotogether (rejects) (phase 2)which-success,which-not-found (falls back to baredkg), Windowswhere.exemulti-line output (phase 7)format: 'yaml'/format: 'toml'clean-throw guards (phase 1 — verify the deferred-client posture)Test plan
Co-authored by
qa-engineerfrom two verification passes against the branch — initial brief verification atdde551f7(post-Part-6 docs mirror) and F30-specific re-check atf22b3877(post-phase-7).Pre-merge gates — all green
pnpm run build→ 19/19 turbo tasks;pnpm --filter @origintrail-official/dkg build→ tsc + network/json copy steps clean. Phase 1'sClientTargetarchitecture refactor + phase 7's F30 changes don't break the umbrella CLI's tsc step.packages/cli/test/mcp-setup.test.ts34/34 passing atf22b3877(was 28 atdde551f7; was 11 at the W6-pre baselinee78d8027). Distribution: phase 1 dispatch + phase 2 monorepo detection + phase 3 Claude Desktop + Windsurf + phase 4 VSCode + Copilot Chat (incl.servers.dkgasymmetry) + phase 5 Cline globalStorage + phase 7 F30 bin resolution all covered. Test file diff: +552 lines net.dkg mcp setup --print-onlybyte-correct in monorepo mode. Run from inside the dkg-v9 checkout: output emits{ "command": "node", "args": ["C:\\Projects\\dkg-v9\\packages\\cli\\dist\\cli.js", "mcp", "serve"] }. Absolute path tocli.js(not globaldkg); confirmsfindDkgMonorepoRoot()correctly identifies the monorepo context.dkg mcp setup --print-only --installedbyte-correct in installed mode (F30 fix verified live on Windows). Output emits the resolved absolute Windows path:{ "command": "C:\\Users\\jurij\\AppData\\Local\\fnm_multishells\\38776_1777447566844\\dkg", "args": ["mcp", "serve"] }. Matcheswhich dkgoutput exactly. NOT the bare"dkg"string anymore — this is the F30 fix smoke. The fnm-shell-managed bin path resolves correctly on Windows under the npm-version-manager indirection layer (nvm-windows users would hit the same code path).--installedoverride smoke from monorepo cwd — same probe as above forces installed-mode entry from inside the monorepo checkout;--installedflag correctly overrides the auto-detected monorepo context, escape hatch works as designed.README.mdandpackages/mcp-dkg/README.mddocument Cursor, Claude Code, Claude Desktop, Windsurf, VSCode + Copilot Chat, Cline.mcp-setup.ts:406-437detectClients()source enumeration matches: same 6 names. Deferred clients (Continue YAML, Codex CLI TOML) explicitly called out in both READMEs as not-auto-detected with manual-paste fallback. No drift.servers.dkgvsmcpServers.dkgasymmetry covered.mcp-setup.test.ts:600-728has 3 phase-4 cases pinning the asymmetry: (a) detected + writes underservers.dkg, (b) sibling preservation underservers.<other>on merge, (c) staleness reclassification on installed→monorepo flip (servers.dkg.commandflips fromdkgtonode). Per-client serializer dispatch correctly handles the only client that doesn't use the canonicalmcpServers.dkgshape.F30 re-check verification (post-phase-7 at
f22b3877)Independent confirmation on Windows via the qa checkout, providing the second cross-platform datapoint after mcp-lead's local Windows smoke. Live results captured above in checkboxes #4 and #5; no regressions on the monorepo-auto-detect path (#3 unchanged across pre/post-F30).
Pre-existing failures noted (NOT introduced by this PR)
packages/adapter-openclaw2/810 R7 failures (test/setup.test.ts:344autoUpdate config-shape) — pre-existing onmain, unchanged from PR feat(mcp): V10 MCP consolidation — single keeper, 21-tool surface, 2-command setup #381. Out-of-scope for this PR; tracked as a separate post-merge concern per team-lead's 2026-05-04 sequencing call.Release-readiness gates explicitly deferred
npm install -g @origintrail-official/dkgagainst the production registry, thendkg mcp setupin Claude Desktop / Windsurf / VSCode / Cline / Cursor / Claude Code on macOS / Windows / Linux. Combinatorial; runs as part of the broader release cycle, not pre-merge here.mcp-setup.test.tsstubhomedir()andplatform()so per-platform path resolution is covered at the contract level; the real-OS smoke catches OS-version-specific edge cases.QA sign-off
qa-engineersigns off the Test-plan section based on both verification passes. Pre-merge gates green atf22b3877. Release-readiness gates deferred and documented; they do not block PR-open.Migration notes
For users with existing
dkgMCP entries written by an older / pre-F30 setup:"dkg"entries always stay registered. A pre-existing bare-"dkg"entry is treated as equivalent to whatever absolute path resolution produces (or to bare-"dkg"itself when resolution fails) — both invoke the currently-installed bin, so the classify logic atmcp-setup.ts:618-625makes them register-equivalent regardless of resolution outcome. Pre-F30 entries from older setup runs do NOT trigger a spurious refresh on a routinedkg mcp setupre-run.--forcere-run. Pre-F30 users with bare-"dkg"entries who want the GUI-friendly absolute-path form have two options: pass--forceto refresh all detected clients (writes the absolute path on every entry), or manually delete the existing entry (the next un-forced setup writes the absolute-path form against the now-not-registered slot). Without--force, re-runs are no-ops by design — the bare-form is treated as equivalent.stale. A pre-existing entry that pins one absolute path while the currently-resolved bin lives at a different absolute path (e.g. an old/Users/me/.npm-global/dkgafter a Node-version-manager rotation, vs the current/opt/homebrew/bin/dkg) is genuine drift, classifies asstale, and gets refreshed without--force. Bare-form vs absolute-form is not in this category — only absolute-vs-different-absolute is.dkg mcp setupcontinues writing the bare-"dkg"form, checkwhich dkg(POSIX) orwhere.exe dkg(Windows). An empty output means the bin isn't on PATH; the bare-"dkg"fallback is correct in that case but won't help GUI MCP clients (Claude Desktop, etc.) that don't inherit the shell PATH. Either install the umbrella CLI globally so the resolver finds it, or manually edit the client config to point at the absolute path you want."dkg"↔ resolved-absolute-path equivalence is uniform across all of them, so existing entries continue working unchanged. In practice the absolute-path form only matters for GUI-spawned clients (Claude Desktop, etc.) that don't inherit the shell PATH; CLI-spawned clients (Cursor, Claude Code) work either way.--forceis the deliberate opt-in to upgrade legacy entries to the GUI-friendly absolute-path form.Deferred (post-this-PR follow-ups)
Two clients deferred per the plan's defer-rule: their detection paths are speculative until verified against a live install, and their config formats need a worked-shape spec before we wire them up.
ClientTarget.format = 'yaml'; a clean-throw guard is in place so a future commit only needs to add the YAML write helper + the live-install-verified path/entry-shape. No architectural blocker.@iarna/toml(or equivalent) workspace dep, which deserves a separate commit so the dependency boundary is reviewable. Phase 1'sClientTarget.format = 'toml'clean-throw guard is in place; same posture as Continue.Both follow-ups inherit the phase-1 architecture and don't require any further refactor — only the per-format writer + the per-client path/entry-shape.
🤖 Generated with Claude Code