clay-server 2.28.0-beta.1 → 2.28.0-beta.3
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-mcp.js +371 -0
- package/lib/project-user-message.js +1 -0
- package/lib/project.js +51 -11
- package/lib/public/app.js +4 -0
- package/lib/public/css/filebrowser.css +204 -0
- package/lib/public/index.html +16 -0
- package/lib/public/modules/app-messages.js +10 -1
- package/lib/public/modules/app-misc.js +104 -0
- package/lib/public/modules/mcp-ui.js +295 -0
- package/lib/sdk-bridge.js +48 -5
- package/lib/server.js +6 -0
- package/lib/ws-schema.js +10 -0
- package/package.json +1 -1
|
@@ -227,6 +227,210 @@
|
|
|
227
227
|
margin: -8px 0 20px;
|
|
228
228
|
}
|
|
229
229
|
|
|
230
|
+
/* MCP Servers Modal */
|
|
231
|
+
#mcp-modal { position: fixed; inset: 0; z-index: 300; display: flex; align-items: center; justify-content: center; }
|
|
232
|
+
#mcp-modal.hidden { display: none; }
|
|
233
|
+
.mcp-dialog {
|
|
234
|
+
width: 480px;
|
|
235
|
+
max-width: 94vw;
|
|
236
|
+
max-height: 70vh;
|
|
237
|
+
display: flex;
|
|
238
|
+
flex-direction: column;
|
|
239
|
+
padding: 0;
|
|
240
|
+
overflow: hidden;
|
|
241
|
+
}
|
|
242
|
+
.mcp-header {
|
|
243
|
+
display: flex;
|
|
244
|
+
align-items: center;
|
|
245
|
+
justify-content: space-between;
|
|
246
|
+
padding: 16px 20px;
|
|
247
|
+
border-bottom: 1px solid var(--border);
|
|
248
|
+
}
|
|
249
|
+
.mcp-title {
|
|
250
|
+
display: flex;
|
|
251
|
+
align-items: center;
|
|
252
|
+
gap: 8px;
|
|
253
|
+
font-size: 16px;
|
|
254
|
+
font-weight: 700;
|
|
255
|
+
color: var(--text);
|
|
256
|
+
}
|
|
257
|
+
.mcp-title .lucide { width: 18px; height: 18px; }
|
|
258
|
+
.mcp-content {
|
|
259
|
+
padding: 16px 20px;
|
|
260
|
+
overflow-y: auto;
|
|
261
|
+
flex: 1;
|
|
262
|
+
}
|
|
263
|
+
.mcp-desc {
|
|
264
|
+
font-size: 13px;
|
|
265
|
+
color: var(--text-muted);
|
|
266
|
+
margin: 0 0 16px;
|
|
267
|
+
line-height: 1.5;
|
|
268
|
+
}
|
|
269
|
+
.mcp-empty {
|
|
270
|
+
text-align: center;
|
|
271
|
+
padding: 32px 16px;
|
|
272
|
+
color: var(--text-muted);
|
|
273
|
+
}
|
|
274
|
+
.mcp-empty .lucide { width: 32px; height: 32px; margin-bottom: 12px; opacity: 0.4; }
|
|
275
|
+
.mcp-empty-title {
|
|
276
|
+
font-size: 15px;
|
|
277
|
+
font-weight: 600;
|
|
278
|
+
color: var(--text-secondary);
|
|
279
|
+
margin: 0 0 6px;
|
|
280
|
+
}
|
|
281
|
+
.mcp-empty-desc {
|
|
282
|
+
font-size: 13px;
|
|
283
|
+
color: var(--text-muted);
|
|
284
|
+
margin: 0;
|
|
285
|
+
line-height: 1.5;
|
|
286
|
+
}
|
|
287
|
+
.mcp-ext-setup-btn {
|
|
288
|
+
display: inline-flex;
|
|
289
|
+
align-items: center;
|
|
290
|
+
gap: 6px;
|
|
291
|
+
padding: 7px 14px;
|
|
292
|
+
margin-top: 14px;
|
|
293
|
+
font-size: 13px;
|
|
294
|
+
font-weight: 500;
|
|
295
|
+
color: var(--text);
|
|
296
|
+
background: var(--bg-hover, rgba(255,255,255,0.06));
|
|
297
|
+
border: 1px solid var(--border);
|
|
298
|
+
border-radius: 8px;
|
|
299
|
+
cursor: pointer;
|
|
300
|
+
transition: background 0.15s;
|
|
301
|
+
}
|
|
302
|
+
.mcp-ext-setup-btn:hover {
|
|
303
|
+
background: var(--bg-active, rgba(255,255,255,0.1));
|
|
304
|
+
}
|
|
305
|
+
.mcp-ext-setup-btn .lucide {
|
|
306
|
+
width: 14px;
|
|
307
|
+
height: 14px;
|
|
308
|
+
}
|
|
309
|
+
.mcp-install-cmd-row {
|
|
310
|
+
display: flex;
|
|
311
|
+
align-items: center;
|
|
312
|
+
gap: 6px;
|
|
313
|
+
margin-top: 12px;
|
|
314
|
+
max-width: 100%;
|
|
315
|
+
}
|
|
316
|
+
.mcp-install-cmd {
|
|
317
|
+
flex: 1;
|
|
318
|
+
padding: 8px 10px;
|
|
319
|
+
background: var(--bg-deep, rgba(0,0,0,0.25));
|
|
320
|
+
border: 1px solid var(--border);
|
|
321
|
+
border-radius: 6px;
|
|
322
|
+
font-family: "SF Mono", Monaco, "Cascadia Code", monospace;
|
|
323
|
+
font-size: 12px;
|
|
324
|
+
color: var(--text);
|
|
325
|
+
overflow-x: auto;
|
|
326
|
+
white-space: nowrap;
|
|
327
|
+
line-height: 1.4;
|
|
328
|
+
}
|
|
329
|
+
.mcp-install-copy-btn {
|
|
330
|
+
flex-shrink: 0;
|
|
331
|
+
padding: 6px 8px;
|
|
332
|
+
background: var(--bg-hover, rgba(255,255,255,0.06));
|
|
333
|
+
border: 1px solid var(--border);
|
|
334
|
+
border-radius: 6px;
|
|
335
|
+
color: var(--text-muted);
|
|
336
|
+
cursor: pointer;
|
|
337
|
+
transition: background 0.15s;
|
|
338
|
+
}
|
|
339
|
+
.mcp-install-copy-btn:hover {
|
|
340
|
+
background: var(--bg-active, rgba(255,255,255,0.1));
|
|
341
|
+
}
|
|
342
|
+
.mcp-install-copy-btn .lucide {
|
|
343
|
+
width: 14px;
|
|
344
|
+
height: 14px;
|
|
345
|
+
}
|
|
346
|
+
.mcp-steps {
|
|
347
|
+
display: flex;
|
|
348
|
+
flex-direction: column;
|
|
349
|
+
gap: 2px;
|
|
350
|
+
}
|
|
351
|
+
.mcp-step {
|
|
352
|
+
display: flex;
|
|
353
|
+
gap: 12px;
|
|
354
|
+
padding: 12px 14px;
|
|
355
|
+
border-radius: 8px;
|
|
356
|
+
transition: opacity 0.15s;
|
|
357
|
+
}
|
|
358
|
+
.mcp-step.disabled {
|
|
359
|
+
opacity: 0.4;
|
|
360
|
+
pointer-events: none;
|
|
361
|
+
}
|
|
362
|
+
.mcp-step-icon {
|
|
363
|
+
flex-shrink: 0;
|
|
364
|
+
width: 22px;
|
|
365
|
+
height: 22px;
|
|
366
|
+
margin-top: 1px;
|
|
367
|
+
color: var(--text-muted);
|
|
368
|
+
}
|
|
369
|
+
.mcp-step-icon.done {
|
|
370
|
+
color: var(--green, #2ecc71);
|
|
371
|
+
}
|
|
372
|
+
.mcp-step-icon .lucide {
|
|
373
|
+
width: 20px;
|
|
374
|
+
height: 20px;
|
|
375
|
+
}
|
|
376
|
+
.mcp-step-body {
|
|
377
|
+
flex: 1;
|
|
378
|
+
min-width: 0;
|
|
379
|
+
}
|
|
380
|
+
.mcp-step-title {
|
|
381
|
+
font-size: 13px;
|
|
382
|
+
font-weight: 600;
|
|
383
|
+
color: var(--text);
|
|
384
|
+
margin-bottom: 2px;
|
|
385
|
+
}
|
|
386
|
+
.mcp-step-desc {
|
|
387
|
+
font-size: 12px;
|
|
388
|
+
color: var(--text-muted);
|
|
389
|
+
line-height: 1.4;
|
|
390
|
+
}
|
|
391
|
+
.mcp-step.done .mcp-step-desc {
|
|
392
|
+
color: var(--green, #2ecc71);
|
|
393
|
+
}
|
|
394
|
+
.mcp-divider {
|
|
395
|
+
height: 1px;
|
|
396
|
+
background: var(--border);
|
|
397
|
+
margin: 12px 0;
|
|
398
|
+
}
|
|
399
|
+
.mcp-server-row {
|
|
400
|
+
display: flex;
|
|
401
|
+
align-items: center;
|
|
402
|
+
gap: 12px;
|
|
403
|
+
padding: 10px 0;
|
|
404
|
+
border-bottom: 1px solid var(--border);
|
|
405
|
+
cursor: pointer;
|
|
406
|
+
transition: background 0.1s;
|
|
407
|
+
}
|
|
408
|
+
.mcp-server-row:last-child { border-bottom: none; }
|
|
409
|
+
.mcp-server-row:hover { background: var(--bg-dim); margin: 0 -20px; padding: 10px 20px; }
|
|
410
|
+
.mcp-server-row input[type="checkbox"] {
|
|
411
|
+
width: 16px;
|
|
412
|
+
height: 16px;
|
|
413
|
+
margin: 0;
|
|
414
|
+
cursor: pointer;
|
|
415
|
+
flex-shrink: 0;
|
|
416
|
+
}
|
|
417
|
+
.mcp-server-info {
|
|
418
|
+
display: flex;
|
|
419
|
+
flex-direction: column;
|
|
420
|
+
gap: 2px;
|
|
421
|
+
flex: 1;
|
|
422
|
+
min-width: 0;
|
|
423
|
+
}
|
|
424
|
+
.mcp-server-name {
|
|
425
|
+
font-size: 14px;
|
|
426
|
+
font-weight: 500;
|
|
427
|
+
color: var(--text);
|
|
428
|
+
}
|
|
429
|
+
.mcp-server-meta {
|
|
430
|
+
font-size: 12px;
|
|
431
|
+
color: var(--text-muted);
|
|
432
|
+
}
|
|
433
|
+
|
|
230
434
|
/* Beta toggle row inside settings card */
|
|
231
435
|
.ps-beta-toggle-row,
|
|
232
436
|
.ss-beta-toggle-row {
|
package/lib/public/index.html
CHANGED
|
@@ -191,6 +191,7 @@
|
|
|
191
191
|
<button id="file-browser-btn"><i data-lucide="folder-tree"></i> <span>File browser</span></button>
|
|
192
192
|
<button id="terminal-sidebar-btn"><i data-lucide="square-terminal"></i> <span>Terminal</span><span id="terminal-sidebar-count" class="sidebar-badge hidden"></span></button>
|
|
193
193
|
<button id="sticky-notes-sidebar-btn"><i data-lucide="sticky-note"></i> <span>Sticky Notes</span><span id="sticky-notes-sidebar-count" class="sidebar-badge hidden"></span></button>
|
|
194
|
+
<button id="mcp-btn"><i data-lucide="cable"></i> <span>MCP Servers</span><span id="mcp-sidebar-count" class="sidebar-badge hidden"></span></button>
|
|
194
195
|
<button id="skills-btn"><i data-lucide="puzzle"></i> <span>Skills</span></button>
|
|
195
196
|
<button id="scheduler-btn"><i data-lucide="calendar-clock"></i> <span>Scheduled Tasks</span></button>
|
|
196
197
|
</div>
|
|
@@ -255,6 +256,7 @@
|
|
|
255
256
|
<button id="mate-memory-btn"><i data-lucide="brain"></i> <span>Memory</span><span id="mate-memory-count" class="sidebar-badge hidden"></span></button>
|
|
256
257
|
<button id="mate-knowledge-btn"><i data-lucide="book-open"></i> <span>Knowledge</span><span id="mate-knowledge-count" class="sidebar-badge hidden"></span></button>
|
|
257
258
|
<button id="mate-sticky-notes-btn"><i data-lucide="sticky-note"></i> <span>Sticky Notes</span></button>
|
|
259
|
+
<button id="mate-mcp-btn"><i data-lucide="cable"></i> <span>MCP Servers</span><span id="mate-mcp-sidebar-count" class="sidebar-badge hidden"></span></button>
|
|
258
260
|
<button id="mate-skills-btn"><i data-lucide="puzzle"></i> <span>Skills</span></button>
|
|
259
261
|
<button id="mate-scheduler-btn"><i data-lucide="calendar-clock"></i> <span>Scheduled Tasks</span></button>
|
|
260
262
|
</div>
|
|
@@ -1507,6 +1509,20 @@
|
|
|
1507
1509
|
</div>
|
|
1508
1510
|
</div>
|
|
1509
1511
|
|
|
1512
|
+
<!-- MCP Servers Modal -->
|
|
1513
|
+
<div id="mcp-modal" class="hidden">
|
|
1514
|
+
<div class="confirm-backdrop"></div>
|
|
1515
|
+
<div class="confirm-dialog mcp-dialog">
|
|
1516
|
+
<div class="mcp-header">
|
|
1517
|
+
<span class="mcp-title"><i data-lucide="cable"></i> MCP Servers</span>
|
|
1518
|
+
<button class="skills-close" id="mcp-modal-close"><i data-lucide="x"></i></button>
|
|
1519
|
+
</div>
|
|
1520
|
+
<div class="mcp-content" id="mcp-content">
|
|
1521
|
+
<p class="mcp-no-servers">No MCP servers detected. Configure in Clay Chrome Extension.</p>
|
|
1522
|
+
</div>
|
|
1523
|
+
</div>
|
|
1524
|
+
</div>
|
|
1525
|
+
|
|
1510
1526
|
<!-- Debate Modal (Wizard) -->
|
|
1511
1527
|
<div id="debate-modal" class="hidden">
|
|
1512
1528
|
<div class="debate-modal-backdrop"></div>
|
|
@@ -31,7 +31,8 @@ import { handleSkillInstalled, handleSkillUninstalled } from './skills.js';
|
|
|
31
31
|
import { showRewindModal, onRewindComplete, setRewindMode, onRewindError, clearPendingRewindUuid, addRewindButton } from './rewind.js';
|
|
32
32
|
import { checkAdminAccess } from './admin.js';
|
|
33
33
|
import { mateAvatarUrl } from './avatar.js';
|
|
34
|
-
import { showImageModal, sendExtensionCommand } from './app-misc.js';
|
|
34
|
+
import { showImageModal, sendExtensionCommand, handleMcpToolCallMessage } from './app-misc.js';
|
|
35
|
+
import { handleMcpServersState } from './mcp-ui.js';
|
|
35
36
|
import { handleLoopRegistryUpdated, handleScheduleRunStarted, handleScheduleRunFinished, handleLoopScheduled, isSchedulerOpen, enterCraftingMode, exitCraftingMode, handleLoopRegistryFiles } from './scheduler.js';
|
|
36
37
|
|
|
37
38
|
// --- App module imports ---
|
|
@@ -999,6 +1000,14 @@ export function processMessage(msg) {
|
|
|
999
1000
|
sendExtensionCommand(msg.command, msg.args, msg.requestId);
|
|
1000
1001
|
break;
|
|
1001
1002
|
|
|
1003
|
+
case "mcp_tool_call":
|
|
1004
|
+
handleMcpToolCallMessage(msg);
|
|
1005
|
+
break;
|
|
1006
|
+
|
|
1007
|
+
case "mcp_servers_state":
|
|
1008
|
+
handleMcpServersState(msg);
|
|
1009
|
+
break;
|
|
1010
|
+
|
|
1002
1011
|
case "term_created":
|
|
1003
1012
|
handleTermCreated(msg);
|
|
1004
1013
|
if (store.getState().pendingTermCommand) {
|
|
@@ -5,6 +5,7 @@ import { refreshIcons, iconHtml } from './icons.js';
|
|
|
5
5
|
import { escapeHtml, copyToClipboard } from './utils.js';
|
|
6
6
|
import { getWs } from './ws-ref.js';
|
|
7
7
|
import { updateBrowserTabList } from './context-sources.js';
|
|
8
|
+
import { setExtensionConnected } from './mcp-ui.js';
|
|
8
9
|
|
|
9
10
|
// --- Module-owned state ---
|
|
10
11
|
var confirmCallback = null;
|
|
@@ -95,6 +96,7 @@ export function initMisc() {
|
|
|
95
96
|
var msg = event.data.payload;
|
|
96
97
|
|
|
97
98
|
if (msg.type === "clay_ext_tab_list") {
|
|
99
|
+
setExtensionConnected(true);
|
|
98
100
|
updateBrowserTabList(msg.tabs);
|
|
99
101
|
// Also inform server about tab list
|
|
100
102
|
var ws = getWs();
|
|
@@ -108,9 +110,111 @@ export function initMisc() {
|
|
|
108
110
|
if (msg.type === "clay_ext_result") {
|
|
109
111
|
handleExtensionResult(msg.requestId, msg.result);
|
|
110
112
|
}
|
|
113
|
+
if (msg.type === "clay_ext_disconnected") {
|
|
114
|
+
setExtensionConnected(false);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// MCP bridge: extension reports available MCP servers
|
|
118
|
+
if (msg.type === "mcp_servers_available") {
|
|
119
|
+
var ws2 = getWs();
|
|
120
|
+
if (ws2 && ws2.readyState === 1) {
|
|
121
|
+
ws2.send(JSON.stringify({
|
|
122
|
+
type: "mcp_servers_available",
|
|
123
|
+
servers: msg.servers,
|
|
124
|
+
hostConnected: msg.hostConnected
|
|
125
|
+
}));
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// MCP bridge: tool result from extension
|
|
130
|
+
if (msg.type === "mcp_tool_result") {
|
|
131
|
+
var ws3 = getWs();
|
|
132
|
+
if (ws3 && ws3.readyState === 1) {
|
|
133
|
+
ws3.send(JSON.stringify({
|
|
134
|
+
type: msg.error ? "mcp_tool_error" : "mcp_tool_result",
|
|
135
|
+
callId: msg.callId,
|
|
136
|
+
result: msg.result || null,
|
|
137
|
+
error: msg.error || null
|
|
138
|
+
}));
|
|
139
|
+
}
|
|
140
|
+
}
|
|
111
141
|
});
|
|
112
142
|
}
|
|
113
143
|
|
|
144
|
+
// Forward an MCP tool call from the server to the Chrome extension
|
|
145
|
+
export function forwardMcpToolCall(msg) {
|
|
146
|
+
window.postMessage({
|
|
147
|
+
source: "clay-page",
|
|
148
|
+
payload: {
|
|
149
|
+
type: "clay_mcp_tool_call",
|
|
150
|
+
callId: msg.callId,
|
|
151
|
+
server: msg.server,
|
|
152
|
+
method: msg.method,
|
|
153
|
+
params: msg.params,
|
|
154
|
+
}
|
|
155
|
+
}, "*");
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// Forward an MCP tool call directly via HTTP for HTTP-transport servers
|
|
159
|
+
var _httpMcpServers = {}; // name -> url
|
|
160
|
+
export function setHttpMcpServers(servers) {
|
|
161
|
+
_httpMcpServers = {};
|
|
162
|
+
for (var i = 0; i < servers.length; i++) {
|
|
163
|
+
if (servers[i].transport === "http" && servers[i].url) {
|
|
164
|
+
_httpMcpServers[servers[i].name] = servers[i].url;
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
export function handleMcpToolCallMessage(msg) {
|
|
170
|
+
var httpUrl = _httpMcpServers[msg.server];
|
|
171
|
+
if (httpUrl) {
|
|
172
|
+
// HTTP transport: call directly via fetch
|
|
173
|
+
fetch(httpUrl, {
|
|
174
|
+
method: "POST",
|
|
175
|
+
headers: { "Content-Type": "application/json" },
|
|
176
|
+
body: JSON.stringify({
|
|
177
|
+
jsonrpc: "2.0",
|
|
178
|
+
id: msg.callId,
|
|
179
|
+
method: msg.method,
|
|
180
|
+
params: msg.params,
|
|
181
|
+
}),
|
|
182
|
+
})
|
|
183
|
+
.then(function (resp) { return resp.json(); })
|
|
184
|
+
.then(function (data) {
|
|
185
|
+
var ws = getWs();
|
|
186
|
+
if (ws && ws.readyState === 1) {
|
|
187
|
+
if (data.error) {
|
|
188
|
+
ws.send(JSON.stringify({
|
|
189
|
+
type: "mcp_tool_error",
|
|
190
|
+
callId: msg.callId,
|
|
191
|
+
error: data.error.message || JSON.stringify(data.error),
|
|
192
|
+
}));
|
|
193
|
+
} else {
|
|
194
|
+
ws.send(JSON.stringify({
|
|
195
|
+
type: "mcp_tool_result",
|
|
196
|
+
callId: msg.callId,
|
|
197
|
+
result: data.result,
|
|
198
|
+
}));
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
})
|
|
202
|
+
.catch(function (err) {
|
|
203
|
+
var ws = getWs();
|
|
204
|
+
if (ws && ws.readyState === 1) {
|
|
205
|
+
ws.send(JSON.stringify({
|
|
206
|
+
type: "mcp_tool_error",
|
|
207
|
+
callId: msg.callId,
|
|
208
|
+
error: "HTTP MCP fetch failed: " + err.message,
|
|
209
|
+
}));
|
|
210
|
+
}
|
|
211
|
+
});
|
|
212
|
+
} else {
|
|
213
|
+
// stdio transport: forward to extension
|
|
214
|
+
forwardMcpToolCall(msg);
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
114
218
|
export function showImageModal(src) {
|
|
115
219
|
var modal = document.getElementById("image-modal");
|
|
116
220
|
var img = document.getElementById("image-modal-img");
|
|
@@ -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
|
+
}
|