SessionStart hooks with mcp_tool type fire before MCP servers connect
Description
Plugin SessionStart hooks that use type: "mcp_tool" always fail because they fire before the MCP server has connected. The hooks execute synchronously during startup, and MCP connection begins ~290ms later. Every mcp_tool hook gets skipped with "MCP server not connected."
Steps to Reproduce
- Create a plugin with
hooks/hooks.jsoncontaining SessionStart hooks that call MCP tools:
{
"hooks": {
"SessionStart": [
{
"matcher": "startup",
"hooks": [
{
"type": "mcp_tool",
"server": "concurai-ememory",
"tool": "e_property",
"input": { "action": "list" },
"timeout": 30
}
]
}
]
}
}
- Plugin has
.mcp.jsonconfiguring an HTTP MCP server - Launch Claude Code with
--plugin-dir
Expected Behavior
SessionStart hooks with type: "mcp_tool" should execute after the MCP server they reference has connected, or Claude Code should provide an McpReady hook event that fires after MCP connections are established.
Actual Behavior
All mcp_tool hooks are skipped with warning: Hooks: mcp_tool hook skipped — MCP server 'concurai-ememory' not connected
Debug log shows the timing gap clearly:
2026-05-11T01:45:08.907Z [WARN] Hooks: mcp_tool hook skipped — MCP server 'concurai-ememory' not connected
2026-05-11T01:45:08.907Z [WARN] Hooks: mcp_tool hook skipped — MCP server 'concurai-ememory' not connected
2026-05-11T01:45:08.907Z [WARN] Hooks: mcp_tool hook skipped — MCP server 'concurai-ememory' not connected
2026-05-11T01:45:08.907Z [WARN] Hooks: mcp_tool hook skipped — MCP server 'concurai-ememory' not connected
2026-05-11T01:45:08.921Z [DEBUG] MCP server "plugin:concurai-ememory:concurai-ememory": Starting connection...
2026-05-11T01:45:09.197Z [DEBUG] MCP server "plugin:concurai-ememory:concurai-ememory": Successfully connected (transport: http) in 280ms
Hooks fire at .907, MCP connection starts at .921 and completes at .197 — hooks never had a chance.
Use Case
Session memory continuity. Our plugin uses SessionStart hooks to restore context from a knowledge store on startup (orient), on context compaction (restore session state), and on resume (cross-session context). These are the primary use cases for mcp_tool hooks — they're useless if they can't call the MCP server they depend on.
Suggested Fix
Either:
- Wait for MCP connections before firing SessionStart hooks that reference MCP tools
- Add an
McpReadyhook event that fires after all MCP servers have connected - Retry
mcp_toolhooks that fail due to server not connected (with backoff)
Environment
- Claude Code v2.1.138
- macOS (Darwin 25.3.0)
- MCP transport: HTTP (Streamable HTTP)
- Plugin loaded via
--plugin-dir
6 Comments
Found 3 possible duplicate issues:
This issue will be automatically closed as a duplicate in 3 days.
🤖 Generated with Claude Code
Update: PostCompact hooks also affected
Same MCP disconnection issue affects
PostCompacthooks, not justSessionStart.Reproduction
hooks.json with PostCompact event:
Run
/compactin a session with the plugin loaded.Debug log evidence
Hook fires 5ms after compaction completes, MCP still disconnected.
User-visible error
Impact
This makes
mcp_toolhooks unusable for all lifecycle events — SessionStart, PostCompact, and likely PreCompact and SessionEnd too. MCP disconnects during lifecycle transitions and doesn't reconnect before hooks fire.The primary use case for PostCompact + mcp_tool is session rehydration: restoring context from a knowledge store after compaction destroys it. Without this, plugins that provide long-term memory have no automated recovery path after compaction.
Environment
Resolution: Not a race condition — plugin-scoped MCP limitation
After further testing, this is not a timing race. The root cause:
.mcp.json) are torn down during all lifecycle transitions (SessionStart, PostCompact, PreCompact). Hooks firing during these events find the server disconnected.claudeai-proxytransport) stay connected through all lifecycle transitions. Hooks targeting connectors work perfectly — PostCompact hook via connector executed successfully.The fix (for plugin authors)
Don't put MCP server definitions in the plugin's
.mcp.json. Configure the MCP connection as a connector at the account level. Plugin provides skills, CLAUDE.md, and hooks — hooks reference the connector server name. This works today with no changes to Claude Code.Still potentially worth fixing
Plugin-scoped MCP servers could survive lifecycle transitions like connectors do, but it's not a blocker — the connector approach is architecturally cleaner anyway (plugin = logic, connector = infrastructure).
Closing as resolved via workaround.
Your workaround doesn't fix SessionStart, right? Feels like this should be reopened.
Correct. we just are avoiding the problem by setting up MCP outside of the plugin.
This issue has been automatically locked since it was closed and has not had any activity for 7 days. If you're experiencing a similar issue, please file a new issue and reference this one if it's relevant.