Claw Web commited on
Commit
b423b58
Β·
1 Parent(s): 9427d4d

feat: MCP tools dynamic injection into agent TOOL_DEFINITIONS

Browse files

- agent.ts: Initialize MCP servers from .claw/settings.json config at start of agent loop
- agent.ts: Merge discovered MCP tools with static TOOL_DEFINITIONS for LLM requests
- agent.ts: Convert MCP tool format (name/description/input_schema) to OpenAI function calling format
- executor.ts: Route mcp__* prefixed tool calls through McpServerManager.callTool()
- Non-fatal: MCP init failure doesn't break agent, continues with static tools only
- Matches original claw-code behavior: dynamic tool list with MCP server discovery

Files changed (2) hide show
  1. server/runtime/agent.ts +39 -2
  2. server/tools/executor.ts +17 -0
server/runtime/agent.ts CHANGED
@@ -5,7 +5,7 @@
5
 
6
  import { ENV } from "../_core/env";
7
  import { buildSystemPrompt, TOOL_DEFINITIONS } from "./system-prompt";
8
- import { executeTool, getPlanMode, runPreToolHooks, runPostToolHooks } from "../tools/executor";
9
  // Compact is manual-only via /compact command (matches original claw-code)
10
  // import { shouldCompact, compactSession, dbMessagesToSession, DEFAULT_COMPACTION_CONFIG } from "./compact";
11
  import { UsageTracker, pricingForModel, defaultSonnetTierPricing, estimateCostUsdWithPricing, totalCostUsd, formatUsd, summaryLinesForModel } from "./usage";
@@ -259,6 +259,43 @@ export async function runAgentLoop(
259
  const assistantMessages: AgentMessage[] = [];
260
  const toolResultMessages: AgentMessage[] = [];
261
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
262
  sendSSE(res, "status", { status: "thinking", message: "Processing your request..." });
263
 
264
  while (iterations < MAX_ITERATIONS) {
@@ -279,7 +316,7 @@ export async function runAgentLoop(
279
  if (m.name) msg.name = m.name;
280
  return msg;
281
  }),
282
- tools: TOOL_DEFINITIONS,
283
  tool_choice: "auto",
284
  max_tokens: cfg.maxTokens,
285
  temperature: cfg.temperature,
 
5
 
6
  import { ENV } from "../_core/env";
7
  import { buildSystemPrompt, TOOL_DEFINITIONS } from "./system-prompt";
8
+ import { executeTool, getPlanMode, runPreToolHooks, runPostToolHooks, initializeMcpFromConfig, getMcpManager } from "../tools/executor";
9
  // Compact is manual-only via /compact command (matches original claw-code)
10
  // import { shouldCompact, compactSession, dbMessagesToSession, DEFAULT_COMPACTION_CONFIG } from "./compact";
11
  import { UsageTracker, pricingForModel, defaultSonnetTierPricing, estimateCostUsdWithPricing, totalCostUsd, formatUsd, summaryLinesForModel } from "./usage";
 
259
  const assistantMessages: AgentMessage[] = [];
260
  const toolResultMessages: AgentMessage[] = [];
261
 
262
+ // ─── MCP Tools Dynamic Injection (matches original claw-code) ──────────
263
+ // Initialize MCP servers from config and merge discovered tools with static TOOL_DEFINITIONS.
264
+ // This is how the original claw-code dynamically builds the tool list:
265
+ // 1. Load MCP server configs from .claw/settings.json
266
+ // 2. Connect to each server via stdio JSON-RPC
267
+ // 3. Call tools/list to discover available tools
268
+ // 4. Prefix tool names as mcp__servername__toolname
269
+ // 5. Merge with static tool definitions
270
+ let allTools = [...TOOL_DEFINITIONS];
271
+ try {
272
+ const mcpTools = await initializeMcpFromConfig(workDir);
273
+ if (mcpTools.length > 0) {
274
+ const mcpManager = getMcpManager();
275
+ if (mcpManager) {
276
+ const mcpDefs = mcpManager.getToolDefinitions();
277
+ // Convert MCP tool format to OpenAI function calling format
278
+ const mcpToolDefs = mcpDefs.map((t) => ({
279
+ type: "function" as const,
280
+ function: {
281
+ name: t.name,
282
+ description: t.description,
283
+ parameters: t.input_schema || { type: "object", properties: {} },
284
+ },
285
+ }));
286
+ allTools = [...TOOL_DEFINITIONS, ...mcpToolDefs];
287
+ console.log(`[agent] MCP tools injected: ${mcpDefs.map((t) => t.name).join(", ")}`);
288
+ sendSSE(res, "status", {
289
+ status: "mcp_ready",
290
+ message: `MCP tools loaded: ${mcpDefs.length} tools from ${mcpManager.getConnectedServers().length} servers`,
291
+ });
292
+ }
293
+ }
294
+ } catch (err: any) {
295
+ console.error(`[agent] MCP initialization error (non-fatal):`, err.message);
296
+ // MCP init failure is non-fatal β€” agent continues with static tools only
297
+ }
298
+
299
  sendSSE(res, "status", { status: "thinking", message: "Processing your request..." });
300
 
301
  while (iterations < MAX_ITERATIONS) {
 
316
  if (m.name) msg.name = m.name;
317
  return msg;
318
  }),
319
+ tools: allTools,
320
  tool_choice: "auto",
321
  max_tokens: cfg.maxTokens,
322
  temperature: cfg.temperature,
server/tools/executor.ts CHANGED
@@ -557,6 +557,23 @@ export async function executeTool(
557
  case "RemoteTrigger": output = await executeRemoteTrigger(args); break;
558
  case "SyntheticOutput": output = executeSyntheticOutput(args); break;
559
  default:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
560
  output = `Unknown tool: ${toolName}`;
561
  return { output, isError: true, durationMs: Date.now() - start };
562
  }
 
557
  case "RemoteTrigger": output = await executeRemoteTrigger(args); break;
558
  case "SyntheticOutput": output = executeSyntheticOutput(args); break;
559
  default:
560
+ // ─── Dynamic MCP tool routing ─────────────────────────────────
561
+ // When MCP tools are injected into the LLM's tool list, the LLM
562
+ // will call them by their qualified name (mcp__servername__toolname).
563
+ // Route these calls through McpServerManager.callTool().
564
+ if (toolName.startsWith("mcp__") && mcpManager) {
565
+ try {
566
+ const result = await mcpManager.callTool(toolName, args);
567
+ const textParts = result.content
568
+ .filter((c: any) => c.type === "text" && c.text)
569
+ .map((c: any) => c.text);
570
+ output = textParts.join("\n") || JSON.stringify(result.content);
571
+ } catch (err: any) {
572
+ output = `MCP tool error: ${err.message}`;
573
+ return { output, isError: true, durationMs: Date.now() - start };
574
+ }
575
+ break;
576
+ }
577
  output = `Unknown tool: ${toolName}`;
578
  return { output, isError: true, durationMs: Date.now() - start };
579
  }