Skip to content

feat(daemon): keep MCP server logins in sync across accounts and live sessions - #143

Open
koushikraj-s wants to merge 23 commits into
uwuclxdy:mommyfrom
koushikraj-s:feat/mcp-login-sync
Open

koushikraj-s wants to merge 23 commits into
uwuclxdy:mommyfrom
koushikraj-s:feat/mcp-login-sync

Conversation

@koushikraj-s

Copy link
Copy Markdown

Summary

Stacked on #142. This branch contains #142's 4 commits plus 7 of its own. Please review it after #142; I'll rebase once #142 is merged. Related: #138.

MCP server logins (mcpOAuth) are stored per Claude account, but MCP servers aren't tied to the Claude account. In a live run, a session was swapped twice in one second (→support, then the chain moved it →koushikraj). It landed on an account whose Atlassian entry held an already-rotated refresh token, so the server lost auth. Logins made inside one session also stay in that session's per-session item, which is the #138 divergence.

This PR makes MCP logins shared:

  • Hand-off fan-out. A hand-off merges the item's mcpOAuth into every account's credentials.json, session-token.json and session-token.static.json, not just the incoming one.
  • Daemon sync. At most every 15 s, on its own thread, the daemon syncs MCP logins across all of those stores and every live session's per-session item:
    • per server, the usable entry with the latest expiresAt wins;
    • a cleared entry (no tokens) never wins and never overwrites;
    • it re-reads before writing, touches only mcpOAuth, and never creates an item;
    • a locked keychain sits the pass out.
  • Adoption. Claude Code creates a per-session item itself on an /mcp sign-in, so the item has no owner row. Such an item is adopted (authorize_live_write) before writing, but only inside a live session's own runtime-<sid> dir. Foreign CLAUDE_CONFIG_DIR items stay untouched (Bug: Keychain census deletes per-config-dir login items clauth did not seed (foreign CLAUDE_CONFIG_DIR sessions lose their login, unlogged) #81).
  • Opt-out: sync_mcp_logins = false turns off both the sync and the fan-out.

Design points worth your view:

  • Signing out of one MCP server comes back while any other account or session still holds a usable copy. Turning the setting off is the way to sign out everywhere.
  • Writing into a live session's item assumes Claude Code doesn't later overwrite it from an older in-memory copy. Live use showed no such overwrite, but I can't verify Claude Code's internals.
  • If Claude Code marked a server "needs authentication" when a session started, that session keeps the state until /mcp → Disable → Enable. That reconnects with the synced login and needs no new sign-in.
  • The sync is macOS only. The file half would work on Linux too, if you want that.

Verified live on macOS (Claude Code 2.1.310):

  • three swaps in 4 s on one session, and Atlassian kept working;
  • fresh Sentry and Firecrawl sign-ins in one session reached all 12 stores and every live session's item in one pass;
  • both worked in a session that had never signed in.

Checklist

  • Secrets redacted from any pasted output (no sk-ant-*, no ~/.claude/~/.clauth contents)
  • cargo fmt --all -- --check, cargo clippy --locked --all-targets --all-features -- -D warnings and cargo test --locked --all-features pass locally

Local run (macOS, -j 2 --test-threads=2):

  • cargo test --locked --all-features: 8368 passed, 1 failed, 11 ignored.
  • The failure was the structural test no_item_leg_folds_in_a_carry_or_a_flock, which counts the call sites of owned_keychain_leg. The sync adds a seventh. That call isn't inside a state-lock hold or a carry, so b7fea2a updates the count, and the test passes on re-run.
  • fmt and clippy are clean.

AI

  • no AI
  • AI-assisted, I reviewed everything
  • AI did most of it under my direction

@uwuclxdy uwuclxdy left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

Thanks for splitting this out from #142 and for the live runs. I'd like the shared model as a choice rather than the only behavior: a toggle between keeping MCP logins per account and syncing them across every account and live session the way this PR does. Three asks inline.

Comment thread src/profile.rs Resolved
Comment thread src/runtime.rs Resolved
Comment thread src/claude.rs Resolved
@koushikraj-s

Copy link
Copy Markdown
Author

Thanks, that makes sense. The shared model is now a choice: a sync MCP logins row in the Config tab (3a741af), on by default, and off keeps logins per account as before. The store sync runs on every platform with only the Keychain legs macOS-only (02510cc), and the winner is picked through fresher_copy (ecec46a). I also rebuilt the branch on the updated #142 and updated the wiki pages for the new row (6befdf6).

@uwuclxdy uwuclxdy left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

The three asks landed: the sync MCP logins row, the store sync on every platform, and one per-server rule in fresher_token. One gap is left off macOS.

Claude Code's first save into a session's .credentials.json (an MCP sign-in is one) replaces clauth's symlink with a regular file, and from then on the sync neither reads nor writes that file. A login made inside a running Linux session then reaches no other account until that session switches. Writing those live files would race Claude Code's own refresh of the same file, so the sync should read them and keep writing stores only. The row's on-hint and the sync_mcp_logins rows in wiki/Configuration.md and wiki/Security.md then say what a running session gets off macOS.

Comment thread src/runtime.rs Resolved
Comment thread src/tui/render/global_config.rs Resolved
Comment thread src/claude.rs Resolved
Comment thread wiki/Security.md Resolved
@koushikraj-s

Copy link
Copy Markdown
Author

Thanks! Rebased onto current mommy (now without #142's commits). The live-file part is in 040de46: every live session's .credentials.json (the runtime dirs live_session_runtime_dirs lists, plus ~/.claude/.credentials.json) is read as an MCP login source on every platform, and the sync still writes only stores, never those live files. The hint and wiki follow in cf9f8ba and 12a20c6.

@uwuclxdy uwuclxdy left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

I pushed two commits onto the branch: 5fe948a pins the live-file read with a sync pass over a running session's file and the global one, and 926f616 moves the sync MCP logins sentence in wiki/Auto-Switch.md back beside the logins it refers to, since the rebase put the background-session passage between them. This merges after the v0.18.1 tag.

koushikrajs and others added 20 commits October 29, 2026 02:56
@koushikraj-s

Copy link
Copy Markdown
Author

Rebased onto the new #146 (1dab4c0) with the history kept: my fifteen commits in their original order, then your two commits (the wiki sentence move and the live-file test) unchanged and under your name. Two small commits on top finish the move. The first makes this branch use the live-session helpers and write witnesses that now live in #146 (mcp_sync_may_adopt is now session_item_may_adopt, and authorize_live_write takes the background flag). The second keeps one copy of the swap paragraph, with the MCP sync sentence beside the logins as you placed it and the copied-bearer sentences from #146. Behaviour is unchanged, and your new test passes.

One heads-up if you read it commit by commit: rebasing onto #146 left the earlier commits carrying duplicates of the helpers that moved, and only the last two commits remove them. The branch tip builds and passes the targeted tests and clippy, but commits 2–17 don't build on their own. It also conflicts with current mommy again; I'll rebase once more after #146 lands.

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.

3 participants