Skip to content

feat: migrate .copilot/mcp-config.json -> .mcp.json (Copilot CLI auto-load path) - #1208

Closed
tamirdresher wants to merge 6 commits into
github:devfrom
tamirdresher:feat/mcp-json-migration
Closed

tamirdresher wants to merge 6 commits into
github:devfrom
tamirdresher:feat/mcp-json-migration

Conversation

@tamirdresher

Copy link
Copy Markdown
Collaborator

Why

GitHub Copilot CLI auto-loads MCP server configuration from .mcp.json at the repo root, not from .copilot/mcp-config.json. This was clarified by @caarlos0 in github/copilot-cli#3642 (now closed) and verified empirically against copilot mcp --help on CLI 1.0.58:

Configuration is loaded from multiple sources:
  User       ~/.copilot/mcp-config.json
  Workspace  .mcp.json
  Plugin     Installed plugins with MCP servers

squad-cli has been writing project-local MCP config to a path the CLI was never auto-loading. Users had to pass --additional-mcp-config @./.copilot/mcp-config.json on every invocation or they'd silently miss their project's MCP servers — many teams never noticed.

What

Additive migration from .copilot/mcp-config.json → .mcp.json (repo root). No breaking changes — existing repos keep working, and squad upgrade merges seamlessly.

6 commits organized in two phases:

Phase 1 — path rename (commit 892b2da2)

  • packages/squad-sdk/src/config/init.ts: squad init now writes .mcp.json at repo root
  • 4 template mirrors + squad.agent.md.template updates (twin-file invariant preserved via scripts/sync-templates.mjs --sync)
  • 5 docs files updated, headlined by a full rewrite of docs/.../features/mcp.md with a migration troubleshooting section
  • test/cli/init.test.ts: 15/15 (new .mcp.json assertion + legacy negative)
  • test/mcp-config.test.cjs: 10/10 (legacy preservation verified)
  • index.cjs: legacy inline writer removed; SDK init owns it now

Phase 2 — squad upgrade merge helper + dual-write (commits 3207f075, 4b635463, c264e57b, 92e3a394)

  • New helper: packages/squad-sdk/src/upgrade/migrate-mcp-config.ts (401 lines)
    • migrateMcpConfig(repoRoot, opts) — merges legacy mcpServers into .mcp.json, preserves the legacy file (no silent delete)
    • ensureMcpServerPinned(path, name, entry, opts) — used for squad_state pin
    • atomicWriteJson(path, value) — temp-file + intra-directory rename, never leaves a half-written .mcp.json
  • Conflict policy (informed by empirical Copilot CLI behavior):
    • Equivalent entries (same command/args/env, sort-key-normalized) → no-op
    • Non-equivalent same-name entries → warn + preserve existing .mcp.json entry (never silently overwrite)
  • Pre-write JSON validation — Copilot CLI silently drops malformed .mcp.json with zero diagnostic; the helper refuses to clobber a malformed file and refuses to write one
  • ensureSquadStateMcpPinned dual-write — during the additive window, squad_state is pinned to BOTH .mcp.json (auto-loaded) and .copilot/mcp-config.json IF it already exists (never creates the legacy file fresh)
  • Wired into runUpgrade before any other state migration so MCP servers are discoverable for the rest of the session
  • 13 vitest cases in test/upgrade-mcp-merge.test.ts: migration, merge, conflict (both branches), malformed input refusal, idempotence, atomic-write cleanliness

Phase 2 hardening (commit 77186501)

  • init.ts: legacy dual-write call wrapped in try/catch — EACCES/EISDIR on the legacy file downgrades to a warning instead of crashing squad init
  • atomicWriteJson: round-trip JSON.parse(serialized) before disk I/O — defense-in-depth against any future serialization regression
  • +2 vitest cases (parse-round-trip + POSIX EACCES via it.skipIf)

Quick Check

git fetch origin pull/<this-pr>/head:test-mcp-json && git checkout test-mcp-json
npm install
npx vitest run test/upgrade-mcp-merge.test.ts test/cli/init.test.ts
# expected: 29 passed, 1 skipped (POSIX-only EACCES test on Windows)

Then exercise the helper:

mkdir test-repo && cd test-repo
mkdir .copilot
cat > .copilot/mcp-config.json <<'EOF'
{ "mcpServers": { "example": { "type": "local", "command": "node", "args": ["-e", "console.error('hello')"] } } }
EOF
node /path/to/your/local/squad-cli upgrade
ls -la .mcp.json .copilot/mcp-config.json
# .mcp.json now has `example`; legacy file preserved.

Readiness Checklist

  • Tests: 29 passed in target suites, 1 POSIX-only skipped
  • Build green: npm run build -w packages/squad-sdk ✅
  • No new dependencies
  • Templates kept in sync via scripts/sync-templates.mjs --sync
  • Changeset present: .changeset/mcp-json-migration.md (minor for @bradygaster/squad-cli + @bradygaster/squad-sdk)
  • Existing .changeset/mcp-frontmatter-init.md path reference updated
  • No breaking changes — .copilot/mcp-config.json preserved on upgrade; --additional-mcp-config escape hatch still works for users who want overlays

Out of scope (deferred)

  • ADC (Agent Dev Compute) sandbox /root/.copilot/mcp-config.json — user-level path remains auto-loaded by Copilot CLI, so the sandbox keeps working unchanged
  • Removing the legacy write site — planned for release N+1 after one minor cycle of additive support; the merge helper itself stays forever for users skipping a version

Closes

Closes the round-trip on github/copilot-cli#3642.


PR queued behind #1200, #1202, #1207 — happy to defer review until you've caught up on those.

tamirdresher and others added 6 commits June 3, 2026 06:22
…g.json (Phase 1)

Phase 1 of the MCP config path migration. `squad init` now writes the
per-repo MCP server config to `.mcp.json` at the repo root — the workspace
path that GitHub Copilot CLI auto-loads per `copilot mcp --help`.

The legacy `.copilot/mcp-config.json` path was never auto-loaded by the
CLI; users had to pass `--additional-mcp-config .copilot/mcp-config.json`
on every invocation, and many teams never noticed their MCP servers
weren't loading. See github/copilot-cli#3642.

Scope (all AUDIT-MATCH, no collateral, no unrelated changes):

  Code (4)
    - packages/squad-sdk/src/config/init.ts  (write target → .mcp.json)
    - packages/squad-cli/src/cli/core/init.ts  (option doc comment)
    - packages/squad-cli/src/cli/commands/state-mcp.ts  (help text)
    - index.cjs  (legacy inline writer removed; SDK init owns it now)

  Tests (1)
    - test/cli/init.test.ts  (new .mcp.json assertion + legacy negative)

  Templates — twin-file mirrors (8)
    - .squad-templates/mcp-config.md
    - .squad-templates/squad.agent.md
    - .github/agents/squad.agent.md
    - packages/squad-cli/templates/mcp-config.md
    - packages/squad-cli/templates/squad.agent.md.template
    - packages/squad-sdk/templates/mcp-config.md
    - packages/squad-sdk/templates/squad.agent.md.template
    - templates/mcp-config.md
    - templates/squad.agent.md.template

  Docs (5)
    - docs/src/content/docs/features/mcp.md  (full rewrite + migration section)
    - docs/src/content/docs/concepts/portability.md
    - docs/src/content/docs/features/enterprise-platforms.md
    - docs/src/content/docs/features/notifications.md
    - docs/src/content/docs/reference/config.md

  Changesets (2)
    - .changeset/mcp-json-migration.md  (NEW — minor bump cli+sdk)
    - .changeset/mcp-frontmatter-init.md  (1-line rationale update)

Phase 2 (deferred, not in this commit):
  - `squad upgrade` merge helper for existing `.copilot/mcp-config.json`
  - `ensureSquadStateMcpPinned` dual-write window
  - Worf review pass before PR

Tests: test/cli/init.test.ts 15/15 pass; test/mcp-config.test.cjs 10/10 pass.

Refs: github/copilot-cli#3642

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Adds SDK helper migrateMcpConfig() that folds legacy .copilot/mcp-config.json into root .mcp.json with atomic temp+rename writes. Conflict policy: keep .mcp.json, warn on diff. Wired into runUpgrade() early so downstream steps see merged servers. Legacy file preserved for one deprecation cycle.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…fig.json (Phase 2b)

If a legacy .copilot/mcp-config.json already exists, init now keeps its squad_state entry in sync with the .mcp.json spec via ensureMcpServerPinned with createIfMissing=false. Fresh inits still skip the legacy file entirely — design contract enforced by test/cli/init.test.ts:101-104.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
13 new vitest cases under test/upgrade-mcp-merge.test.ts exercise migrateMcpConfig, ensureMcpServerPinned, and atomicWriteJson against real tempdirs (the helpers use raw node:fs). Covers: no-legacy, fresh migration, disjoint merge, equivalent skip, conflict policy, malformed-target refusal, idempotence, empty-legacy, absent-file no-op, stale-pin overwrite, conflict-without-overwrite, and no .tmp leftovers on success.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- init.ts: wrap legacy ensureMcpServerPinned in try/catch (Worf Condition A)
  -- prevents squad init crash on EACCES/EISDIR of .copilot/mcp-config.json
- atomicWriteJson: round-trip JSON.parse before write (Worf Condition B)
  -- defense-in-depth against silent-fallback hazard (Seven section c)
- tests: cover both new failure modes

Reviewed by: Worf
Closes blocking conditions on feat/mcp-json-migration before upstream PR.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings June 3, 2026 03:45

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR migrates Squad’s per-repo MCP server configuration from the legacy .copilot/mcp-config.json location to the Copilot CLI auto-loaded workspace path .mcp.json (repo root), and adds an upgrade-time merge helper to preserve and reconcile existing legacy configs.

Changes:

  • Update squad init (SDK-driven) to write .mcp.json at repo root, with best-effort dual-write pinning of squad_state into an existing legacy file.
  • Add migrateMcpConfig() + supporting helpers to fold legacy .copilot/mcp-config.json into .mcp.json during squad upgrade, with conflict detection and atomic writes.
  • Refresh templates/docs/tests to reflect the new config location and migration behavior.

Reviewed changes

Copilot reviewed 26 out of 26 changed files in this pull request and generated 8 comments.

Show a summary per file
File Description
test/upgrade-mcp-merge.test.ts Adds vitest coverage for merge/conflict/malformed/atomic-write behavior.
test/cli/init.test.ts Updates init assertions for .mcp.json + adds legacy-write hardening test.
templates/squad.agent.md.template Updates template guidance to reference .mcp.json (repo root).
templates/mcp-config.md Updates MCP config location guidance (migration note needs follow-up per comments).
packages/squad-sdk/templates/squad.agent.md.template Mirrors the .mcp.json guidance change for SDK templates.
packages/squad-sdk/templates/mcp-config.md Mirrors MCP config location guidance for SDK templates.
packages/squad-sdk/src/upgrade/migrate-mcp-config.ts New SDK helper implementing merge policy + atomic JSON writes.
packages/squad-sdk/src/upgrade/index.ts New upgrade barrel export for SDK consumers.
packages/squad-sdk/src/index.ts Exposes the new upgrade/* exports from the SDK root.
packages/squad-sdk/src/config/init.ts Switches init output to .mcp.json and adds legacy pin dual-write.
packages/squad-cli/templates/squad.agent.md.template Mirrors .mcp.json guidance change for CLI templates.
packages/squad-cli/templates/mcp-config.md Mirrors MCP config location guidance for CLI templates.
packages/squad-cli/src/cli/core/upgrade.ts Runs MCP merge early in squad upgrade and reports warnings.
packages/squad-cli/src/cli/core/init.ts Updates option docstring to reference .mcp.json instead of legacy path.
packages/squad-cli/src/cli/commands/state-mcp.ts Updates help text to reference .mcp.json auto-load behavior.
index.cjs Removes legacy inline MCP writer; documents SDK init as source of truth.
docs/src/content/docs/reference/config.md Updates optional file list to .mcp.json (needs rule text alignment per comments).
docs/src/content/docs/features/notifications.md Updates examples text to reference .mcp.json (repo root).
docs/src/content/docs/features/mcp.md Rewrites config scope guidance and adds migration section (needs “upgrade now ships merge” wording fix per comments).
docs/src/content/docs/features/enterprise-platforms.md Updates ADO MCP instructions to reference .mcp.json.
docs/src/content/docs/concepts/portability.md Updates portability table to reference .mcp.json.
.squad-templates/squad.agent.md Mirrors .mcp.json guidance change in squad templates.
.squad-templates/mcp-config.md Mirrors MCP config location guidance in squad templates.
.github/agents/squad.agent.md Mirrors .mcp.json guidance in the committed agent file.
.changeset/mcp-json-migration.md Adds changeset documenting the path migration + upgrade merge behavior.
.changeset/mcp-frontmatter-init.md Updates changeset text to reference .mcp.json as the non-frontmatter default.

Comment on lines +115 to 117
- **`.mcp.json`** — MCP server config at repo root (opt-in: `--include-mcp-config`); auto-loaded by Copilot CLI

> ⚠️ **Hard rule:** Squad NEVER writes temp files, logs, or memory to your repo root. All team state lives in `.squad/` only. Your project tree stays clean.
3. Delete `.copilot/mcp-config.json` once you've verified the merged `.mcp.json` works (`copilot mcp` should list the merged servers).
4. Commit `.mcp.json`. You can stop passing `--additional-mcp-config`.

A `squad upgrade` guided merge will ship in a follow-up release; until then, the migration is manual.
Comment thread templates/mcp-config.md
3. **User-level:** `~/.copilot/mcp-config.json` (personal, user-wide).
4. **CLI override:** `--additional-mcp-config <path>` (session-specific).

> **Migration note:** Older Squad versions wrote the per-repo config to `.copilot/mcp-config.json`. That path was not auto-loaded by Copilot CLI and required `--additional-mcp-config`. New `squad init` writes `.mcp.json` instead. If your repo still has a `.copilot/mcp-config.json`, it is preserved as-is — see `docs/features/mcp.md` for a manual merge recipe until `squad upgrade` ships a guided merge.
3. **User-level:** `~/.copilot/mcp-config.json` (personal, user-wide).
4. **CLI override:** `--additional-mcp-config <path>` (session-specific).

> **Migration note:** Older Squad versions wrote the per-repo config to `.copilot/mcp-config.json`. That path was not auto-loaded by Copilot CLI and required `--additional-mcp-config`. New `squad init` writes `.mcp.json` instead. If your repo still has a `.copilot/mcp-config.json`, it is preserved as-is — see `docs/features/mcp.md` for a manual merge recipe until `squad upgrade` ships a guided merge.
3. **User-level:** `~/.copilot/mcp-config.json` (personal, user-wide).
4. **CLI override:** `--additional-mcp-config <path>` (session-specific).

> **Migration note:** Older Squad versions wrote the per-repo config to `.copilot/mcp-config.json`. That path was not auto-loaded by Copilot CLI and required `--additional-mcp-config`. New `squad init` writes `.mcp.json` instead. If your repo still has a `.copilot/mcp-config.json`, it is preserved as-is — see `docs/features/mcp.md` for a manual merge recipe until `squad upgrade` ships a guided merge.
3. **User-level:** `~/.copilot/mcp-config.json` (personal, user-wide).
4. **CLI override:** `--additional-mcp-config <path>` (session-specific).

> **Migration note:** Older Squad versions wrote the per-repo config to `.copilot/mcp-config.json`. That path was not auto-loaded by Copilot CLI and required `--additional-mcp-config`. New `squad init` writes `.mcp.json` instead. If your repo still has a `.copilot/mcp-config.json`, it is preserved as-is — see `docs/features/mcp.md` for a manual merge recipe until `squad upgrade` ships a guided merge.
Comment on lines +1361 to +1363
`Updated legacy .copilot/mcp-config.json with the latest squad_state pin. ` +
`Run \`squad upgrade\` to fold this file into .mcp.json and delete the legacy copy.`,
);
Comment on lines +365 to +366
writeFileSync(tempPath, serialized, 'utf-8');
renameSync(tempPath, filePath);
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.

2 participants