[BUG] Plugin MCP servers killed by excludeStalePluginClients right after a successful connect (managed/team accounts)
Preflight Checklist
- [x] I have searched existing issues and this hasn't been reported yet
- [x] This is a single bug report (please file separate reports for different bugs)
- [x] I am using the latest version of Claude Code
What's Wrong?
On managed/team (enterprise) accounts, MCP servers bundled with a Claude Code plugin are killed by SIGINT within ~1.5–45s of a successful connection, via an excludeStalePluginClients: marking stale (removed) sweep. This happens even though the server connected successfully (hasTools:true), the plugin content did not change (same gitCommitSha), and the server passes the enterprise allowedMcpServers allowlist.
Result: the servers do not appear in /mcp (or flicker in and out). The same plugin + servers work fine on personal (non-managed) accounts, which strongly points to the enterprise settings-refresh / plugin re-registration path as the trigger.
Full evidence with logs (5 scenarios: same-session kill, cross-session kill, nondeterminism, no-actual-update, and ruling out auth/network/allowlist as the cause) is in "Additional Information" below.
What Should Happen?
A plugin MCP server that connected successfully and whose plugin has not changed should stay connected and be listed in /mcp. The stale-client sweep should discard only genuinely stale clients (from a previous plugin version, i.e. a changed gitCommitSha), not freshly-connected current ones. The sweep should also be scoped to the session that owns the client — a second Claude Code session should not be able to tear down MCP servers belonging to a different, already-running session.
Error Messages/Logs
Evidence A — same session kills its own just-connected server (~1.5s later)
Log: mcp-logs-plugin-<plugin>-playwright/2026-07-07T09-18-25-674Z.jsonl
2026-07-07T09:18:40.859Z | sess=eeacd94f | Successfully connected (transport: stdio) in 1141ms
2026-07-07T09:18:40.859Z | sess=eeacd94f | Connection established with capabilities: {"hasTools":true,...,"serverVersion":{"name":"Playwright","version":"1.61..."}}
2026-07-07T09:18:40.866Z | sess=eeacd94f | Channel notifications skipped: server did not declare claude/channel capability
2026-07-07T09:18:42.404Z | sess=eeacd94f | excludeStalePluginClients: marking stale (removed)
2026-07-07T09:18:42.404Z | sess=eeacd94f | Sending SIGINT to MCP server process
2026-07-07T09:18:42.456Z | sess=eeacd94f | MCP server process exited cleanly
The server connects at 40.859, and the same session (eeacd94f) marks it stale and SIGINTs it at 42.404 — ~1.5s later.
Evidence B — a different session kills an already-running server (cross-session sweep)
Log: mcp-logs-plugin-<plugin>-playwright/2026-07-07T10-03-49-131Z.jsonl
2026-07-07T10:04:01.223Z | sess=2df1ea93 | Successfully connected (transport: stdio) in 1074ms
2026-07-07T10:04:01.223Z | sess=2df1ea93 | Connection established with capabilities: {"hasTools":true,...}
2026-07-07T10:04:44.525Z | sess=37470a12 | excludeStalePluginClients: marking stale (removed)
2026-07-07T10:04:44.526Z | sess=37470a12 | Sending SIGINT to MCP server process
2026-07-07T10:04:44.578Z | sess=37470a12 | MCP server process exited cleanly
Session 2df1ea93 brings the server up. 43s later a different session 37470a12 marks it stale and SIGINTs it.
Evidence C — nondeterministic (same machine, same plugin, same SHA)
On 2026-07-08, three consecutive sessions connected and were NOT swept:
2026-07-08T09-57-31-988Z.jsonl : connected=1 stale/SIGINT=0 (survived)
2026-07-08T10-02-35-430Z.jsonl : connected=1 stale/SIGINT=0 (survived)
2026-07-08T10-03-04-326Z.jsonl : connected=1 stale/SIGINT=0 (survived)
...while the 2026-07-07 sessions above were swept. Identical machine, plugin, and commit — the difference is timing, characteristic of a race, not a deterministic policy decision.
Evidence D — no actual plugin update occurred
Installed gitCommitSha matches marketplace HEAD; the marketplace repo was not updated between these runs. The plugin nonetheless re-registers on every start (its lastUpdated is bumped each launch even when the SHA is unchanged), which appears to be what triggers the stale-sweep.
Evidence E — rules out token / network / policy / transport auth
playwright is a purely local stdio browser server: it makes no network calls and uses no token, and it passes the allowedMcpServers allowlist (matched by server name). It is killed by the identical sweep. So the cause is not authentication, network reachability, the enterprise MCP allowlist, or a specific server — it is the plugin-client stale-sweep itself.
Steps to Reproduce
- Use a managed/enterprise account with remote settings (forceRemoteSettingsRefresh) and an allowedMcpServers allowlist.
- Install a plugin from a git marketplace that declares a stdio MCP server in its .mcp.json (server name allowlisted, e.g. playwright via npx @playwright/mcp).
- Start Claude Code (claude).
- Check the per-server MCP log immediately: ~/Library/Caches/claude-cli-nodejs/<project>/mcp-logs-plugin-<plugin>-<server>/<timestamp>.jsonl
- Observe the server connect successfully (Successfully connected, hasTools:true), then shortly after get excludeStalePluginClients: marking stale (removed) followed by SIGINT and clean exit — despite no plugin update (same gitCommitSha).
- Intermittent — retry a few starts, and/or open a second Claude Code session concurrently to reliably trigger the cross-session variant (see Evidence B).
Note: on a personal (non-managed) account, the identical plugin/config does not reproduce this — consistent with the trigger being the managed-only forceRemoteSettingsRefresh path.
Claude Model
None
Is this a regression?
No, this never worked
Last Working Version
_No response_
Claude Code Version
2.1.161, 2.1.199, 2.1.202 (reproduced on all three). Changelog reviewed through 2.1.204 — no related fix found in any intervening release (checked entries mentioning MCP/plugin/managed settings specifically; closest-but-unrelated fixes were in 2.1.169 and 2.1.191, both about allowlist enforcement timing and forceRemoteSettingsRefresh not firing at all via MDM — neither addresses this stale-client-kill race).
Platform
Anthropic API
Operating System
macOS
Terminal/Shell
iTerm2
Additional Information
Suspected root cause: initialization ordering race between (a) bringing up plugin MCP clients from cache and (b) asynchronous plugin re-registration + forceRemoteSettingsRefresh. Clients created before re-registration completes are misclassified as stale and swept, even though they belong to the current, unchanged plugin. The cross-session variant (Evidence B) additionally suggests the stale-sweep is not scoped to the session that owns the client.
Suggested fix: excludeStalePluginClients should compare plugin content/version (e.g. gitCommitSha, not lastUpdated) before and after re-registration, and only tear down clients belonging to plugins that actually changed. The sweep should also be scoped to the session that owns the client.
Workaround in use: declaring the same servers via managed-mcp.json instead of the plugin's .mcp.json avoids the issue entirely — managed-mcp.json is a separate config scope not tied to plugin lifecycle, so excludeStalePluginClients never touches it. No allowlist changes were needed since both servers were already permitted by name in allowedMcpServers (which filters by server name, not by declaration source). This is a viable mitigation, but shouldn't be necessary.
Impact: plugin-provided MCP servers are unusable/unreliable on managed accounts (the intended distribution path for org MCP via plugins). Users must work around it via managed-mcp.json or by keeping strictly one Claude Code session — neither is a proper fix.
Log locations (macOS): per-server plugin MCP logs (source of the Evidence A–C excerpts): ~/Library/Caches/claude-cli-nodejs/<project>/mcp-logs-plugin-<plugin>-<server>/<timestamp>.jsonl