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.
- package/lib/project-http.js +3 -1
- package/lib/project-session-pair.js +1 -1
- package/lib/project.js +36 -4
- package/lib/sdk-bridge.js +65 -5
- package/lib/session-split-groups.js +5 -0
- package/lib/yoke/adapters/codex.js +81 -1
- package/lib/yoke/adapters/kiro.js +4 -2
- package/lib/yoke/mcp-bridge-server.js +21 -12
- package/package.json +1 -1
package/lib/project-http.js
CHANGED
|
@@ -698,7 +698,9 @@ function attachHTTP(ctx) {
|
|
|
698
698
|
res.end('{"error":"MCP bridge not configured"}');
|
|
699
699
|
return;
|
|
700
700
|
}
|
|
701
|
-
var
|
|
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.
|
|
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
|
|
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
|
-
//
|
|
1371
|
-
|
|
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
|
-
//
|
|
1448
|
-
//
|
|
1449
|
-
//
|
|
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
|
|
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
|
|
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:
|
|
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
|
|
2
|
+
// mcp-bridge-server.js - Stdio MCP bridge for Clay tools
|
|
3
3
|
// --------------------------------------------------
|
|
4
|
-
// Codex spawns this as a native MCP server
|
|
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.
|
|
13
|
-
// 2.
|
|
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.
|
|
16
|
-
// 5.
|
|
17
|
-
// 6. When stdin closes
|
|
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: "[
|
|
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