Skip to content

fix(claudecode-mcp): write global MCP to documented ~/.claude.json path#1633

Open
sirmacik wants to merge 3 commits into
dyoshikawa:mainfrom
sirmacik:fix/claudecode-mcp-global-path
Open

fix(claudecode-mcp): write global MCP to documented ~/.claude.json path#1633
sirmacik wants to merge 3 commits into
dyoshikawa:mainfrom
sirmacik:fix/claudecode-mcp-global-path

Conversation

@sirmacik
Copy link
Copy Markdown
Contributor

Summary

This fixes Claude Code global MCP output to use the documented user-scope path: ~/.claude.json.

Rulesync currently writes Claude Code global MCP to ~/.claude/.claude.json. That path worked in some environments, but it does not match Claude Code's documented MCP location and makes the generated config harder to audit, automate against, or reason about.

Closes #1387.

Why this needs care

Claude Code is widely used, including in enterprise setups where config files may be managed, audited, backed up, or inspected by automation. We should fix the path bug without introducing a destructive migration.

Earlier drafts considered deleting or archiving the legacy file to prevent possible stale/ghost MCP entries. I backed away from that after checking rulesync's own precedent: PR #333 introduced legacy path handling for .rulesync/.mcp.json by reading the legacy path with a deprecation warning, but never modifying it.

This PR follows that same pattern.

Behavior after this change

  • fromRulesyncMcp({ global: true }) writes to the documented path:
    • ~/.claude.json
  • fromFile({ global: true }) reads from:
    1. ~/.claude.json first
    2. then falls back to ~/.claude/.claude.json if the recommended path is absent
  • When the legacy path is used, rulesync emits a deprecation warning.
  • The legacy file is never deleted, renamed, archived, merged, or modified.

This keeps the migration conservative: rulesync fixes its output path while leaving any user-owned legacy file untouched.

Notes for upgrading users

If you previously generated Claude Code global MCP with rulesync, you may have this legacy file:

~/.claude/.claude.json

After upgrading, rulesync writes to:

~/.claude.json

If you want to fully avoid any risk of Claude Code reading stale MCP entries from the legacy path, manually inspect and remove the old file after confirming your new config is correct:

rm ~/.claude/.claude.json

Rulesync intentionally does not do this automatically.

Changes

  • Update ClaudecodeMcp.getSettablePaths({ global: true }) to return:
    • relativeDirPath: "."
    • relativeFilePath: ".claude.json"
  • Add legacy fallback in ClaudecodeMcp.fromFile.
  • Keep fromRulesyncMcp write behavior focused on the recommended path only.
  • Add tests for:
    • recommended path priority
    • legacy fallback
    • deprecation warning
    • local mode not using legacy
    • malformed recommended / legacy JSON behavior
    • byte-for-byte preservation of the legacy file
    • read-modify-write preservation of unrelated ~/.claude.json keys
  • Update E2E coverage for the global Claude Code MCP output path.

Verification

  • pnpm test src/features/mcp/claudecode-mcp.test.ts
  • pnpm test:e2e src/e2e/e2e-mcp.spec.ts
  • pnpm cicheck

All pass locally.

Risk

Low-to-medium.

The path change is straightforward and matches Claude Code documentation. The main risk is upgrade behavior for users who already have the legacy file. To minimize that risk, this PR deliberately avoids destructive actions and follows the existing rulesync legacy-path precedent from PR #333.

Per Claude Code docs (https://docs.claude.com/en/docs/claude-code/mcp),
user-scope MCP servers are stored in `~/.claude.json` at HOME root —
NOT inside `~/.claude/`. Earlier rulesync versions (≤ v8.17.0) wrote
to the wrong path `~/.claude/.claude.json`.

Issue dyoshikawa#1387 described this exact bug; it was closed by the reporter as
"filed prematurely" but the root cause is still present in main.

## Changes

### src/features/mcp/claudecode-mcp.ts

- `getSettablePaths({ global: true })` now returns the documented path
  `{ relativeDirPath: ".", relativeFilePath: ".claude.json" }`.
- `fromFile` falls back to the legacy path `~/.claude/.claude.json`
  with a deprecation warning via the optional `logger` parameter when
  the recommended path is absent. Mirrors the backward-compatibility
  pattern established by PR dyoshikawa#333 (`RulesyncMcp.fromFile` for legacy
  `.rulesync/.mcp.json`).
- `fromRulesyncMcp` always writes to the recommended path; the legacy
  file is never modified, deleted, or renamed by rulesync.

### Migration philosophy

Rulesync intentionally does NOT actively migrate user files at legacy
paths. The pattern is consistent with every prior legacy-path change
in the project (dyoshikawa#333, commit 5787195): read-fallback + deprecation
warning, never touch user data. Users who want to fully eliminate any
ghost-MCP risk from Claude Code's runtime behavior should manually
delete `~/.claude/.claude.json` after upgrading.

### Tests

- `fromFile`: 5 new tests
  - prefers recommended path when both exist
  - falls back to legacy path with deprecation warning when only legacy exists
  - does NOT fall back to legacy in local mode
  - initializes empty mcpServers when neither file exists
  - works without a logger when reading the legacy path
- `fromRulesyncMcp`: 4 tests
  - fresh install: initializes recommended path
  - does NOT modify legacy file (byte-identical assertion)
  - does not touch legacy file in local mode
  - preserves unrelated keys on `~/.claude.json` via RMW
- Existing global-mode tests updated to assert the new path.

### E2E

- Updated the global-MCP matrix entry: claudecode → `.claude.json`.
- New test: `should preserve legacy ~/.claude/.claude.json when writing
  to recommended path (global)` — byte-identical legacy assertion
  at CLI level.

## Verification

- `pnpm test src/features/mcp/claudecode-mcp.test.ts`: 56 tests pass.
- `pnpm test:e2e src/e2e/e2e-mcp.spec.ts`: 47 tests pass.
- `pnpm cicheck`: clean.

## Notes for upgrading users

After upgrading, rulesync writes global MCP to `~/.claude.json` (the
documented path). If you had a previous rulesync version that wrote to
`~/.claude/.claude.json`, that file is left in place untouched. To
fully eliminate any ghost-MCP risk from Claude Code's runtime behavior,
manually delete the legacy file:

    rm ~/.claude/.claude.json

## Related

- Closes dyoshikawa#1387 (was closed prematurely by reporter without fix).
- Related-but-out-of-scope: dyoshikawa#1275 (`global` flag not passed to
  `ClaudecodeMcp` constructor) — separate concern, separate PR.
@github-actions
Copy link
Copy Markdown
Contributor

Thank you for your contribution! Unfortunately, you currently have 3 open PRs (including this one), which exceeds the limit of 2 for external contributors.

Please wait for an existing PR to be reviewed/merged, or close one before opening a new one. See CONTRIBUTING.md for details.

@sirmacik
Copy link
Copy Markdown
Contributor Author

sirmacik commented May 12, 2026

I consider this PR ready from my side. I’m keeping it in draft only to respect the current two-PR contributor limit while #1631 and #1632 are still in flight. Once one of those lands and a review slot opens, I’ll mark this ready for review.

@sirmacik sirmacik marked this pull request as ready for review May 14, 2026 18:41
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.

claudecode global MCP writes to wrong path (~/.claude/.claude.json vs ~/.claude.json)

1 participant