Skip to content

bug: MCP tools always 'Unknown tool' due to async init race condition #386

Description

@xinhuagu

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):

  1. 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.

  2. 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).

  3. 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

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions