Spaces:
Sleeping
Sleeping
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
- server/runtime/agent.ts +39 -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:
|
| 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 |
}
|