Skip to content

fix: Implement LazyToolLoader pattern to resolve 'Client not initialized' error#1

Open
Adriftnote wants to merge 1 commit intothedotmack:mainfrom
Adriftnote:fix/lazy-tool-loader-pattern
Open

fix: Implement LazyToolLoader pattern to resolve 'Client not initialized' error#1
Adriftnote wants to merge 1 commit intothedotmack:mainfrom
Adriftnote:fix/lazy-tool-loader-pattern

Conversation

@Adriftnote
Copy link

Fix: Implement LazyToolLoader pattern to resolve "Client not initialized" error

Background

Hi! 👋 First of all, thank you so much for creating mcp-client-cli. I've been using claude-mem and it's been incredibly helpful for my workflow. I wanted to interact with MCP servers directly from the command line, so I found your CLI tool.

While testing it with my SQLite MCP servers, I encountered the "Client not initialized" error. After diving into the code and debugging, I found the root cause and implemented a fix based on Anthropic's LazyToolLoader pattern.

This is my first time contributing to GitHub, so please let me know if I should change anything! 😅 I hope this helps improve the tool.


Problem

The CLI was experiencing a "Client not initialized" error because:

  1. Client connected during tool registration (registerServerTools)
  2. Tools were registered as action handlers
  3. Client immediately disconnected after registration
  4. Later execution attempted to use the already-disconnected client → Error
// ❌ Before
async function registerServerTools(...) {
  const client = new MCPClient(serverConfig);

  await client.connect();              // Connect
  const tools = await client.listTools();

  for (const tool of tools) {
    toolCmd.action(async (options) => {
      await executeToolCommand(client, tool.name, options);  // Uses disconnected client
    });
  }

  await client.disconnect();  // Immediately disconnect!
}

Solution

Implemented LazyToolLoader pattern with tool caching:

1. Added ToolCache class

  • Caches tool definitions to ~/.mcp-cli-cache.json
  • 24-hour TTL (configurable)
  • Avoids server connection during CLI startup

2. Modified registerServerTools

  • Uses cached tools (no connection needed)
  • Creates fresh client at execution time
  • Connects → Executes → Disconnects properly
// ✅ After
async function registerServerTools(...) {
  // Get tools from cache (no connection!)
  const tools = await toolCache.getOrFetch(serverName, serverConfig);

  for (const tool of tools) {
    toolCmd.action(async (options) => {
      // Create fresh client at execution time
      const client = new MCPClient(serverConfig);

      try {
        await client.connect();
        await executeToolCommand(client, tool.name, options);
        await client.disconnect();
      } catch (error) {
        await client.disconnect();
        throw error;
      }
    });
  }

  // No disconnect here!
}

3. Added cache management commands

mcp-cli refresh <server>   # Refresh cache for specific server
mcp-cli clear-cache        # Clear all caches

Performance Improvements

Metric Before After Improvement
Execution time 0.983s 0.475s 2.1x faster
CPU usage 0.370s 0.037s 10x less
Server load Every startup Cached Reduced

Testing

Tested with 3 MCP servers in my environment:

Basic functionality

# First run (cache generation)
$ mcp-cli sqlite_tiktok list_tables
Fetching tools for sqlite_tiktok...
[18 tables listed]

# Second run (cache usage)
$ mcp-cli sqlite_tiktok list_tables
Using cached tools for sqlite_tiktok
[18 tables listed]

Query execution

$ mcp-cli sqlite_tiktok read_query --query "SELECT * FROM master_tiktok LIMIT 3"
Using cached tools for sqlite_tiktok
[3 rows returned - success!]

Cache management

$ mcp-cli refresh sqlite_tiktok
Cache cleared for 'sqlite_tiktok'. Tools will be fetched on next use.

$ mcp-cli clear-cache
All tool caches cleared.

All tests passed

  • sqlite_tiktok: 18 tables, all queries successful
  • sqlite_instagram: 30 tables, all queries successful
  • sqlite_dashboard: 10 tables, all queries successful

Related Work

This pattern is inspired by:


Files Changed

  • src/client/ToolCache.ts - New file (90 lines)
  • src/cli/index.ts - Modified (59 lines changed)

Thank you for reviewing! Let me know if you'd like any changes. 🙏

…zed" error

- Add ToolCache class for 24-hour TTL tool caching (~/.mcp-cli-cache.json)
- Modify registerServerTools to use lazy connection pattern
- Add cache management commands (refresh, clear-cache)
- Update help text to include new cache commands

This fixes the "Client not initialized" error that occurred because
the client was connecting during tool registration, then immediately
disconnecting before tools were actually executed. The lazy loading
pattern defers client connection until tool execution time.

Performance improvements:
- 2.1x faster execution (0.983s → 0.475s)
- 10x less CPU usage (0.370s → 0.037s user time)
- Reduced server load (cached tool discovery)

Tested with multiple MCP servers:
- sqlite_tiktok: 18 tables, all queries successful
- sqlite_instagram: 30 tables, all queries successful
- sqlite_dashboard: 10 tables, all queries successful

Related pattern: Anthropic Tool Search with Embeddings
https://github.com/anthropics/anthropic-cookbook/blob/main/tool_use/tool_search_with_embeddings.ipynb
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.

1 participant