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
|
@@ -0,0 +1,295 @@
|
|
|
1
|
+
// mcp-ui.js - MCP Servers modal (sidebar button + panel)
|
|
2
|
+
// Renders available MCP servers with per-project toggle checkboxes.
|
|
3
|
+
|
|
4
|
+
import { getWs } from './ws-ref.js';
|
|
5
|
+
import { refreshIcons } from './icons.js';
|
|
6
|
+
import { setHttpMcpServers } from './app-misc.js';
|
|
7
|
+
|
|
8
|
+
var modal = null;
|
|
9
|
+
var contentEl = null;
|
|
10
|
+
var _mcpServers = []; // { name, transport, toolCount, extensionEnabled, projectEnabled }
|
|
11
|
+
var _extensionConnected = false;
|
|
12
|
+
var _nativeHostConnected = false;
|
|
13
|
+
var _extensionId = null;
|
|
14
|
+
|
|
15
|
+
export function initMcp() {
|
|
16
|
+
modal = document.getElementById("mcp-modal");
|
|
17
|
+
contentEl = document.getElementById("mcp-content");
|
|
18
|
+
|
|
19
|
+
var btn = document.getElementById("mcp-btn");
|
|
20
|
+
var mateBtn = document.getElementById("mate-mcp-btn");
|
|
21
|
+
var closeBtn = document.getElementById("mcp-modal-close");
|
|
22
|
+
var backdrop = modal ? modal.querySelector(".confirm-backdrop") : null;
|
|
23
|
+
|
|
24
|
+
if (btn) btn.addEventListener("click", openMcpModal);
|
|
25
|
+
if (mateBtn) mateBtn.addEventListener("click", openMcpModal);
|
|
26
|
+
if (closeBtn) closeBtn.addEventListener("click", closeMcpModal);
|
|
27
|
+
if (backdrop) backdrop.addEventListener("click", closeMcpModal);
|
|
28
|
+
|
|
29
|
+
// ESC to close
|
|
30
|
+
document.addEventListener("keydown", function (e) {
|
|
31
|
+
if (e.key === "Escape" && modal && !modal.classList.contains("hidden")) {
|
|
32
|
+
closeMcpModal();
|
|
33
|
+
}
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function handleMcpServersState(msg) {
|
|
38
|
+
_mcpServers = msg.servers || [];
|
|
39
|
+
_extensionConnected = true;
|
|
40
|
+
if (msg.hostConnected !== undefined) _nativeHostConnected = msg.hostConnected;
|
|
41
|
+
if (msg.extensionId) _extensionId = msg.extensionId;
|
|
42
|
+
|
|
43
|
+
// Update HTTP MCP server registry for direct fetch calls
|
|
44
|
+
setHttpMcpServers(_mcpServers);
|
|
45
|
+
|
|
46
|
+
// Update sidebar badge
|
|
47
|
+
updateBadge();
|
|
48
|
+
|
|
49
|
+
// Re-render if modal is open (skip during toggle cooldown)
|
|
50
|
+
if (modal && !modal.classList.contains("hidden") && !_toggleCooldown) {
|
|
51
|
+
renderMcpServerList();
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export function setExtensionConnected(connected) {
|
|
56
|
+
_extensionConnected = connected;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export function getMcpServers() {
|
|
60
|
+
return _mcpServers;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function openMcpModal() {
|
|
64
|
+
if (!modal) return;
|
|
65
|
+
modal.classList.remove("hidden");
|
|
66
|
+
refreshIcons(modal);
|
|
67
|
+
renderMcpServerList();
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function closeMcpModal() {
|
|
71
|
+
if (!modal) return;
|
|
72
|
+
modal.classList.add("hidden");
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function updateBadge() {
|
|
76
|
+
var enabled = 0;
|
|
77
|
+
for (var i = 0; i < _mcpServers.length; i++) {
|
|
78
|
+
if (_mcpServers[i].extensionEnabled && _mcpServers[i].projectEnabled) enabled++;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
var badges = [
|
|
82
|
+
document.getElementById("mcp-sidebar-count"),
|
|
83
|
+
document.getElementById("mate-mcp-sidebar-count"),
|
|
84
|
+
];
|
|
85
|
+
for (var j = 0; j < badges.length; j++) {
|
|
86
|
+
var badge = badges[j];
|
|
87
|
+
if (!badge) continue;
|
|
88
|
+
if (enabled > 0) {
|
|
89
|
+
badge.textContent = String(enabled);
|
|
90
|
+
badge.classList.remove("hidden");
|
|
91
|
+
} else {
|
|
92
|
+
badge.classList.add("hidden");
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function renderMcpServerList() {
|
|
98
|
+
if (!contentEl) return;
|
|
99
|
+
contentEl.innerHTML = "";
|
|
100
|
+
|
|
101
|
+
var available = _mcpServers.filter(function (s) { return s.extensionEnabled; });
|
|
102
|
+
var hasServers = available.length > 0;
|
|
103
|
+
var allDone = _extensionConnected && _nativeHostConnected && hasServers;
|
|
104
|
+
|
|
105
|
+
// --- All setup complete: skip wizard, show server list only ---
|
|
106
|
+
if (allDone) {
|
|
107
|
+
var desc = document.createElement("p");
|
|
108
|
+
desc.className = "mcp-desc";
|
|
109
|
+
desc.textContent = "Toggle which MCP servers this project can use.";
|
|
110
|
+
contentEl.appendChild(desc);
|
|
111
|
+
|
|
112
|
+
for (var i = 0; i < available.length; i++) {
|
|
113
|
+
var server = available[i];
|
|
114
|
+
var row = document.createElement("label");
|
|
115
|
+
row.className = "mcp-server-row";
|
|
116
|
+
|
|
117
|
+
var cb = document.createElement("input");
|
|
118
|
+
cb.type = "checkbox";
|
|
119
|
+
cb.checked = server.projectEnabled;
|
|
120
|
+
cb.dataset.serverName = server.name;
|
|
121
|
+
cb.addEventListener("change", onToggle);
|
|
122
|
+
|
|
123
|
+
var info = document.createElement("div");
|
|
124
|
+
info.className = "mcp-server-info";
|
|
125
|
+
|
|
126
|
+
var nameSpan = document.createElement("span");
|
|
127
|
+
nameSpan.className = "mcp-server-name";
|
|
128
|
+
nameSpan.textContent = server.name;
|
|
129
|
+
|
|
130
|
+
var meta = document.createElement("span");
|
|
131
|
+
meta.className = "mcp-server-meta";
|
|
132
|
+
meta.textContent = server.toolCount + " tool" + (server.toolCount === 1 ? "" : "s");
|
|
133
|
+
if (server.transport === "http") meta.textContent += " \u00B7 HTTP";
|
|
134
|
+
|
|
135
|
+
info.appendChild(nameSpan);
|
|
136
|
+
info.appendChild(meta);
|
|
137
|
+
|
|
138
|
+
row.appendChild(cb);
|
|
139
|
+
row.appendChild(info);
|
|
140
|
+
contentEl.appendChild(row);
|
|
141
|
+
}
|
|
142
|
+
refreshIcons(contentEl);
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// --- Setup incomplete: show step wizard ---
|
|
147
|
+
var steps = document.createElement("div");
|
|
148
|
+
steps.className = "mcp-steps";
|
|
149
|
+
|
|
150
|
+
// Step 1: Chrome Extension
|
|
151
|
+
var step1Done = _extensionConnected;
|
|
152
|
+
steps.appendChild(renderStep({
|
|
153
|
+
num: 1,
|
|
154
|
+
done: step1Done,
|
|
155
|
+
title: "Install Chrome Extension",
|
|
156
|
+
desc: step1Done
|
|
157
|
+
? "Connected"
|
|
158
|
+
: "Required to bridge your browser with Clay.",
|
|
159
|
+
action: step1Done ? null : {
|
|
160
|
+
label: "Setup Extension",
|
|
161
|
+
icon: "puzzle",
|
|
162
|
+
onClick: function () {
|
|
163
|
+
closeMcpModal();
|
|
164
|
+
setTimeout(function () {
|
|
165
|
+
var extPill = document.getElementById("ext-pill");
|
|
166
|
+
if (extPill) extPill.click();
|
|
167
|
+
}, 100);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
}));
|
|
171
|
+
|
|
172
|
+
// Step 2: Native Host (only needed for remote users)
|
|
173
|
+
var step2Done = _extensionConnected && _nativeHostConnected;
|
|
174
|
+
var installCmd = _extensionId
|
|
175
|
+
? "npx clay-mcp-bridge install " + _extensionId
|
|
176
|
+
: "npx clay-mcp-bridge install <extension-id>";
|
|
177
|
+
steps.appendChild(renderStep({
|
|
178
|
+
num: 2,
|
|
179
|
+
done: step2Done,
|
|
180
|
+
title: "Install MCP Bridge",
|
|
181
|
+
desc: step2Done
|
|
182
|
+
? "Connected"
|
|
183
|
+
: "Run in your terminal, then restart your browser.",
|
|
184
|
+
disabled: !step1Done,
|
|
185
|
+
copyCmd: (!step2Done && step1Done) ? installCmd : null
|
|
186
|
+
}));
|
|
187
|
+
|
|
188
|
+
// Step 3: Configure MCP Servers
|
|
189
|
+
var step3Done = hasServers;
|
|
190
|
+
var step3Desc = "";
|
|
191
|
+
if (step3Done) {
|
|
192
|
+
step3Desc = available.length + " server" + (available.length === 1 ? "" : "s") + " enabled";
|
|
193
|
+
} else if (step2Done) {
|
|
194
|
+
step3Desc = "Add servers from the Clay Chrome Extension popup using the + button, or import an existing config file.";
|
|
195
|
+
} else {
|
|
196
|
+
step3Desc = "Configure after installing the bridge.";
|
|
197
|
+
}
|
|
198
|
+
steps.appendChild(renderStep({
|
|
199
|
+
num: 3,
|
|
200
|
+
done: step3Done,
|
|
201
|
+
title: "Add MCP Servers",
|
|
202
|
+
desc: step3Desc,
|
|
203
|
+
disabled: !step2Done,
|
|
204
|
+
action: (!step3Done && step2Done) ? {
|
|
205
|
+
label: "Open Extension popup to add servers",
|
|
206
|
+
icon: "puzzle",
|
|
207
|
+
onClick: function () {
|
|
208
|
+
closeMcpModal();
|
|
209
|
+
setTimeout(function () {
|
|
210
|
+
var extPill = document.getElementById("ext-pill");
|
|
211
|
+
if (extPill) extPill.click();
|
|
212
|
+
}, 100);
|
|
213
|
+
}
|
|
214
|
+
} : null
|
|
215
|
+
}));
|
|
216
|
+
|
|
217
|
+
contentEl.appendChild(steps);
|
|
218
|
+
refreshIcons(contentEl);
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
function renderStep(opts) {
|
|
222
|
+
var el = document.createElement("div");
|
|
223
|
+
el.className = "mcp-step" + (opts.done ? " done" : "") + (opts.disabled ? " disabled" : "");
|
|
224
|
+
|
|
225
|
+
var icon = opts.done ? "check-circle-2" : "circle";
|
|
226
|
+
var iconClass = opts.done ? "mcp-step-icon done" : "mcp-step-icon";
|
|
227
|
+
|
|
228
|
+
var html = '<div class="' + iconClass + '"><i data-lucide="' + icon + '"></i></div>'
|
|
229
|
+
+ '<div class="mcp-step-body">'
|
|
230
|
+
+ '<div class="mcp-step-title">' + opts.title + '</div>'
|
|
231
|
+
+ '<div class="mcp-step-desc">' + opts.desc + '</div>';
|
|
232
|
+
|
|
233
|
+
if (opts.copyCmd) {
|
|
234
|
+
html += '<div class="mcp-install-cmd-row">'
|
|
235
|
+
+ '<code class="mcp-install-cmd">' + opts.copyCmd + '</code>'
|
|
236
|
+
+ '<button class="mcp-install-copy-btn" type="button"><i data-lucide="copy"></i></button>'
|
|
237
|
+
+ '</div>';
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
html += '</div>';
|
|
241
|
+
el.innerHTML = html;
|
|
242
|
+
|
|
243
|
+
if (opts.action && !opts.disabled) {
|
|
244
|
+
var btn = document.createElement("button");
|
|
245
|
+
btn.className = "mcp-ext-setup-btn";
|
|
246
|
+
btn.type = "button";
|
|
247
|
+
btn.innerHTML = '<i data-lucide="' + opts.action.icon + '"></i> ' + opts.action.label;
|
|
248
|
+
btn.addEventListener("click", opts.action.onClick);
|
|
249
|
+
el.querySelector(".mcp-step-body").appendChild(btn);
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
if (opts.copyCmd) {
|
|
253
|
+
var copyBtn = el.querySelector(".mcp-install-copy-btn");
|
|
254
|
+
copyBtn.addEventListener("click", function () {
|
|
255
|
+
navigator.clipboard.writeText(opts.copyCmd).then(function () {
|
|
256
|
+
copyBtn.innerHTML = '<i data-lucide="check"></i>';
|
|
257
|
+
refreshIcons(copyBtn);
|
|
258
|
+
setTimeout(function () {
|
|
259
|
+
copyBtn.innerHTML = '<i data-lucide="copy"></i>';
|
|
260
|
+
refreshIcons(copyBtn);
|
|
261
|
+
}, 1500);
|
|
262
|
+
});
|
|
263
|
+
});
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
return el;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
var _toggleCooldown = false;
|
|
270
|
+
|
|
271
|
+
function onToggle(e) {
|
|
272
|
+
var name = e.target.dataset.serverName;
|
|
273
|
+
var enabled = e.target.checked;
|
|
274
|
+
|
|
275
|
+
// Optimistic update: apply locally so incoming broadcasts don't revert
|
|
276
|
+
for (var i = 0; i < _mcpServers.length; i++) {
|
|
277
|
+
if (_mcpServers[i].name === name) {
|
|
278
|
+
_mcpServers[i].projectEnabled = enabled;
|
|
279
|
+
break;
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
// Suppress re-renders from broadcasts for a short window
|
|
284
|
+
_toggleCooldown = true;
|
|
285
|
+
setTimeout(function () { _toggleCooldown = false; }, 1000);
|
|
286
|
+
|
|
287
|
+
var ws = getWs();
|
|
288
|
+
if (ws && ws.readyState === 1) {
|
|
289
|
+
ws.send(JSON.stringify({
|
|
290
|
+
type: "mcp_toggle_server",
|
|
291
|
+
name: name,
|
|
292
|
+
enabled: enabled,
|
|
293
|
+
}));
|
|
294
|
+
}
|
|
295
|
+
}
|