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 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
- if (_mergedMcp) queryOptions.mcpServers = _mergedMcp;
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";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "clay-server",
3
- "version": "2.29.0",
3
+ "version": "2.29.1",
4
4
  "description": "Self-hosted Claude Code in your browser. Multi-session, multi-user, push notifications.",
5
5
  "bin": {
6
6
  "clay-server": "./bin/cli.js",