clay-server 2.27.1 → 2.28.0-beta.2
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/daemon.js +21 -0
- package/lib/mcp-local.js +355 -0
- package/lib/project-connection.js +2 -0
- package/lib/project-loop.js +116 -34
- package/lib/project-mcp.js +371 -0
- package/lib/project-user-message.js +6 -3
- package/lib/project.js +51 -11
- package/lib/public/app.js +4 -0
- package/lib/public/css/filebrowser.css +204 -0
- package/lib/public/css/scheduler-modal.css +156 -1
- package/lib/public/css/scheduler.css +81 -0
- package/lib/public/index.html +99 -59
- package/lib/public/modules/app-loop-ui.js +85 -2
- package/lib/public/modules/app-messages.js +11 -1
- package/lib/public/modules/app-misc.js +104 -0
- package/lib/public/modules/mcp-ui.js +295 -0
- package/lib/public/modules/scheduler-config.js +241 -162
- package/lib/public/modules/scheduler-history.js +57 -5
- package/lib/public/modules/scheduler.js +80 -36
- package/lib/sdk-bridge.js +86 -17
- package/lib/server-mates.js +7 -2
- package/lib/server.js +6 -0
- package/lib/ws-schema.js +10 -0
- package/package.json +1 -1
|
@@ -7,8 +7,10 @@
|
|
|
7
7
|
|
|
8
8
|
import { iconHtml } from './icons.js';
|
|
9
9
|
import { showToast } from './utils.js';
|
|
10
|
+
import { renderModelList, renderModeList, renderEffortBar, renderThinkingBar } from './settings-defaults.js';
|
|
11
|
+
import { store } from './store.js';
|
|
10
12
|
import { initSchedulerConfig, setupCreateModal, openCreateModal, openCreateModalWithRecord, closeCreateModal, removePreview, getPreviewEl, showPreviewOnCell, showPreviewOnSlot, showPreviewForCreate, applyDraggedTask, parseCronSimple } from './scheduler-config.js';
|
|
11
|
-
import { initSchedulerHistory, renderHistory } from './scheduler-history.js';
|
|
13
|
+
import { initSchedulerHistory, renderHistory, _lastFiles as lastLoopFiles } from './scheduler-history.js';
|
|
12
14
|
export { handleLoopRegistryUpdated, handleLoopRegistryFiles, handleScheduleRunStarted, handleScheduleRunFinished, handleLoopScheduled } from './scheduler-history.js';
|
|
13
15
|
|
|
14
16
|
var ctx = null;
|
|
@@ -671,7 +673,7 @@ function renderDetail() {
|
|
|
671
673
|
html += '<div class="scheduler-detail-tabs">';
|
|
672
674
|
html += '<button class="scheduler-detail-tab active" data-tab="prompt">PROMPT.md</button>';
|
|
673
675
|
html += '<button class="scheduler-detail-tab" data-tab="judge">JUDGE.md</button>';
|
|
674
|
-
html += '<button class="scheduler-detail-tab" data-tab="
|
|
676
|
+
html += '<button class="scheduler-detail-tab" data-tab="model">Model</button>';
|
|
675
677
|
html += '</div>';
|
|
676
678
|
|
|
677
679
|
html += '<div class="scheduler-detail-body" id="scheduler-detail-body">';
|
|
@@ -738,41 +740,83 @@ function renderDetailBody(tab, rec) {
|
|
|
738
740
|
var bodyEl2 = document.getElementById("scheduler-detail-body");
|
|
739
741
|
if (!bodyEl2) return;
|
|
740
742
|
|
|
741
|
-
if (tab === "
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
var scheduleStr = isScheduled ? cronToHuman(rec.cron) : "One-off";
|
|
745
|
-
var statusStr = isScheduled ? (rec.enabled ? "Enabled" : "Paused") : "One-off";
|
|
746
|
-
var createdStr = rec.createdAt ? formatDateTime(new Date(rec.createdAt)) : "—";
|
|
747
|
-
var lastRunStr = "Never";
|
|
748
|
-
if (lastRun) {
|
|
749
|
-
var resultStr = lastRun.result || "?";
|
|
750
|
-
var iterStr = (lastRun.iterations || 0) + " iter";
|
|
751
|
-
lastRunStr = formatDateTime(new Date(lastRun.finishedAt || lastRun.startedAt)) + " — " + resultStr + " (" + iterStr + ")";
|
|
752
|
-
}
|
|
753
|
-
|
|
754
|
-
var html = '<div class="scheduler-detail-meta">';
|
|
755
|
-
html += '<span class="scheduler-detail-meta-label">Schedule</span>';
|
|
756
|
-
html += '<span class="scheduler-detail-meta-value">' + esc(scheduleStr) + '</span>';
|
|
757
|
-
html += '<span class="scheduler-detail-meta-label">Status</span>';
|
|
758
|
-
html += '<span class="scheduler-detail-meta-value">' + esc(statusStr) + '</span>';
|
|
759
|
-
html += '<span class="scheduler-detail-meta-label">Max Iterations</span>';
|
|
760
|
-
html += '<span class="scheduler-detail-meta-value">' + (rec.maxIterations || "—") + '</span>';
|
|
761
|
-
html += '<span class="scheduler-detail-meta-label">Created</span>';
|
|
762
|
-
html += '<span class="scheduler-detail-meta-value">' + esc(createdStr) + '</span>';
|
|
763
|
-
html += '<span class="scheduler-detail-meta-label">Last Run</span>';
|
|
764
|
-
html += '<span class="scheduler-detail-meta-value">' + esc(lastRunStr) + '</span>';
|
|
765
|
-
if (isScheduled && rec.nextRunAt) {
|
|
766
|
-
html += '<span class="scheduler-detail-meta-label">Next Run</span>';
|
|
767
|
-
html += '<span class="scheduler-detail-meta-value">' + esc(formatDateTime(new Date(rec.nextRunAt))) + '</span>';
|
|
768
|
-
}
|
|
769
|
-
html += '</div>';
|
|
770
|
-
bodyEl2.innerHTML = html;
|
|
771
|
-
} else {
|
|
772
|
-
// prompt or judge — request files from server
|
|
773
|
-
bodyEl2.innerHTML = '<div class="scheduler-detail-loading">Loading...</div>';
|
|
774
|
-
send({ type: "loop_registry_files", id: selectedTaskId });
|
|
743
|
+
if (tab === "model") {
|
|
744
|
+
renderModelTab(bodyEl2, rec);
|
|
745
|
+
return;
|
|
775
746
|
}
|
|
747
|
+
|
|
748
|
+
// prompt or judge — request files from server
|
|
749
|
+
bodyEl2.innerHTML = '<div class="scheduler-detail-loading">Loading...</div>';
|
|
750
|
+
send({ type: "loop_registry_files", id: selectedTaskId });
|
|
751
|
+
}
|
|
752
|
+
|
|
753
|
+
function renderModelTab(bodyEl, rec) {
|
|
754
|
+
var settings = lastLoopFiles.settings || {};
|
|
755
|
+
var loopFilesId = rec.linkedTaskId || rec.id;
|
|
756
|
+
|
|
757
|
+
bodyEl.innerHTML =
|
|
758
|
+
'<div class="scheduler-model-settings">' +
|
|
759
|
+
'<div class="settings-card"><div class="settings-field">' +
|
|
760
|
+
'<label class="settings-label">Model</label>' +
|
|
761
|
+
'<div class="settings-hint">Choose the Claude model for this task.</div>' +
|
|
762
|
+
'<div id="ls-model-list" class="settings-model-list"></div>' +
|
|
763
|
+
'</div></div>' +
|
|
764
|
+
'<div class="settings-card"><div class="settings-field">' +
|
|
765
|
+
'<label class="settings-label">Mode</label>' +
|
|
766
|
+
'<div class="settings-hint">Controls how Claude handles tool use and file edits.</div>' +
|
|
767
|
+
'<div id="ls-mode-list" class="settings-model-list"></div>' +
|
|
768
|
+
'</div></div>' +
|
|
769
|
+
'<div class="settings-card"><div class="settings-field">' +
|
|
770
|
+
'<label class="settings-label">Effort</label>' +
|
|
771
|
+
'<div class="settings-hint">Controls how much thinking effort Claude puts into responses.</div>' +
|
|
772
|
+
'<div class="settings-btn-group" id="ls-effort-bar"></div>' +
|
|
773
|
+
'</div></div>' +
|
|
774
|
+
'<div class="settings-card"><div class="settings-field">' +
|
|
775
|
+
'<label class="settings-label">Thinking</label>' +
|
|
776
|
+
'<div class="settings-hint">Controls whether Claude shows its reasoning process.</div>' +
|
|
777
|
+
'<div class="settings-btn-group" id="ls-thinking-bar"></div>' +
|
|
778
|
+
'<div id="ls-thinking-budget-row" class="settings-budget-row" style="display:none">' +
|
|
779
|
+
'<label class="settings-budget-label">Budget tokens</label>' +
|
|
780
|
+
'<input id="ls-thinking-budget" type="number" class="settings-budget-input" min="1024" max="128000" step="1024" value="10000">' +
|
|
781
|
+
'</div>' +
|
|
782
|
+
'</div></div>' +
|
|
783
|
+
'</div>';
|
|
784
|
+
|
|
785
|
+
function saveLoopSetting(key, value) {
|
|
786
|
+
var updated = Object.assign({}, settings);
|
|
787
|
+
updated[key] = value;
|
|
788
|
+
settings = updated;
|
|
789
|
+
send({ type: "loop_registry_save_files", id: loopFilesId, settings: updated });
|
|
790
|
+
}
|
|
791
|
+
|
|
792
|
+
var opts = {
|
|
793
|
+
models: store.getState().currentModels || [],
|
|
794
|
+
currentModel: settings.model || "",
|
|
795
|
+
currentMode: settings.permissionMode || "default",
|
|
796
|
+
currentEffort: settings.effort || "medium",
|
|
797
|
+
currentThinking: settings.thinking || "adaptive",
|
|
798
|
+
currentThinkingBudget: settings.thinkingBudget || 10000,
|
|
799
|
+
sendMsg: function (msgType, data) {
|
|
800
|
+
if (msgType === "set_model" || msgType === "loop_set_model") {
|
|
801
|
+
saveLoopSetting("model", data.model);
|
|
802
|
+
} else if (msgType === "loop_set_mode") {
|
|
803
|
+
saveLoopSetting("permissionMode", data.mode);
|
|
804
|
+
} else if (msgType === "loop_set_effort") {
|
|
805
|
+
saveLoopSetting("effort", data.effort);
|
|
806
|
+
} else if (msgType === "set_thinking") {
|
|
807
|
+
saveLoopSetting("thinking", data.thinking);
|
|
808
|
+
if (data.budgetTokens) saveLoopSetting("thinkingBudget", data.budgetTokens);
|
|
809
|
+
}
|
|
810
|
+
},
|
|
811
|
+
modelMsgType: "loop_set_model",
|
|
812
|
+
modeMsgType: "loop_set_mode",
|
|
813
|
+
effortMsgType: "loop_set_effort",
|
|
814
|
+
};
|
|
815
|
+
|
|
816
|
+
renderModelList("ls", opts);
|
|
817
|
+
renderModeList("ls", opts);
|
|
818
|
+
renderEffortBar("ls", opts);
|
|
819
|
+
renderThinkingBar("ls", opts);
|
|
776
820
|
}
|
|
777
821
|
|
|
778
822
|
// --- Chat reparenting ---
|
package/lib/sdk-bridge.js
CHANGED
|
@@ -10,6 +10,31 @@ 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
|
+
// Merge in-process MCP servers with remote (extension-bridged) MCP servers.
|
|
14
|
+
// Returns the merged object, or null if no servers exist.
|
|
15
|
+
function mergeMcpServers(localServers, getRemoteFn) {
|
|
16
|
+
var merged = {};
|
|
17
|
+
var hasAny = false;
|
|
18
|
+
if (localServers) {
|
|
19
|
+
var lk = Object.keys(localServers);
|
|
20
|
+
for (var i = 0; i < lk.length; i++) {
|
|
21
|
+
merged[lk[i]] = localServers[lk[i]];
|
|
22
|
+
hasAny = true;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
if (typeof getRemoteFn === "function") {
|
|
26
|
+
var remote = getRemoteFn();
|
|
27
|
+
if (remote) {
|
|
28
|
+
var rk = Object.keys(remote);
|
|
29
|
+
for (var j = 0; j < rk.length; j++) {
|
|
30
|
+
merged[rk[j]] = remote[rk[j]];
|
|
31
|
+
hasAny = true;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
return hasAny ? merged : null;
|
|
36
|
+
}
|
|
37
|
+
|
|
13
38
|
function createSDKBridge(opts) {
|
|
14
39
|
var cwd = opts.cwd;
|
|
15
40
|
var slug = opts.slug || "";
|
|
@@ -22,6 +47,7 @@ function createSDKBridge(opts) {
|
|
|
22
47
|
var isMate = opts.isMate || (slug.indexOf("mate-") === 0);
|
|
23
48
|
var dangerouslySkipPermissions = opts.dangerouslySkipPermissions || false;
|
|
24
49
|
var mcpServers = opts.mcpServers || null;
|
|
50
|
+
var getRemoteMcpServers = opts.getRemoteMcpServers || null;
|
|
25
51
|
var onProcessingChanged = opts.onProcessingChanged || function () {};
|
|
26
52
|
var onTurnDone = opts.onTurnDone || null;
|
|
27
53
|
|
|
@@ -553,20 +579,32 @@ function createSDKBridge(opts) {
|
|
|
553
579
|
agentProgressSummaries: true,
|
|
554
580
|
};
|
|
555
581
|
|
|
556
|
-
|
|
557
|
-
if (
|
|
558
|
-
|
|
582
|
+
var _mergedMcp = mergeMcpServers(mcpServers, getRemoteMcpServers);
|
|
583
|
+
if (_mergedMcp) queryOptions.mcpServers = _mergedMcp;
|
|
584
|
+
|
|
585
|
+
// Per-loop settings override global defaults when present
|
|
586
|
+
var ls2 = session.loopSettings || {};
|
|
587
|
+
|
|
588
|
+
if (ls2.model || sm.currentModel) queryOptions.model = ls2.model || sm.currentModel;
|
|
589
|
+
if (ls2.effort || sm.currentEffort) queryOptions.effort = ls2.effort || sm.currentEffort;
|
|
559
590
|
if (sm.currentBetas && sm.currentBetas.length > 0) queryOptions.betas = sm.currentBetas;
|
|
560
|
-
|
|
591
|
+
|
|
592
|
+
var thinkingMode2 = ls2.thinking || sm.currentThinking;
|
|
593
|
+
if (thinkingMode2 === "disabled") {
|
|
561
594
|
queryOptions.thinking = { type: "disabled" };
|
|
562
|
-
} else if (
|
|
563
|
-
|
|
595
|
+
} else if (thinkingMode2 === "budget") {
|
|
596
|
+
var budgetTokens2 = ls2.thinkingBudget || sm.currentThinkingBudget;
|
|
597
|
+
if (budgetTokens2) queryOptions.thinking = { type: "enabled", budgetTokens: budgetTokens2 };
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
if (ls2.disableAllHooks !== undefined) {
|
|
601
|
+
queryOptions.settings = Object.assign({}, queryOptions.settings || {}, { disableAllHooks: ls2.disableAllHooks });
|
|
564
602
|
}
|
|
565
603
|
|
|
566
604
|
if (dangerouslySkipPermissions) {
|
|
567
605
|
queryOptions.allowDangerouslySkipPermissions = true;
|
|
568
606
|
}
|
|
569
|
-
var modeToApply = session.acceptEditsAfterStart ? "acceptEdits" : sm.currentPermissionMode;
|
|
607
|
+
var modeToApply = ls2.permissionMode || (session.acceptEditsAfterStart ? "acceptEdits" : sm.currentPermissionMode);
|
|
570
608
|
if (session.acceptEditsAfterStart) delete session.acceptEditsAfterStart;
|
|
571
609
|
if (modeToApply && modeToApply !== "default") {
|
|
572
610
|
queryOptions.permissionMode = modeToApply;
|
|
@@ -959,6 +997,19 @@ function createSDKBridge(opts) {
|
|
|
959
997
|
return { behavior: "allow", updatedInput: input };
|
|
960
998
|
}
|
|
961
999
|
|
|
1000
|
+
// Auto-approve remote MCP tools that the user explicitly enabled in project settings.
|
|
1001
|
+
// These are user-owned local MCP servers, so no additional permission prompt needed.
|
|
1002
|
+
if (toolName.indexOf("mcp__") === 0 && getRemoteMcpServers) {
|
|
1003
|
+
var _rmcp = getRemoteMcpServers();
|
|
1004
|
+
if (_rmcp) {
|
|
1005
|
+
var _mcpParts = toolName.split("__");
|
|
1006
|
+
var _mcpServerName = _mcpParts.length >= 2 ? _mcpParts[1] : "";
|
|
1007
|
+
if (_rmcp[_mcpServerName]) {
|
|
1008
|
+
return { behavior: "allow", updatedInput: input };
|
|
1009
|
+
}
|
|
1010
|
+
}
|
|
1011
|
+
}
|
|
1012
|
+
|
|
962
1013
|
// Auto-approve safe Bash commands (read-only, non-destructive)
|
|
963
1014
|
// Applies to ALL sessions (mates and regular projects alike).
|
|
964
1015
|
// These are purely read-only commands that cannot modify files, install
|
|
@@ -1429,7 +1480,7 @@ function createSDKBridge(opts) {
|
|
|
1429
1480
|
abortController: session.abortController,
|
|
1430
1481
|
promptSuggestions: true,
|
|
1431
1482
|
agentProgressSummaries: true,
|
|
1432
|
-
mcpServers: mcpServers || undefined,
|
|
1483
|
+
mcpServers: mergeMcpServers(mcpServers, getRemoteMcpServers) || undefined,
|
|
1433
1484
|
canUseTool: function(toolName, input, toolOpts) {
|
|
1434
1485
|
return handleCanUseTool(session, toolName, input, toolOpts);
|
|
1435
1486
|
},
|
|
@@ -1438,29 +1489,44 @@ function createSDKBridge(opts) {
|
|
|
1438
1489
|
},
|
|
1439
1490
|
};
|
|
1440
1491
|
|
|
1441
|
-
|
|
1442
|
-
|
|
1492
|
+
// Per-loop settings override global defaults when present
|
|
1493
|
+
var ls = session.loopSettings || {};
|
|
1494
|
+
|
|
1495
|
+
if (ls.model || sm.currentModel) {
|
|
1496
|
+
queryOptions.model = ls.model || sm.currentModel;
|
|
1443
1497
|
}
|
|
1444
1498
|
|
|
1445
|
-
if (sm.currentEffort) {
|
|
1446
|
-
queryOptions.effort = sm.currentEffort;
|
|
1499
|
+
if (ls.effort || sm.currentEffort) {
|
|
1500
|
+
queryOptions.effort = ls.effort || sm.currentEffort;
|
|
1447
1501
|
}
|
|
1448
1502
|
|
|
1449
1503
|
if (sm.currentBetas && sm.currentBetas.length > 0) {
|
|
1450
1504
|
queryOptions.betas = sm.currentBetas;
|
|
1451
1505
|
}
|
|
1452
1506
|
|
|
1453
|
-
|
|
1507
|
+
var thinkingMode = ls.thinking || sm.currentThinking;
|
|
1508
|
+
if (thinkingMode === "disabled") {
|
|
1454
1509
|
queryOptions.thinking = { type: "disabled" };
|
|
1455
|
-
} else if (
|
|
1456
|
-
|
|
1510
|
+
} else if (thinkingMode === "budget") {
|
|
1511
|
+
var budgetTokens = ls.thinkingBudget || sm.currentThinkingBudget;
|
|
1512
|
+
if (budgetTokens) queryOptions.thinking = { type: "enabled", budgetTokens: budgetTokens };
|
|
1513
|
+
}
|
|
1514
|
+
|
|
1515
|
+
if (ls.permissionMode) {
|
|
1516
|
+
// Will be applied below, store for later
|
|
1517
|
+
session._loopPermissionMode = ls.permissionMode;
|
|
1518
|
+
}
|
|
1519
|
+
|
|
1520
|
+
// Pass through any extra SDK settings from LOOP.json
|
|
1521
|
+
if (ls.disableAllHooks !== undefined) {
|
|
1522
|
+
queryOptions.settings = Object.assign({}, queryOptions.settings || {}, { disableAllHooks: ls.disableAllHooks });
|
|
1457
1523
|
}
|
|
1458
1524
|
|
|
1459
1525
|
if (dangerouslySkipPermissions) {
|
|
1460
1526
|
queryOptions.allowDangerouslySkipPermissions = true;
|
|
1461
1527
|
}
|
|
1462
1528
|
// Pass permissionMode in queryOptions at creation time to avoid race condition
|
|
1463
|
-
var modeToApply = session.acceptEditsAfterStart ? "acceptEdits" : sm.currentPermissionMode;
|
|
1529
|
+
var modeToApply = session._loopPermissionMode || (session.acceptEditsAfterStart ? "acceptEdits" : sm.currentPermissionMode);
|
|
1464
1530
|
if (session.acceptEditsAfterStart) delete session.acceptEditsAfterStart;
|
|
1465
1531
|
if (modeToApply && modeToApply !== "default") {
|
|
1466
1532
|
queryOptions.permissionMode = modeToApply;
|
|
@@ -1763,7 +1829,10 @@ function createSDKBridge(opts) {
|
|
|
1763
1829
|
},
|
|
1764
1830
|
};
|
|
1765
1831
|
if (opts.model) mentionQueryOptions.model = opts.model;
|
|
1766
|
-
if (opts.includeMcpServers
|
|
1832
|
+
if (opts.includeMcpServers) {
|
|
1833
|
+
var _mentionMcp = mergeMcpServers(mcpServers, getRemoteMcpServers);
|
|
1834
|
+
if (_mentionMcp) mentionQueryOptions.mcpServers = _mentionMcp;
|
|
1835
|
+
}
|
|
1767
1836
|
query = sdk.query({
|
|
1768
1837
|
prompt: mq,
|
|
1769
1838
|
options: mentionQueryOptions,
|
package/lib/server-mates.js
CHANGED
|
@@ -65,8 +65,13 @@ function attachMates(ctx) {
|
|
|
65
65
|
// --- Mate message handlers ---
|
|
66
66
|
|
|
67
67
|
function handleMessage(ws, msg) {
|
|
68
|
-
|
|
69
|
-
|
|
68
|
+
var userId;
|
|
69
|
+
if (users.isMultiUser()) {
|
|
70
|
+
if (!ws._clayUser) return false;
|
|
71
|
+
userId = ws._clayUser.id;
|
|
72
|
+
} else {
|
|
73
|
+
userId = "default";
|
|
74
|
+
}
|
|
70
75
|
|
|
71
76
|
if (msg.type === "mate_create") {
|
|
72
77
|
if (!msg.seedData) return true;
|
package/lib/server.js
CHANGED
|
@@ -144,6 +144,8 @@ function createServer(opts) {
|
|
|
144
144
|
var onSetServerDefaultMode = opts.onSetServerDefaultMode || null;
|
|
145
145
|
var onGetProjectDefaultMode = opts.onGetProjectDefaultMode || null;
|
|
146
146
|
var onSetProjectDefaultMode = opts.onSetProjectDefaultMode || null;
|
|
147
|
+
var onGetProjectMcpServers = opts.onGetProjectMcpServers || null;
|
|
148
|
+
var onSetProjectMcpServers = opts.onSetProjectMcpServers || null;
|
|
147
149
|
var onGetDaemonConfig = opts.onGetDaemonConfig || null;
|
|
148
150
|
var onSetPin = opts.onSetPin || null;
|
|
149
151
|
var onSetKeepAwake = opts.onSetKeepAwake || null;
|
|
@@ -766,6 +768,8 @@ function createServer(opts) {
|
|
|
766
768
|
return origEmit.apply(ws, arguments);
|
|
767
769
|
};
|
|
768
770
|
ws._clayUser = wsUser; // attach user context
|
|
771
|
+
var remoteAddr = req.socket.remoteAddress || "";
|
|
772
|
+
ws._clayLocal = (remoteAddr === "127.0.0.1" || remoteAddr === "::1" || remoteAddr === "::ffff:127.0.0.1");
|
|
769
773
|
// Clear cross-project unread for this project when client connects
|
|
770
774
|
var unreadMap = getCrossProjectUnread(ws);
|
|
771
775
|
if (unreadMap[wsSlug]) {
|
|
@@ -984,6 +988,8 @@ function createServer(opts) {
|
|
|
984
988
|
onSetServerDefaultMode: onSetServerDefaultMode,
|
|
985
989
|
onGetProjectDefaultMode: onGetProjectDefaultMode,
|
|
986
990
|
onSetProjectDefaultMode: onSetProjectDefaultMode,
|
|
991
|
+
onGetProjectMcpServers: onGetProjectMcpServers,
|
|
992
|
+
onSetProjectMcpServers: onSetProjectMcpServers,
|
|
987
993
|
onGetDaemonConfig: onGetDaemonConfig,
|
|
988
994
|
onSetPin: onSetPin,
|
|
989
995
|
onSetKeepAwake: onSetKeepAwake,
|
package/lib/ws-schema.js
CHANGED
|
@@ -338,6 +338,16 @@ var schema = {
|
|
|
338
338
|
"clay_ext_result": { direction: "s2c", handler: "lib/public/modules/app-misc.js", description: "Browser extension command result (broadcast)" },
|
|
339
339
|
"clay_ext_command": { direction: "c2s", handler: "lib/public/modules/app-misc.js", description: "Send a command to the browser extension" },
|
|
340
340
|
|
|
341
|
+
// -----------------------------------------------------------------------
|
|
342
|
+
// MCP Bridge (remote MCP servers via Chrome Extension)
|
|
343
|
+
// -----------------------------------------------------------------------
|
|
344
|
+
"mcp_servers_available": { direction: "c2s", handler: "lib/project-mcp.js", description: "Report available MCP servers from Chrome Extension" },
|
|
345
|
+
"mcp_tool_call": { direction: "s2c", handler: "lib/public/modules/app-messages.js", description: "Forward MCP tool call to extension or HTTP endpoint" },
|
|
346
|
+
"mcp_tool_result": { direction: "c2s", handler: "lib/project-mcp.js", description: "Return MCP tool result from extension" },
|
|
347
|
+
"mcp_tool_error": { direction: "c2s", handler: "lib/project-mcp.js", description: "Return MCP tool error from extension" },
|
|
348
|
+
"mcp_toggle_server": { direction: "c2s", handler: "lib/project-mcp.js", description: "Toggle MCP server enabled state for this project" },
|
|
349
|
+
"mcp_servers_state": { direction: "s2c", handler: "lib/public/modules/app-messages.js", description: "Broadcast MCP server list and enabled state to clients" },
|
|
350
|
+
|
|
341
351
|
// -----------------------------------------------------------------------
|
|
342
352
|
// Skills
|
|
343
353
|
// -----------------------------------------------------------------------
|