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.
@@ -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
+ }