clay-server 3.0.0-beta.2 → 3.0.0

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.
@@ -698,7 +698,9 @@ function attachHTTP(ctx) {
698
698
  res.end('{"error":"MCP bridge not configured"}');
699
699
  return;
700
700
  }
701
- var handler = getMcpBridgeHandler();
701
+ var sessionId = Number(body.sessionId);
702
+ if (!Number.isInteger(sessionId)) sessionId = null;
703
+ var handler = getMcpBridgeHandler(sessionId, body.pairOnly === true);
702
704
  if (!handler) {
703
705
  res.writeHead(500, { "Content-Type": "application/json" });
704
706
  res.end('{"error":"MCP bridge handler unavailable"}');
@@ -299,7 +299,7 @@ function attachSessionPair(ctx) {
299
299
  function getSystemPrompt(session) {
300
300
  var group = store.groupForMember(session.localId);
301
301
  if (!group || !group.pair || group.pair.driverId !== session.localId) return "";
302
- return "You are the Driver in a two-agent pair. Plan the work, delegate concrete bounded tasks with send_to_partner, inspect results with read_partner, then integrate and verify the final outcome. Do not delegate work that must be performed sequentially in this same session.";
302
+ return "You are the Driver in a two-agent pair. The tools send_to_partner and read_partner are provided directly to you. Use send_to_partner to delegate concrete bounded work to the Worker, use read_partner to inspect its progress or recent results, then integrate and verify the final outcome. Do not search the project for implementations of these tools, and do not delegate work that must be performed sequentially in this same session.";
303
303
  }
304
304
 
305
305
  return { getToolDefs: getToolDefs, handleMessage: handleMessage, getSystemPrompt: getSystemPrompt };
package/lib/project.js CHANGED
@@ -419,11 +419,18 @@ function createProjectContext(opts) {
419
419
  sm.availableVendors = Object.keys(adapters);
420
420
  sm.defaultVendor = defaultVendor;
421
421
 
422
+ var sdk = null;
422
423
  var _splitGroups = attachSplitGroups({
423
424
  sm: sm,
424
425
  clients: clients,
425
426
  sendTo: sendTo,
426
427
  usersModule: usersModule,
428
+ onPairChanged: function (group) {
429
+ if (!sdk || !group || !Array.isArray(group.members)) return;
430
+ for (var i = 0; i < group.members.length; i++) {
431
+ sdk.refreshSessionRuntime(sm.sessions.get(group.members[i]));
432
+ }
433
+ },
427
434
  });
428
435
 
429
436
  var _projMode = typeof opts.onGetProjectDefaultMode === "function" ? opts.onGetProjectDefaultMode(slug) : null;
@@ -487,7 +494,6 @@ function createProjectContext(opts) {
487
494
 
488
495
  // The SDK bridge is created after local MCP servers. Session spawning uses
489
496
  // a getter so tool handlers see the initialized bridge when they run.
490
- var sdk = null;
491
497
  var _sessionPair = attachSessionPair({
492
498
  sm: sm,
493
499
  isMate: isMate,
@@ -764,6 +770,7 @@ function createProjectContext(opts) {
764
770
  return false;
765
771
  },
766
772
  getSessionSystemPrompt: function (session) { return _sessionPair.getSystemPrompt(session); },
773
+ getSessionToolDefs: function (session) { return _sessionPair.getToolDefs(session); },
767
774
  });
768
775
 
769
776
  // --- Loop engine (delegated to project-loop.js) ---
@@ -1365,10 +1372,12 @@ function createProjectContext(opts) {
1365
1372
  FS_MAX_SIZE: FS_MAX_SIZE,
1366
1373
  });
1367
1374
 
1368
- // --- MCP bridge handler for Codex (Track 2) ---
1375
+ // --- MCP bridge handler for Codex and session-bound Kiro tools ---
1369
1376
  // Provides list_tools and call_tool operations over HTTP for mcp-bridge-server.js.
1370
- // Excludes local MCP servers since Codex manages those natively via Track 1.
1371
- function getMcpBridgeHandler() {
1377
+ // The normal Codex bridge excludes local MCP servers it manages natively;
1378
+ // Kiro's pair-only bridge exposes only the tools bound to its Clay session.
1379
+ function getMcpBridgeHandler(sessionId, pairOnly) {
1380
+ var boundSession = Number.isInteger(sessionId) ? sm.sessions.get(sessionId) : null;
1372
1381
  // Build set of local MCP server names to exclude (Codex handles these natively)
1373
1382
  var localMcpNames = {};
1374
1383
  try {
@@ -1405,6 +1414,20 @@ function createProjectContext(opts) {
1405
1414
  }
1406
1415
  }
1407
1416
 
1417
+ if (boundSession) {
1418
+ var pairTools = _sessionPair.getToolDefs(boundSession);
1419
+ for (var pti = 0; pti < pairTools.length; pti++) {
1420
+ tools.push({
1421
+ server: "clay-sessions",
1422
+ name: pairTools[pti].name,
1423
+ description: pairTools[pti].description || pairTools[pti].name,
1424
+ inputSchema: pairTools[pti].inputSchema || { type: "object", properties: {} },
1425
+ });
1426
+ }
1427
+ }
1428
+
1429
+ if (pairOnly) return Promise.resolve(tools);
1430
+
1408
1431
  // In-app MCP servers (debate, browser, email).
1409
1432
  // Use getLocalMcpServers() so clay-browser is hidden unless the
1410
1433
  // Chrome extension is currently connected (see issue #325).
@@ -1430,6 +1453,15 @@ function createProjectContext(opts) {
1430
1453
  return Promise.resolve(tools);
1431
1454
  },
1432
1455
  callTool: function (serverName, toolName, args) {
1456
+ if (boundSession && serverName === "clay-sessions") {
1457
+ var pairTools = _sessionPair.getToolDefs(boundSession);
1458
+ for (var pti = 0; pti < pairTools.length; pti++) {
1459
+ if (pairTools[pti].name === toolName && typeof pairTools[pti].handler === "function") {
1460
+ return Promise.resolve(pairTools[pti].handler(args || {}));
1461
+ }
1462
+ }
1463
+ }
1464
+ if (pairOnly) return Promise.reject(new Error("Pair tool not found: " + toolName));
1433
1465
  // Try in-app servers first (gated by extension connectivity for clay-browser).
1434
1466
  var localMcp = getLocalMcpServers();
1435
1467
  if (localMcp && localMcp[serverName]) {
package/lib/sdk-bridge.js CHANGED
@@ -124,10 +124,31 @@ function createSDKBridge(opts) {
124
124
  var clayPort = opts.clayPort || 2633;
125
125
  var clayTls = opts.clayTls || false;
126
126
  var clayAuthToken = opts.clayAuthToken || null;
127
+ var getSessionToolDefs = opts.getSessionToolDefs || function () { return []; };
127
128
  var onProcessingChanged = opts.onProcessingChanged || function () {};
128
129
  var _cachedFreshAuthState = null;
129
130
  var _cachedFreshAuthAt = 0;
130
131
 
132
+ function getKiroMcpServers(session) {
133
+ if (!session) return [];
134
+ var bridgeArgs = [
135
+ path.join(__dirname, "yoke", "mcp-bridge-server.js"),
136
+ "--port", String(clayPort),
137
+ "--slug", slug,
138
+ "--session", String(session.localId),
139
+ "--pair-only",
140
+ ];
141
+ if (clayTls) bridgeArgs.push("--tls");
142
+ var env = [];
143
+ if (clayAuthToken) env.push({ name: "CLAY_AUTH_TOKEN", value: clayAuthToken });
144
+ return [{
145
+ name: "clay-tools",
146
+ command: process.execPath,
147
+ args: bridgeArgs,
148
+ env: env,
149
+ }];
150
+ }
151
+
131
152
  function getFreshAuthState(force) {
132
153
  var now = Date.now();
133
154
  if (!force && _cachedFreshAuthState && now - _cachedFreshAuthAt < 15000) {
@@ -766,6 +787,7 @@ function createSDKBridge(opts) {
766
787
  // not resources from a newer query that may have been created after an abort.
767
788
  var myQueryInstance = session.queryInstance;
768
789
  var myAbortController = session.abortController;
790
+ var myMessageQueue = session.messageQueue;
769
791
  console.log("[sdk-bridge] processQueryStream: starting for-await loop, vendor=" + (session.vendor || adapter.vendor));
770
792
  try {
771
793
  for await (var msg of myQueryInstance) {
@@ -926,7 +948,7 @@ function createSDKBridge(opts) {
926
948
  } catch (e) {}
927
949
  session.queryInstance = null;
928
950
  }
929
- session.messageQueue = null;
951
+ if (session.messageQueue === myMessageQueue) session.messageQueue = null;
930
952
  if (session.abortController === myAbortController) session.abortController = null;
931
953
  session.taskStopRequested = false;
932
954
  session.pendingPermissions = {};
@@ -1373,6 +1395,7 @@ function createSDKBridge(opts) {
1373
1395
  var codexConfig = getCodexConfig(sm);
1374
1396
  var kiroConfig = getKiroConfig(sm);
1375
1397
  var mergedMcpServers = mergeMcpServers(getMcpServers(session), getRemoteMcpServers) || undefined;
1398
+ var sessionToolDefs = getSessionToolDefs(session) || [];
1376
1399
 
1377
1400
  // Derive an explicit session title for fresh queries so the SDK records
1378
1401
  // it at session creation and skips its own auto-generation. This also
@@ -1416,6 +1439,15 @@ function createSDKBridge(opts) {
1416
1439
  title: initialTitle || undefined,
1417
1440
  toolServers: mergedMcpServers,
1418
1441
  toolServerDescriptors: extractMcpDescriptors(mergedMcpServers) || undefined,
1442
+ dynamicTools: sessionToolDefs,
1443
+ callDynamicTool: function (toolName, args) {
1444
+ for (var dti = 0; dti < sessionToolDefs.length; dti++) {
1445
+ if (sessionToolDefs[dti].name === toolName && typeof sessionToolDefs[dti].handler === "function") {
1446
+ return sessionToolDefs[dti].handler(args || {});
1447
+ }
1448
+ }
1449
+ return Promise.reject(new Error("Session tool not found: " + toolName));
1450
+ },
1419
1451
  resumeSessionId: session.cliSessionId || undefined,
1420
1452
  abortController: linuxUser ? undefined : session.abortController,
1421
1453
  canUseTool: function(toolName, input, toolOpts) {
@@ -1444,10 +1476,10 @@ function createSDKBridge(opts) {
1444
1476
  KIRO: {
1445
1477
  engine: kiroConfig.engine,
1446
1478
  mode: kiroConfig.mode,
1447
- // Kiro reads its native ~/.kiro/settings/mcp.json. Clay-managed MCP
1448
- // servers are intentionally not forwarded until the ACP entry shape
1449
- // and duplicate-registration behavior are verified against CLI 3.x.
1450
- mcpServers: [],
1479
+ // ACP accepts additional stdio MCP servers per session. The bridge is
1480
+ // bound to this Clay session so partner calls cannot leak across
1481
+ // concurrent Kiro sessions or users.
1482
+ mcpServers: getKiroMcpServers(session),
1451
1483
  },
1452
1484
  },
1453
1485
  };
@@ -1543,6 +1575,17 @@ function createSDKBridge(opts) {
1543
1575
  // the processing indicator without ever answering until a second send.
1544
1576
  function pushMessage(session, text, images) {
1545
1577
  session.lastActivityAt = Date.now();
1578
+ if (session._runtimeRefreshRequested && !session._queryStarting) {
1579
+ session._runtimeRefreshRequested = false;
1580
+ var staleQuery = session.queryInstance;
1581
+ if (staleQuery && typeof staleQuery.close === "function") {
1582
+ try { staleQuery.close(); } catch (e) {}
1583
+ }
1584
+ session.queryInstance = null;
1585
+ session.abortController = null;
1586
+ session.messageQueue = null;
1587
+ return false;
1588
+ }
1546
1589
  // Route through QueryHandle (works for both in-process and worker paths)
1547
1590
  if (session.queryInstance && typeof session.queryInstance.pushMessage === "function") {
1548
1591
  session.queryInstance.pushMessage(text, images);
@@ -1556,6 +1599,22 @@ function createSDKBridge(opts) {
1556
1599
  return false;
1557
1600
  }
1558
1601
 
1602
+ function refreshSessionRuntime(session) {
1603
+ if (!session) return false;
1604
+ session._runtimeRefreshRequested = true;
1605
+ if (!session.isProcessing && !session._queryStarting) {
1606
+ var staleQuery = session.queryInstance;
1607
+ if (staleQuery && typeof staleQuery.close === "function") {
1608
+ try { staleQuery.close(); } catch (e) {}
1609
+ }
1610
+ session.queryInstance = null;
1611
+ session.abortController = null;
1612
+ session.messageQueue = null;
1613
+ session._runtimeRefreshRequested = false;
1614
+ }
1615
+ return true;
1616
+ }
1617
+
1559
1618
  function permissionPushTitle(toolName, input) {
1560
1619
  if (!input) return "Claude wants to use " + toolName;
1561
1620
  var file = input.file_path ? input.file_path.split(/[/\\]/).pop() : "";
@@ -2010,6 +2069,7 @@ function createSDKBridge(opts) {
2010
2069
  forkSession: forkSessionUnified,
2011
2070
  startQuery: startQuery,
2012
2071
  pushMessage: pushMessage,
2072
+ refreshSessionRuntime: refreshSessionRuntime,
2013
2073
  setModel: setModel,
2014
2074
  setEffort: setEffort,
2015
2075
  setPermissionMode: setPermissionMode,
@@ -16,6 +16,7 @@ function createSplitGroupStore(opts) {
16
16
  var groupsFile = path.join(opts.sessionsDir, "split-groups.json");
17
17
  var usersModule = opts.usersModule;
18
18
  var broadcast = opts.broadcast || function () {};
19
+ var onPairChanged = opts.onPairChanged || function () {};
19
20
  var groups = [];
20
21
 
21
22
  // localIds are reassigned on every daemon restart (loadSessions renumbers
@@ -174,6 +175,7 @@ function createSplitGroupStore(opts) {
174
175
  groups.push(group);
175
176
  save();
176
177
  broadcast();
178
+ if (group.pair) onPairChanged(group);
177
179
  return { ok: true, group: group };
178
180
  }
179
181
 
@@ -203,6 +205,7 @@ function createSplitGroupStore(opts) {
203
205
  delete group.pairCliIds;
204
206
  save();
205
207
  broadcast();
208
+ onPairChanged(group);
206
209
  return { ok: true, group: group };
207
210
  }
208
211
  if (!Number.isInteger(msg.driverId) || group.members.indexOf(msg.driverId) === -1) {
@@ -212,6 +215,7 @@ function createSplitGroupStore(opts) {
212
215
  group.pair = { driverId: msg.driverId, workerId: workerId };
213
216
  save();
214
217
  broadcast();
218
+ onPairChanged(group);
215
219
  return { ok: true, group: group };
216
220
  }
217
221
 
@@ -264,6 +268,7 @@ function attachSplitGroups(ctx) {
264
268
  sessionsDir: ctx.sm.sessionsDir,
265
269
  usersModule: ctx.usersModule,
266
270
  broadcast: broadcast,
271
+ onPairChanged: ctx.onPairChanged,
267
272
  });
268
273
 
269
274
  function sendState(ws) {
@@ -515,6 +515,42 @@ function flattenEvent(notification, state) {
515
515
  return events;
516
516
  }
517
517
 
518
+ // Dynamic tools are supplied by Clay for a specific thread, such as the
519
+ // Driver's pair-session controls.
520
+ if (item.type === "dynamicToolCall" || item.type === "dynamic_tool_call") {
521
+ if (!state.toolBlocks[item.id]) {
522
+ state.blockCounter++;
523
+ state.toolBlocks[item.id] = "blk_" + state.blockCounter;
524
+ var dynamicBlockId = state.toolBlocks[item.id];
525
+ events.push({
526
+ yokeType: "tool_start",
527
+ blockId: dynamicBlockId,
528
+ toolId: item.id,
529
+ toolName: item.tool || "session_tool",
530
+ });
531
+ events.push({
532
+ yokeType: "tool_executing",
533
+ blockId: dynamicBlockId,
534
+ toolId: item.id,
535
+ toolName: item.tool || "session_tool",
536
+ input: item.arguments || {},
537
+ });
538
+ }
539
+ if (evtPhase === "completed") {
540
+ var dynamicText = Array.isArray(item.contentItems) ? item.contentItems.map(function (contentItem) {
541
+ return contentItem && contentItem.text ? contentItem.text : "";
542
+ }).join("\n") : "";
543
+ events.push({
544
+ yokeType: "tool_result",
545
+ toolId: item.id,
546
+ blockId: state.toolBlocks[item.id],
547
+ content: dynamicText,
548
+ isError: item.success === false || item.status === "failed",
549
+ });
550
+ }
551
+ return events;
552
+ }
553
+
518
554
  // Web search
519
555
  if (item.type === "webSearch" || item.type === "web_search") {
520
556
  if (!state.toolBlocks[item.id]) {
@@ -629,8 +665,10 @@ function createEventState(model) {
629
665
 
630
666
  function createCodexQueryHandle(appServer, queryOpts) {
631
667
  var abortController = queryOpts.abortController;
632
- var systemPrompt = queryOpts.systemPrompt || queryOpts.appendSystemPrompt || "";
668
+ var promptParts = [queryOpts.systemPrompt, queryOpts.appendSystemPrompt].filter(function (part) { return !!part; });
669
+ var systemPrompt = promptParts.join("\n\n");
633
670
  var canUseTool = queryOpts.canUseTool || null;
671
+ var callDynamicTool = queryOpts.callDynamicTool || null;
634
672
  var onElicitation = queryOpts.onElicitation || null;
635
673
  var onFinished = queryOpts.onFinished || null;
636
674
 
@@ -764,6 +802,35 @@ function createCodexQueryHandle(appServer, queryOpts) {
764
802
  return;
765
803
  }
766
804
 
805
+ // Session-bound tools supplied at thread creation. Codex sends these as a
806
+ // server request and waits for the client to execute and answer it.
807
+ if (method === "item/tool/call") {
808
+ var dynamicParams = msg.params || {};
809
+ if (!callDynamicTool) {
810
+ appServer.respond(msg.id, {
811
+ contentItems: [{ type: "inputText", text: "Session tool handler is unavailable" }],
812
+ success: false,
813
+ });
814
+ return;
815
+ }
816
+ Promise.resolve(callDynamicTool(dynamicParams.tool, dynamicParams.arguments || {})).then(function (result) {
817
+ var content = result && Array.isArray(result.content) ? result.content : [];
818
+ var contentItems = content.map(function (item) {
819
+ return { type: "inputText", text: item && item.text ? item.text : JSON.stringify(item) };
820
+ });
821
+ if (contentItems.length === 0) {
822
+ contentItems.push({ type: "inputText", text: typeof result === "string" ? result : JSON.stringify(result) });
823
+ }
824
+ appServer.respond(msg.id, { contentItems: contentItems, success: !(result && result.isError) });
825
+ }).catch(function (err) {
826
+ appServer.respond(msg.id, {
827
+ contentItems: [{ type: "inputText", text: "Error: " + (err.message || String(err)) }],
828
+ success: false,
829
+ });
830
+ });
831
+ return;
832
+ }
833
+
767
834
  // MCP tool approval / elicitation (app-server uses mcpServer/elicitation/request)
768
835
  if (method === "item/tool/requestUserInput" || method === "mcpServer/elicitation/request") {
769
836
  var mcpParams = msg.params || {};
@@ -890,6 +957,16 @@ function createCodexQueryHandle(appServer, queryOpts) {
890
957
  if (queryOpts.webSearchMode) {
891
958
  threadParams.webSearchMode = queryOpts.webSearchMode;
892
959
  }
960
+ if (Array.isArray(queryOpts.dynamicTools) && queryOpts.dynamicTools.length > 0) {
961
+ threadParams.dynamicTools = queryOpts.dynamicTools.map(function (tool) {
962
+ return {
963
+ type: "function",
964
+ name: tool.name,
965
+ description: tool.description || tool.name,
966
+ inputSchema: tool.inputSchema || { type: "object", properties: {} },
967
+ };
968
+ });
969
+ }
893
970
 
894
971
  var threadResult;
895
972
  if (queryOpts.resumeSessionId) {
@@ -1479,6 +1556,9 @@ function createCodexAdapter(opts) {
1479
1556
  model: model,
1480
1557
  cwd: queryOpts.cwd || _cwd,
1481
1558
  systemPrompt: queryOpts.systemPrompt || "",
1559
+ appendSystemPrompt: queryOpts.appendSystemPrompt || "",
1560
+ dynamicTools: queryOpts.dynamicTools || [],
1561
+ callDynamicTool: queryOpts.callDynamicTool || null,
1482
1562
  abortController: ac,
1483
1563
  canUseTool: queryOpts.canUseTool || null,
1484
1564
  onElicitation: queryOpts.onElicitation || null,
@@ -397,7 +397,8 @@ function createEventState(opts) {
397
397
 
398
398
  function createKiroQueryHandle(acp, queryOpts) {
399
399
  var abortController = queryOpts.abortController;
400
- var systemPrompt = queryOpts.systemPrompt || queryOpts.appendSystemPrompt || "";
400
+ var promptParts = [queryOpts.systemPrompt, queryOpts.appendSystemPrompt].filter(function (part) { return !!part; });
401
+ var systemPrompt = promptParts.join("\n\n");
401
402
  var canUseTool = queryOpts.canUseTool || null;
402
403
  var onFinished = queryOpts.onFinished || null;
403
404
 
@@ -1071,7 +1072,8 @@ function createKiroAdapter(opts) {
1071
1072
  contextWindow: _modelContextWindows[model] || null,
1072
1073
  mode: kiroOpts.mode || null,
1073
1074
  cwd: queryOpts.cwd || _cwd,
1074
- systemPrompt: (queryOpts.systemPrompt || "") + (skillIndex ? "\n\n" + skillIndex : ""),
1075
+ systemPrompt: queryOpts.systemPrompt || "",
1076
+ appendSystemPrompt: [queryOpts.appendSystemPrompt, skillIndex].filter(function (part) { return !!part; }).join("\n\n"),
1075
1077
  skills: sharedSkills,
1076
1078
  abortController: ac,
1077
1079
  canUseTool: queryOpts.canUseTool || null,
@@ -1,20 +1,20 @@
1
1
  #!/usr/bin/env node
2
- // mcp-bridge-server.js - Stdio MCP bridge for Codex
2
+ // mcp-bridge-server.js - Stdio MCP bridge for Clay tools
3
3
  // --------------------------------------------------
4
- // Codex spawns this as a native MCP server via config.mcp_servers["clay-tools"].
4
+ // Codex or Kiro spawns this as a native MCP server.
5
5
  // It implements the MCP protocol on stdio (JSON-RPC) and proxies tool
6
6
  // list/call requests to Clay's project-scoped HTTP endpoint
7
7
  // (/p/{slug}/api/mcp-bridge) when a slug is provided.
8
8
  //
9
- // Usage: node mcp-bridge-server.js --port 2633 --slug my-project
9
+ // Usage: node mcp-bridge-server.js --port 2633 --slug my-project [--session 42] [--pair-only]
10
10
  //
11
11
  // Lifecycle:
12
- // 1. Codex spawns this process
13
- // 2. Codex sends MCP "initialize" request on stdin
12
+ // 1. The vendor CLI spawns this process
13
+ // 2. The vendor CLI sends MCP "initialize" request on stdin
14
14
  // 3. We respond, then fetch tool list from Clay
15
- // 4. Codex sends "tools/list" -> we return cached tools
16
- // 5. Codex sends "tools/call" -> we proxy to Clay and return result
17
- // 6. When stdin closes (Codex exits), we exit too
15
+ // 4. The CLI sends "tools/list" -> we return cached tools
16
+ // 5. The CLI sends "tools/call" -> we proxy to Clay and return result
17
+ // 6. When stdin closes, we exit too
18
18
 
19
19
  var http = require("http");
20
20
  var https = require("https");
@@ -24,6 +24,8 @@ var args = process.argv.slice(2);
24
24
  var clayPort = 2633;
25
25
  var claySlug = "";
26
26
  var clayTls = false;
27
+ var claySessionId = null;
28
+ var pairOnly = false;
27
29
 
28
30
  for (var i = 0; i < args.length; i++) {
29
31
  if (args[i] === "--port" && args[i + 1]) {
@@ -32,8 +34,14 @@ for (var i = 0; i < args.length; i++) {
32
34
  } else if (args[i] === "--slug" && args[i + 1]) {
33
35
  claySlug = args[i + 1];
34
36
  i++;
37
+ } else if (args[i] === "--session" && args[i + 1]) {
38
+ claySessionId = parseInt(args[i + 1], 10);
39
+ if (!Number.isInteger(claySessionId)) claySessionId = null;
40
+ i++;
35
41
  } else if (args[i] === "--tls") {
36
42
  clayTls = true;
43
+ } else if (args[i] === "--pair-only") {
44
+ pairOnly = true;
37
45
  }
38
46
  }
39
47
 
@@ -111,7 +119,7 @@ function postJson(urlPath, body) {
111
119
 
112
120
  // --- Fetch tools from Clay ---
113
121
  function fetchTools() {
114
- return postJson(CLAY_MCP_PATH, { action: "list_tools" }).then(function (resp) {
122
+ return postJson(CLAY_MCP_PATH, { action: "list_tools", sessionId: claySessionId, pairOnly: pairOnly }).then(function (resp) {
115
123
  if (resp.error) {
116
124
  log("Failed to fetch tools: " + resp.error);
117
125
  return [];
@@ -137,6 +145,8 @@ function callTool(serverName, toolName, args) {
137
145
  server: serverName,
138
146
  tool: toolName,
139
147
  args: args || {},
148
+ sessionId: claySessionId,
149
+ pairOnly: pairOnly,
140
150
  });
141
151
  }
142
152
 
@@ -190,8 +200,7 @@ function handleToolsList(id, params) {
190
200
  var mcpTools = _tools.map(function (t) {
191
201
  return {
192
202
  name: t.server + "__" + t.name,
193
- description: "[REMOTE MCP: " + t.server + "] " + (t.description || t.name)
194
- + " (This tool runs on a REMOTE machine connected via browser extension, NOT on the local server.)",
203
+ description: "[Clay: " + t.server + "] " + (t.description || t.name),
195
204
  inputSchema: t.inputSchema || { type: "object", properties: {} },
196
205
  };
197
206
  });
@@ -299,4 +308,4 @@ process.stdin.on("error", function () {
299
308
  process.on("SIGTERM", function () { process.exit(0); });
300
309
  process.on("SIGINT", function () { process.exit(0); });
301
310
 
302
- log("Started: port=" + clayPort + " slug=" + claySlug);
311
+ log("Started: port=" + clayPort + " slug=" + claySlug + " session=" + (claySessionId === null ? "none" : claySessionId) + " pairOnly=" + pairOnly);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "clay-server",
3
- "version": "3.0.0-beta.2",
3
+ "version": "3.0.0",
4
4
  "description": "Self-hosted team workspace for Claude Code and Codex. Multi-user, browser-based, with persistent AI mates.",
5
5
  "bin": {
6
6
  "clay-server": "./bin/cli.js",