clay-server 2.29.0 → 2.29.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/sdk-bridge.js +72 -1
- package/lib/sdk-worker.js +114 -0
- package/package.json +1 -1
package/lib/sdk-bridge.js
CHANGED
|
@@ -10,6 +10,59 @@ var { splitShellSegments, attachSkillDiscovery } = require("./sdk-skill-discover
|
|
|
10
10
|
var { createMessageQueue } = require("./sdk-message-queue");
|
|
11
11
|
var { attachMessageProcessor } = require("./sdk-message-processor");
|
|
12
12
|
|
|
13
|
+
// Extract serializable tool descriptors from MCP server instances.
|
|
14
|
+
// Used for IPC to worker processes (McpSdkServerConfigWithInstance is not serializable).
|
|
15
|
+
function extractMcpDescriptors(mcpServers) {
|
|
16
|
+
if (!mcpServers) return null;
|
|
17
|
+
var toJSONSchema;
|
|
18
|
+
try { toJSONSchema = require("zod").toJSONSchema; } catch (e) { return null; }
|
|
19
|
+
var descriptors = [];
|
|
20
|
+
var names = Object.keys(mcpServers);
|
|
21
|
+
for (var i = 0; i < names.length; i++) {
|
|
22
|
+
var serverName = names[i];
|
|
23
|
+
var server = mcpServers[serverName];
|
|
24
|
+
if (!server || !server.instance || !server.instance._registeredTools) continue;
|
|
25
|
+
var tools = [];
|
|
26
|
+
var toolNames = Object.keys(server.instance._registeredTools);
|
|
27
|
+
for (var j = 0; j < toolNames.length; j++) {
|
|
28
|
+
var toolName = toolNames[j];
|
|
29
|
+
var toolDef = server.instance._registeredTools[toolName];
|
|
30
|
+
var inputSchema = { type: "object", properties: {} };
|
|
31
|
+
try {
|
|
32
|
+
if (toolDef.inputSchema) inputSchema = toJSONSchema(toolDef.inputSchema);
|
|
33
|
+
} catch (e) { /* fallback to empty schema */ }
|
|
34
|
+
tools.push({
|
|
35
|
+
name: toolName,
|
|
36
|
+
description: toolDef.description || toolName,
|
|
37
|
+
inputSchema: inputSchema,
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
if (tools.length > 0) descriptors.push({ serverName: serverName, tools: tools });
|
|
41
|
+
}
|
|
42
|
+
return descriptors.length > 0 ? descriptors : null;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// Call an MCP tool handler by server name and tool name.
|
|
46
|
+
// Returns a promise that resolves with the tool result.
|
|
47
|
+
function callMcpToolHandler(mcpServers, serverName, toolName, args) {
|
|
48
|
+
if (!mcpServers || !mcpServers[serverName]) {
|
|
49
|
+
return Promise.reject(new Error("MCP server not found: " + serverName));
|
|
50
|
+
}
|
|
51
|
+
var server = mcpServers[serverName];
|
|
52
|
+
if (!server.instance || !server.instance._registeredTools || !server.instance._registeredTools[toolName]) {
|
|
53
|
+
return Promise.reject(new Error("MCP tool not found: " + serverName + "/" + toolName));
|
|
54
|
+
}
|
|
55
|
+
var handler = server.instance._registeredTools[toolName].handler;
|
|
56
|
+
if (typeof handler !== "function") {
|
|
57
|
+
return Promise.reject(new Error("MCP tool handler not a function: " + serverName + "/" + toolName));
|
|
58
|
+
}
|
|
59
|
+
try {
|
|
60
|
+
return Promise.resolve(handler(args));
|
|
61
|
+
} catch (e) {
|
|
62
|
+
return Promise.reject(e);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
13
66
|
// Merge in-process MCP servers with remote (extension-bridged) MCP servers.
|
|
14
67
|
// Returns the merged object, or null if no servers exist.
|
|
15
68
|
function mergeMcpServers(localServers, getRemoteFn) {
|
|
@@ -579,8 +632,13 @@ function createSDKBridge(opts) {
|
|
|
579
632
|
agentProgressSummaries: true,
|
|
580
633
|
};
|
|
581
634
|
|
|
635
|
+
// MCP servers contain circular references (McpServer instances) and cannot
|
|
636
|
+
// be serialized for IPC. Instead, extract serializable tool descriptors and
|
|
637
|
+
// proxy tool calls back to the daemon via IPC.
|
|
582
638
|
var _mergedMcp = mergeMcpServers(mcpServers, getRemoteMcpServers);
|
|
583
|
-
|
|
639
|
+
var _mcpDescriptors = extractMcpDescriptors(_mergedMcp);
|
|
640
|
+
// Do NOT put _mergedMcp into queryOptions; the worker will reconstruct
|
|
641
|
+
// MCP servers from descriptors with IPC-proxied handlers.
|
|
584
642
|
|
|
585
643
|
// Per-loop settings override global defaults when present
|
|
586
644
|
var ls2 = session.loopSettings || {};
|
|
@@ -673,6 +731,18 @@ function createSDKBridge(opts) {
|
|
|
673
731
|
});
|
|
674
732
|
break;
|
|
675
733
|
|
|
734
|
+
case "mcp_tool_call":
|
|
735
|
+
// Worker is proxying an MCP tool call back to the daemon where
|
|
736
|
+
// the actual MCP server instances (with handlers) live.
|
|
737
|
+
callMcpToolHandler(_mergedMcp, msg.serverName, msg.toolName, msg.args)
|
|
738
|
+
.then(function(result) {
|
|
739
|
+
worker.send({ type: "mcp_tool_result", requestId: msg.requestId, result: result });
|
|
740
|
+
})
|
|
741
|
+
.catch(function(e) {
|
|
742
|
+
worker.send({ type: "mcp_tool_result", requestId: msg.requestId, error: e.message || String(e) });
|
|
743
|
+
});
|
|
744
|
+
break;
|
|
745
|
+
|
|
676
746
|
case "context_usage":
|
|
677
747
|
session.lastContextUsage = msg.data;
|
|
678
748
|
sendToSession(session, { type: "context_usage", data: msg.data });
|
|
@@ -859,6 +929,7 @@ function createSDKBridge(opts) {
|
|
|
859
929
|
type: "query_start",
|
|
860
930
|
prompt: initialMessage,
|
|
861
931
|
options: queryOptions,
|
|
932
|
+
mcpDescriptors: _mcpDescriptors,
|
|
862
933
|
singleTurn: !!session.singleTurn,
|
|
863
934
|
originalHome: require("./config").REAL_HOME || null,
|
|
864
935
|
projectPath: session.cwd || null,
|
package/lib/sdk-worker.js
CHANGED
|
@@ -31,6 +31,7 @@ var abortController = null;
|
|
|
31
31
|
var pendingPermissions = {}; // requestId -> resolve
|
|
32
32
|
var pendingAskUser = {}; // toolUseId -> resolve
|
|
33
33
|
var pendingElicitations = {}; // requestId -> resolve
|
|
34
|
+
var pendingMcpCalls = {}; // requestId -> { resolve, reject }
|
|
34
35
|
var conn = null;
|
|
35
36
|
var buffer = "";
|
|
36
37
|
|
|
@@ -127,6 +128,9 @@ function handleMessage(msg) {
|
|
|
127
128
|
case "elicitation_response":
|
|
128
129
|
handleElicitationResponse(msg);
|
|
129
130
|
break;
|
|
131
|
+
case "mcp_tool_result":
|
|
132
|
+
handleMcpToolResult(msg);
|
|
133
|
+
break;
|
|
130
134
|
case "warmup":
|
|
131
135
|
handleWarmup(msg);
|
|
132
136
|
break;
|
|
@@ -208,6 +212,107 @@ function handleElicitationResponse(msg) {
|
|
|
208
212
|
}
|
|
209
213
|
}
|
|
210
214
|
|
|
215
|
+
function handleMcpToolResult(msg) {
|
|
216
|
+
var pending = pendingMcpCalls[msg.requestId];
|
|
217
|
+
if (!pending) return;
|
|
218
|
+
delete pendingMcpCalls[msg.requestId];
|
|
219
|
+
if (msg.error) {
|
|
220
|
+
pending.reject(new Error(msg.error));
|
|
221
|
+
} else {
|
|
222
|
+
pending.resolve(msg.result);
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
// Reconstruct MCP servers from serializable descriptors with IPC-proxied handlers.
|
|
227
|
+
// Each tool call is forwarded to the daemon which has the real MCP server instances.
|
|
228
|
+
function buildMcpServersFromDescriptors(sdk, descriptors) {
|
|
229
|
+
if (!descriptors || descriptors.length === 0) return null;
|
|
230
|
+
var z;
|
|
231
|
+
try { z = require("zod").z; } catch (e) {
|
|
232
|
+
try { z = require("zod"); } catch (e2) {
|
|
233
|
+
console.error("[sdk-worker] Failed to load zod for MCP reconstruction:", e2.message);
|
|
234
|
+
return null;
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
var createSdkMcpServer = sdk.createSdkMcpServer;
|
|
238
|
+
var toolFn = sdk.tool;
|
|
239
|
+
if (!createSdkMcpServer || !toolFn) {
|
|
240
|
+
console.error("[sdk-worker] SDK missing createSdkMcpServer or tool helper");
|
|
241
|
+
return null;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
var servers = {};
|
|
245
|
+
for (var i = 0; i < descriptors.length; i++) {
|
|
246
|
+
var desc = descriptors[i];
|
|
247
|
+
var tools = [];
|
|
248
|
+
for (var j = 0; j < desc.tools.length; j++) {
|
|
249
|
+
var td = desc.tools[j];
|
|
250
|
+
var shape = buildZodShape(z, td.inputSchema);
|
|
251
|
+
tools.push(toolFn(
|
|
252
|
+
td.name,
|
|
253
|
+
td.description || td.name,
|
|
254
|
+
shape,
|
|
255
|
+
createMcpProxyHandler(desc.serverName, td.name)
|
|
256
|
+
));
|
|
257
|
+
}
|
|
258
|
+
if (tools.length > 0) {
|
|
259
|
+
servers[desc.serverName] = createSdkMcpServer({
|
|
260
|
+
name: desc.serverName,
|
|
261
|
+
version: "1.0.0",
|
|
262
|
+
tools: tools,
|
|
263
|
+
});
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
return Object.keys(servers).length > 0 ? servers : null;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
function createMcpProxyHandler(serverName, toolName) {
|
|
270
|
+
return function(args) {
|
|
271
|
+
return new Promise(function(resolve, reject) {
|
|
272
|
+
var requestId = "mcp_" + Date.now() + "_" + crypto.randomUUID().slice(0, 8);
|
|
273
|
+
pendingMcpCalls[requestId] = { resolve: resolve, reject: reject };
|
|
274
|
+
sendToDaemon({
|
|
275
|
+
type: "mcp_tool_call",
|
|
276
|
+
requestId: requestId,
|
|
277
|
+
serverName: serverName,
|
|
278
|
+
toolName: toolName,
|
|
279
|
+
args: args,
|
|
280
|
+
});
|
|
281
|
+
});
|
|
282
|
+
};
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
// Build a Zod shape from MCP JSON Schema inputSchema (mirrors project-mcp.js logic)
|
|
286
|
+
function buildZodShape(z, inputSchema) {
|
|
287
|
+
if (!inputSchema || !inputSchema.properties) return {};
|
|
288
|
+
var shape = {};
|
|
289
|
+
var props = inputSchema.properties;
|
|
290
|
+
var required = inputSchema.required || [];
|
|
291
|
+
var keys = Object.keys(props);
|
|
292
|
+
for (var i = 0; i < keys.length; i++) {
|
|
293
|
+
var k = keys[i];
|
|
294
|
+
var p = props[k];
|
|
295
|
+
var field;
|
|
296
|
+
if (p.type === "number" || p.type === "integer") {
|
|
297
|
+
field = z.number();
|
|
298
|
+
} else if (p.type === "boolean") {
|
|
299
|
+
field = z.boolean();
|
|
300
|
+
} else if (p.type === "array") {
|
|
301
|
+
field = z.array(z.any());
|
|
302
|
+
} else if (p.type === "object") {
|
|
303
|
+
field = z.record(z.any());
|
|
304
|
+
} else if (p.enum) {
|
|
305
|
+
field = z.enum(p.enum);
|
|
306
|
+
} else {
|
|
307
|
+
field = z.string();
|
|
308
|
+
}
|
|
309
|
+
if (p.description) field = field.describe(p.description);
|
|
310
|
+
if (required.indexOf(k) === -1) field = field.optional();
|
|
311
|
+
shape[k] = field;
|
|
312
|
+
}
|
|
313
|
+
return shape;
|
|
314
|
+
}
|
|
315
|
+
|
|
211
316
|
// --- Query handling ---
|
|
212
317
|
async function handleQueryStart(msg) {
|
|
213
318
|
var t0 = msg._perfT0 || Date.now();
|
|
@@ -233,8 +338,17 @@ async function handleQueryStart(msg) {
|
|
|
233
338
|
messageQueue.push(msg.prompt);
|
|
234
339
|
}
|
|
235
340
|
|
|
341
|
+
// Reconstruct MCP servers from serializable descriptors (IPC-proxied handlers)
|
|
342
|
+
if (msg.mcpDescriptors) {
|
|
343
|
+
var _mcpServers = buildMcpServersFromDescriptors(sdk, msg.mcpDescriptors);
|
|
344
|
+
if (_mcpServers) {
|
|
345
|
+
perf("MCP servers reconstructed from descriptors (" + Object.keys(_mcpServers).length + " servers)");
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
|
|
236
349
|
// Build query options (callbacks are local, everything else from daemon)
|
|
237
350
|
var options = msg.options || {};
|
|
351
|
+
if (_mcpServers) options.mcpServers = _mcpServers;
|
|
238
352
|
options.abortController = abortController;
|
|
239
353
|
options.debug = true;
|
|
240
354
|
options.debugFile = "/tmp/clay-cli-debug-" + process.pid + ".log";
|