Bug Description
MCP tools configured via .mcp.json / .aceclaw/mcp-servers.json are never available to the agent — they always return Unknown tool: mcp__<server>__<tool>.
The aceclaw-mcp module is fully implemented (config loading, server lifecycle, tool bridging, health checks), but the daemon integration has a race condition that makes MCP tools effectively unusable.
Root Cause
In AceClawDaemon.java, MCP servers are initialized asynchronously on a virtual thread (line 308) to avoid blocking daemon boot. However, two critical downstream operations run synchronously on the main thread before the async init can complete:
1. System prompt tool names (line 393)
var toolNames = toolRegistry.all().stream()
.map(Tool::name)
.collect(Collectors.toSet());
String systemPrompt = SystemPromptLoader.load(..., toolNames, ...);
This collects tool names for the system prompt before MCP tools are registered. MCP tools are never included in the system prompt's tool guidance, so the LLM doesn't even know they exist.
2. Session-scoped registry snapshot (StreamingAgentHandler.java line 1248)
for (var tool : toolRegistry.all()) {
Tool effectiveTool = materializeSessionScopedTool(...);
registry.register(new PermissionAwareTool(...));
}
Each session creates a snapshot of the global registry at session creation time. If a session is created before the async MCP init completes, MCP tools are missing from that session forever. There is no mechanism to refresh the session registry when new tools are added.
3. No synchronization
The async init thread (line 308-319) has no CountDownLatch, CompletableFuture, or any synchronization primitive that downstream code could await. Even if someone wanted to wait for MCP init to complete, there's no way to do it.
Expected Behavior
MCP tools configured in .mcp.json should be available to the agent and LLM, similar to how Claude Code handles MCP tools.
Suggested Fix
Options (in order of preference):
-
Synchronize with timeout: Use a CompletableFuture for MCP init. Before building the system prompt and creating sessions, await with a reasonable timeout (e.g., 30s). This is the simplest correct fix.
-
Lazy registration with refresh: Keep async init but add a mechanism to refresh the system prompt and session registries when MCP tools become available (more complex, handles slow servers better).
-
Two-phase boot: Load MCP config synchronously but defer connection establishment. Register placeholder tools that block on first invocation until the server is connected (best UX but most complex).
Affected Code
aceclaw-daemon/src/main/java/dev/aceclaw/daemon/AceClawDaemon.java (lines 298-321, 393)
aceclaw-daemon/src/main/java/dev/aceclaw/daemon/StreamingAgentHandler.java (line 1248)
aceclaw-core/src/main/java/dev/aceclaw/core/agent/StreamingAgentLoop.java (line 775 — "Unknown tool" error)
Version
v0.3.3
Bug Description
MCP tools configured via
.mcp.json/.aceclaw/mcp-servers.jsonare never available to the agent — they always returnUnknown tool: mcp__<server>__<tool>.The
aceclaw-mcpmodule is fully implemented (config loading, server lifecycle, tool bridging, health checks), but the daemon integration has a race condition that makes MCP tools effectively unusable.Root Cause
In
AceClawDaemon.java, MCP servers are initialized asynchronously on a virtual thread (line 308) to avoid blocking daemon boot. However, two critical downstream operations run synchronously on the main thread before the async init can complete:1. System prompt tool names (line 393)
This collects tool names for the system prompt before MCP tools are registered. MCP tools are never included in the system prompt's tool guidance, so the LLM doesn't even know they exist.
2. Session-scoped registry snapshot (StreamingAgentHandler.java line 1248)
Each session creates a snapshot of the global registry at session creation time. If a session is created before the async MCP init completes, MCP tools are missing from that session forever. There is no mechanism to refresh the session registry when new tools are added.
3. No synchronization
The async init thread (line 308-319) has no
CountDownLatch,CompletableFuture, or any synchronization primitive that downstream code could await. Even if someone wanted to wait for MCP init to complete, there's no way to do it.Expected Behavior
MCP tools configured in
.mcp.jsonshould be available to the agent and LLM, similar to how Claude Code handles MCP tools.Suggested Fix
Options (in order of preference):
Synchronize with timeout: Use a
CompletableFuturefor MCP init. Before building the system prompt and creating sessions, await with a reasonable timeout (e.g., 30s). This is the simplest correct fix.Lazy registration with refresh: Keep async init but add a mechanism to refresh the system prompt and session registries when MCP tools become available (more complex, handles slow servers better).
Two-phase boot: Load MCP config synchronously but defer connection establishment. Register placeholder tools that block on first invocation until the server is connected (best UX but most complex).
Affected Code
aceclaw-daemon/src/main/java/dev/aceclaw/daemon/AceClawDaemon.java(lines 298-321, 393)aceclaw-daemon/src/main/java/dev/aceclaw/daemon/StreamingAgentHandler.java(line 1248)aceclaw-core/src/main/java/dev/aceclaw/core/agent/StreamingAgentLoop.java(line 775 — "Unknown tool" error)Version
v0.3.3