clay-server 4.8.2 → 5.0.0-beta.1
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/LICENSES/README.md +13 -0
- package/LICENSES/open-iconic.txt +21 -0
- package/LICENSES/plantuml-salt.txt +674 -0
- package/lib/assets/salt-assets.json +15 -0
- package/lib/assets/salt-colors.json +156 -0
- package/lib/assets/salt-font-10.bin.gz +0 -0
- package/lib/assets/salt-font-13.bin.gz +0 -0
- package/lib/assets/salt-font-14.bin.gz +0 -0
- package/lib/assets/salt-font-16.bin.gz +0 -0
- package/lib/assets/salt-font-18.bin.gz +0 -0
- package/lib/assets/salt-font-20.bin.gz +0 -0
- package/lib/assets/salt-font-metrics.bin.gz +0 -0
- package/lib/assets/salt-font-metrics.json +101 -0
- package/lib/assets/salt-font-monospace-10.bin.gz +0 -0
- package/lib/assets/salt-font-monospace-13.bin.gz +0 -0
- package/lib/assets/salt-font-monospace-14.bin.gz +0 -0
- package/lib/assets/salt-font-monospace-16.bin.gz +0 -0
- package/lib/assets/salt-font-monospace-18.bin.gz +0 -0
- package/lib/assets/salt-font-monospace-20.bin.gz +0 -0
- package/lib/assets/salt-font-monospace.bin.gz +0 -0
- package/lib/assets/salt-icons.json +671 -0
- package/lib/bundled-skills/clay-sketch/SKILL.md +198 -0
- package/lib/mcp-browser-proxy.js +37 -0
- package/lib/mcp-connection-store.js +39 -0
- package/lib/mcp-network.js +46 -0
- package/lib/mcp-oauth.js +65 -0
- package/lib/mcp-proxy.js +32 -0
- package/lib/mcp-remote-connections.js +215 -0
- package/lib/mcp-session-access.js +15 -0
- package/lib/plantuml-renderer.js +43 -0
- package/lib/project-file-http.js +2 -0
- package/lib/project-mcp.js +130 -357
- package/lib/project-plantuml.js +48 -0
- package/lib/project-session-document.js +17 -1
- package/lib/project-user-message.js +2 -0
- package/lib/project-wireframe-http.js +88 -0
- package/lib/project.js +28 -45
- package/lib/public/app.js +7 -0
- package/lib/public/css/filebrowser.css +5 -0
- package/lib/public/css/mcp-connections.css +86 -0
- package/lib/public/css/skills-workbench.css +74 -0
- package/lib/public/css/wireframe.css +95 -0
- package/lib/public/index.html +16 -15
- package/lib/public/modules/app-messages.js +10 -2
- package/lib/public/modules/filebrowser.js +29 -3
- package/lib/public/modules/markdown.js +3 -1
- package/lib/public/modules/mcp-connection-view.js +136 -0
- package/lib/public/modules/mcp-permissions.js +122 -0
- package/lib/public/modules/mcp-ui.js +85 -461
- package/lib/public/modules/plantuml-preview.js +76 -0
- package/lib/public/modules/skills-actions.js +47 -79
- package/lib/public/modules/skills-detail.js +32 -145
- package/lib/public/modules/skills-list-view.js +29 -0
- package/lib/public/modules/skills-state.js +4 -2
- package/lib/public/modules/skills.js +104 -399
- package/lib/public/modules/split-view.js +14 -0
- package/lib/public/modules/vendor-login-browser.js +16 -5
- package/lib/public/modules/wireframe-chat.js +52 -0
- package/lib/public/modules/wireframe-render.js +69 -0
- package/lib/public/modules/wireframe-style.js +6 -0
- package/lib/public/modules/wireframe-viewport.js +70 -0
- package/lib/public/modules/wireframe-workbench.js +116 -0
- package/lib/public/style.css +3 -0
- package/lib/salt-chrome.js +36 -0
- package/lib/salt-hand.js +37 -0
- package/lib/salt-images.js +87 -0
- package/lib/salt-layout.js +60 -0
- package/lib/salt-options.js +23 -0
- package/lib/salt-overlays.js +45 -0
- package/lib/salt-parser.js +153 -0
- package/lib/salt-screen.js +111 -0
- package/lib/salt-svg-shapes.js +80 -0
- package/lib/salt-svg.js +164 -0
- package/lib/salt-text.js +112 -0
- package/lib/salt-tree.js +45 -0
- package/lib/sdk-bridge.js +5 -17
- package/lib/sdk-mcp-descriptors.js +2 -1
- package/lib/session-document-mcp-server.js +9 -0
- package/lib/vendor-login-browser-worker.js +18 -8
- package/lib/vendor-login-browser.js +10 -3
- package/lib/yoke/adapters/codex.js +1 -18
- package/package.json +6 -2
|
@@ -1,473 +1,97 @@
|
|
|
1
1
|
import { ensureMcpSkillsWorkbench, openMcpSkillsWorkbench, closeMcpSkillsWorkbench } from './mcp-skills-workbench.js';
|
|
2
|
-
// mcp-ui.js - MCP Servers modal (sidebar button + panel)
|
|
3
|
-
// Renders available MCP servers with per-project toggle checkboxes.
|
|
4
|
-
|
|
5
|
-
import { getWs } from './ws-ref.js';
|
|
6
|
-
import { refreshIcons } from './icons.js';
|
|
7
|
-
import { setHttpMcpServers } from './app-misc.js';
|
|
8
2
|
import { store } from './store.js';
|
|
9
|
-
import {
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
var contentEl = null;
|
|
13
|
-
var _mcpServers = []; // { name, transport, toolCount, extensionEnabled, projectEnabled }
|
|
14
|
-
var _extensionConnected = false;
|
|
15
|
-
var _nativeHostConnected = false;
|
|
16
|
-
var _extensionId = null;
|
|
17
|
-
var MCP_PERMISSION_TIMEOUT_MS = 10000;
|
|
18
|
-
var _permissionModeTimers = {};
|
|
19
|
-
|
|
20
|
-
function pendingKey(projectSlug, sessionId, requestId) {
|
|
21
|
-
return String(projectSlug) + ":" + String(sessionId) + ":" + String(requestId);
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
function pendingMap() {
|
|
25
|
-
return store.get('mcpPermissionModePendingByTarget') || {};
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
function setPendingMap(next) {
|
|
29
|
-
store.set({ mcpPermissionModePendingByTarget: next });
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
function findPending(requestId) {
|
|
33
|
-
var pending = pendingMap();
|
|
34
|
-
var keys = Object.keys(pending);
|
|
35
|
-
for (var i = 0; i < keys.length; i++) {
|
|
36
|
-
if (pending[keys[i]].requestId === requestId) return { key: keys[i], value: pending[keys[i]] };
|
|
37
|
-
}
|
|
38
|
-
return null;
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
function clearPendingRequest(requestId) {
|
|
42
|
-
var found = findPending(requestId);
|
|
43
|
-
if (!found) return null;
|
|
44
|
-
if (_permissionModeTimers[requestId]) clearTimeout(_permissionModeTimers[requestId]);
|
|
45
|
-
delete _permissionModeTimers[requestId];
|
|
46
|
-
var next = Object.assign({}, pendingMap());
|
|
47
|
-
delete next[found.key];
|
|
48
|
-
setPendingMap(next);
|
|
49
|
-
return found.value;
|
|
50
|
-
}
|
|
3
|
+
import { getWs } from './ws-ref.js';
|
|
4
|
+
import { renderConnections } from './mcp-connection-view.js';
|
|
5
|
+
export { clearMcpPermissionModePending, mcpPermissionModeApplicability, sendMcpPermissionMode, handleMcpPermissionModeResult } from './mcp-permissions.js';
|
|
51
6
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
if (
|
|
7
|
+
function state() { return store.get('mcpConnections') || { screen: 'list', servers: [], search: '' }; }
|
|
8
|
+
function patch(value) { store.set({ mcpConnections: Object.assign({}, state(), value) }); }
|
|
9
|
+
function send(message) {
|
|
10
|
+
var ws = getWs();
|
|
11
|
+
if (!ws || ws.readyState !== 1) { patch({ error: 'Reconnect to Clay to manage connections.' }); return false; }
|
|
12
|
+
ws.send(JSON.stringify(message)); return true;
|
|
13
|
+
}
|
|
14
|
+
function requestState() { send({ type: 'mcp_state_request' }); }
|
|
15
|
+
function clearActionTimer() { var timer = store.get('mcpActionTimer'); if (timer) clearTimeout(timer); store.set({ mcpActionTimer: null }); }
|
|
16
|
+
function navigate(screen, id) { patch({ screen: screen, selected: id || null, error: '', notice: '', search: '', detailTab: 'tools' }); }
|
|
17
|
+
function action(name, args) {
|
|
18
|
+
if (state().pending) return;
|
|
19
|
+
var requestId = crypto.randomUUID();
|
|
20
|
+
patch({ pending: { requestId: requestId, action: name }, error: '', notice: '' });
|
|
21
|
+
clearActionTimer();
|
|
22
|
+
store.set({ mcpActionTimer: setTimeout(function () {
|
|
23
|
+
if (state().pending && state().pending.requestId === requestId) { patch({ pending: null, error: 'The server is taking longer than expected. Refresh connections to check its state.' }); requestState(); }
|
|
24
|
+
}, 90000) });
|
|
25
|
+
if (!send(Object.assign({ type: 'mcp_connection_action', action: name, requestId: requestId, id: state().selected }, args || {}))) { clearActionTimer(); patch({ pending: null }); }
|
|
26
|
+
}
|
|
27
|
+
function toggle(row, value) {
|
|
28
|
+
if (row.source === 'url') action('enable', { id: row.id, enabled: value });
|
|
29
|
+
else send({ type: 'mcp_toggle_server', name: row.name, enabled: value });
|
|
30
|
+
}
|
|
31
|
+
function render() {
|
|
32
|
+
var element = document.getElementById('mcp-content');
|
|
33
|
+
if (!element) return;
|
|
34
|
+
var current = state();
|
|
35
|
+
var extension = document.getElementById('ext-pill-wrap');
|
|
36
|
+
if (extension) extension.hidden = current.screen !== 'computer';
|
|
37
|
+
renderConnections(element, current, {
|
|
38
|
+
navigate: navigate, action: action, toggle: toggle,
|
|
39
|
+
search: function (value) { patch({ search: value }); },
|
|
40
|
+
draft: function (value) { store.set({ mcpConnectionDraft: value }); },
|
|
41
|
+
refresh: requestState,
|
|
42
|
+
setup: function () { var button = document.getElementById('ext-pill'); if (button) button.click(); },
|
|
43
|
+
remove: function (row) { navigate('remove', row.id); },
|
|
44
|
+
});
|
|
57
45
|
}
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
if (
|
|
61
|
-
return { applicable: false, reason: "AI review is unavailable for this SDK session." };
|
|
62
|
-
}
|
|
63
|
-
if (state.effectivePermissionMode === "auto" || state.effectivePermissionMode === "bypassPermissions") {
|
|
64
|
-
return { applicable: true, reason: "" };
|
|
65
|
-
}
|
|
66
|
-
if (!state.effectivePermissionMode && state.currentMode === "auto") {
|
|
67
|
-
return { applicable: false, reason: "Waiting for the SDK to confirm Auto." };
|
|
68
|
-
}
|
|
69
|
-
return { applicable: false, reason: "AI review requires an active Auto session." };
|
|
46
|
+
function badge() {
|
|
47
|
+
var count = state().servers.filter(function (row) { return row.projectEnabled; }).length;
|
|
48
|
+
['mcp-sidebar-count', 'mate-mcp-sidebar-count'].forEach(function (id) { var el = document.getElementById(id); if (el) { el.textContent = String(count); el.classList.toggle('hidden', !count); } });
|
|
70
49
|
}
|
|
71
|
-
|
|
72
50
|
export function initMcp() {
|
|
73
51
|
ensureMcpSkillsWorkbench();
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
if (closeBtn) closeBtn.addEventListener("click", closeMcpModal);
|
|
85
|
-
if (backdrop) backdrop.addEventListener("click", closeMcpModal);
|
|
86
|
-
|
|
87
|
-
store.subscribe(function (state, prev) {
|
|
88
|
-
var changed = state.activeSessionId !== prev.activeSessionId || state.currentSlug !== prev.currentSlug ||
|
|
89
|
-
state.currentMode !== prev.currentMode || state.effectivePermissionMode !== prev.effectivePermissionMode ||
|
|
90
|
-
state.permissionCapabilities !== prev.permissionCapabilities || state.mcpPermissionModeOverrides !== prev.mcpPermissionModeOverrides ||
|
|
91
|
-
state.mcpPermissionModePendingByTarget !== prev.mcpPermissionModePendingByTarget || state.connected !== prev.connected;
|
|
92
|
-
changed = changed || state.mcpSkillsWorkbench !== prev.mcpSkillsWorkbench;
|
|
93
|
-
if (changed && modal && !modal.classList.contains("hidden")) renderMcpServerList();
|
|
94
|
-
});
|
|
95
|
-
|
|
96
|
-
// ESC to close
|
|
97
|
-
document.addEventListener("keydown", function (e) {
|
|
98
|
-
if (e.key === "Escape" && modal && !modal.classList.contains("hidden")) {
|
|
99
|
-
closeMcpModal();
|
|
52
|
+
['mcp-btn', 'mate-mcp-btn'].forEach(function (id) { var button = document.getElementById(id); if (button) button.addEventListener('click', function () { openMcpSkillsWorkbench('mcp'); requestState(); render(); }); });
|
|
53
|
+
var close = document.getElementById('mcp-modal-close'); if (close) close.onclick = closeMcpSkillsWorkbench;
|
|
54
|
+
store.subscribe(function (current, previous) {
|
|
55
|
+
if (current.currentSlug !== previous.currentSlug || current.myUserId !== previous.myUserId) {
|
|
56
|
+
clearActionTimer();
|
|
57
|
+
store.set({ mcpConnections: { screen: 'list', servers: [], search: '', loaded: false }, mcpConnectionDraft: '' }); return;
|
|
58
|
+
}
|
|
59
|
+
if (current.connected !== previous.connected) {
|
|
60
|
+
if (current.connected) requestState();
|
|
61
|
+
else { clearActionTimer(); patch({ pending: null, error: 'Connection lost. Reconnect to manage your servers.' }); }
|
|
100
62
|
}
|
|
63
|
+
if (current.mcpConnections !== previous.mcpConnections || current.mcpSkillsWorkbench !== previous.mcpSkillsWorkbench || current.activeSessionId !== previous.activeSessionId || current.mcpPermissionModeOverrides !== previous.mcpPermissionModeOverrides || current.permissionCapabilities !== previous.permissionCapabilities || current.effectivePermissionMode !== previous.effectivePermissionMode || current.mcpPermissionModePendingByTarget !== previous.mcpPermissionModePendingByTarget) { render(); badge(); }
|
|
101
64
|
});
|
|
65
|
+
render();
|
|
102
66
|
}
|
|
103
|
-
|
|
104
67
|
export function handleMcpServersState(msg) {
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
function
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
renderMcpServerList();
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
function closeMcpModal() {
|
|
138
|
-
if (!modal) return;
|
|
139
|
-
closeMcpSkillsWorkbench();
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
function updateBadge() {
|
|
143
|
-
var enabled = 0;
|
|
144
|
-
for (var i = 0; i < _mcpServers.length; i++) {
|
|
145
|
-
if (_mcpServers[i].extensionEnabled && _mcpServers[i].projectEnabled) enabled++;
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
var badges = [
|
|
149
|
-
document.getElementById("mcp-sidebar-count"),
|
|
150
|
-
document.getElementById("mate-mcp-sidebar-count"),
|
|
151
|
-
];
|
|
152
|
-
for (var j = 0; j < badges.length; j++) {
|
|
153
|
-
var badge = badges[j];
|
|
154
|
-
if (!badge) continue;
|
|
155
|
-
if (enabled > 0) {
|
|
156
|
-
badge.textContent = String(enabled);
|
|
157
|
-
badge.classList.remove("hidden");
|
|
158
|
-
} else {
|
|
159
|
-
badge.classList.add("hidden");
|
|
160
|
-
}
|
|
161
|
-
}
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
function renderMcpServerList() {
|
|
165
|
-
if (!contentEl) return;
|
|
166
|
-
contentEl.innerHTML = "";
|
|
167
|
-
|
|
168
|
-
var available = _mcpServers.filter(function (s) { return s.extensionEnabled; });
|
|
169
|
-
var hasServers = available.length > 0;
|
|
170
|
-
var allDone = _extensionConnected && _nativeHostConnected && hasServers;
|
|
171
|
-
|
|
172
|
-
// --- All setup complete: skip wizard, show server list only ---
|
|
173
|
-
if (allDone) {
|
|
174
|
-
var desc = document.createElement("p");
|
|
175
|
-
desc.className = "mcp-desc";
|
|
176
|
-
desc.textContent = "Toggle which MCP servers this project can use.";
|
|
177
|
-
contentEl.appendChild(desc);
|
|
178
|
-
|
|
179
|
-
for (var i = 0; i < available.length; i++) {
|
|
180
|
-
var server = available[i];
|
|
181
|
-
var row = document.createElement("label");
|
|
182
|
-
row.className = "mcp-server-row";
|
|
183
|
-
|
|
184
|
-
var cb = document.createElement("input");
|
|
185
|
-
cb.type = "checkbox";
|
|
186
|
-
cb.checked = server.projectEnabled;
|
|
187
|
-
cb.dataset.serverName = server.name;
|
|
188
|
-
cb.addEventListener("change", onToggle);
|
|
189
|
-
|
|
190
|
-
var info = document.createElement("div");
|
|
191
|
-
info.className = "mcp-server-info";
|
|
192
|
-
|
|
193
|
-
var nameSpan = document.createElement("span");
|
|
194
|
-
nameSpan.className = "mcp-server-name";
|
|
195
|
-
nameSpan.textContent = server.name;
|
|
196
|
-
|
|
197
|
-
var meta = document.createElement("span");
|
|
198
|
-
meta.className = "mcp-server-meta";
|
|
199
|
-
meta.textContent = server.toolCount + " tool" + (server.toolCount === 1 ? "" : "s");
|
|
200
|
-
if (server.transport === "http") meta.textContent += " \u00B7 HTTP";
|
|
201
|
-
|
|
202
|
-
info.appendChild(nameSpan);
|
|
203
|
-
info.appendChild(meta);
|
|
204
|
-
|
|
205
|
-
row.appendChild(cb);
|
|
206
|
-
row.appendChild(info);
|
|
207
|
-
|
|
208
|
-
// Per-server permission mode override (applies to the active session only).
|
|
209
|
-
if (server.projectEnabled) {
|
|
210
|
-
var permissionState = store.snap();
|
|
211
|
-
var applicability = mcpPermissionModeApplicability(permissionState);
|
|
212
|
-
if (!applicability.applicable) {
|
|
213
|
-
var unavailable = document.createElement("span");
|
|
214
|
-
unavailable.className = "mcp-server-perm-reason";
|
|
215
|
-
unavailable.textContent = applicability.reason;
|
|
216
|
-
row.appendChild(unavailable);
|
|
217
|
-
contentEl.appendChild(row);
|
|
218
|
-
continue;
|
|
219
|
-
}
|
|
220
|
-
var modeSel = document.createElement("select");
|
|
221
|
-
modeSel.className = "mcp-server-perm-mode";
|
|
222
|
-
modeSel.dataset.serverName = server.name;
|
|
223
|
-
modeSel.title = "AI review policy for this server (this session only)";
|
|
224
|
-
var optInherit = document.createElement("option");
|
|
225
|
-
optInherit.value = "";
|
|
226
|
-
optInherit.textContent = "Inherit";
|
|
227
|
-
var optAsk = document.createElement("option");
|
|
228
|
-
optAsk.value = "default";
|
|
229
|
-
optAsk.textContent = "Ask";
|
|
230
|
-
var optAuto = document.createElement("option");
|
|
231
|
-
optAuto.value = "auto";
|
|
232
|
-
optAuto.textContent = "AI review";
|
|
233
|
-
modeSel.appendChild(optInherit);
|
|
234
|
-
modeSel.appendChild(optAsk);
|
|
235
|
-
modeSel.appendChild(optAuto);
|
|
236
|
-
var overrides = store.get('mcpPermissionModeOverrides') || {};
|
|
237
|
-
modeSel.value = overrides[server.name] === "auto" || overrides[server.name] === "default" ? overrides[server.name] : "";
|
|
238
|
-
var pending = pendingMap();
|
|
239
|
-
var pendingKeys = Object.keys(pending);
|
|
240
|
-
for (var p = 0; p < pendingKeys.length; p++) {
|
|
241
|
-
var pendingValue = pending[pendingKeys[p]];
|
|
242
|
-
if (pendingValue.projectSlug === permissionState.currentSlug && pendingValue.sessionId === permissionState.activeSessionId && pendingValue.serverName === server.name) {
|
|
243
|
-
modeSel.value = pendingValue.mode;
|
|
244
|
-
modeSel.disabled = true;
|
|
245
|
-
break;
|
|
246
|
-
}
|
|
247
|
-
}
|
|
248
|
-
if (!permissionState.connected) modeSel.disabled = true;
|
|
249
|
-
// The row is a <label>; keep select interactions from toggling the checkbox.
|
|
250
|
-
modeSel.addEventListener("click", function (e) { e.stopPropagation(); });
|
|
251
|
-
modeSel.addEventListener("change", onPermissionModeChange);
|
|
252
|
-
row.appendChild(modeSel);
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
contentEl.appendChild(row);
|
|
256
|
-
}
|
|
257
|
-
refreshIcons(contentEl);
|
|
258
|
-
return;
|
|
259
|
-
}
|
|
260
|
-
|
|
261
|
-
// --- Setup incomplete: show step wizard ---
|
|
262
|
-
var steps = document.createElement("div");
|
|
263
|
-
steps.className = "mcp-steps";
|
|
264
|
-
|
|
265
|
-
// Step 1: Chrome Extension
|
|
266
|
-
var step1Done = _extensionConnected;
|
|
267
|
-
steps.appendChild(renderStep({
|
|
268
|
-
num: 1,
|
|
269
|
-
done: step1Done,
|
|
270
|
-
title: "Install Chrome Extension",
|
|
271
|
-
desc: step1Done
|
|
272
|
-
? "Connected"
|
|
273
|
-
: "Required to bridge your browser with Clay.",
|
|
274
|
-
action: step1Done ? null : {
|
|
275
|
-
label: "Setup Extension",
|
|
276
|
-
icon: "puzzle",
|
|
277
|
-
onClick: function () {
|
|
278
|
-
var extPill = document.getElementById("ext-pill");
|
|
279
|
-
if (extPill && extPill.getAttribute("aria-expanded") !== "true") extPill.click();
|
|
280
|
-
}
|
|
281
|
-
}
|
|
282
|
-
}));
|
|
283
|
-
|
|
284
|
-
// Step 2: Native Host (only needed for remote users)
|
|
285
|
-
var step2Done = _extensionConnected && _nativeHostConnected;
|
|
286
|
-
var installCmd = _extensionId
|
|
287
|
-
? "npx clay-mcp-bridge install " + _extensionId
|
|
288
|
-
: "npx clay-mcp-bridge install <extension-id>";
|
|
289
|
-
steps.appendChild(renderStep({
|
|
290
|
-
num: 2,
|
|
291
|
-
done: step2Done,
|
|
292
|
-
title: "Install MCP Bridge",
|
|
293
|
-
desc: step2Done
|
|
294
|
-
? "Connected"
|
|
295
|
-
: "Run in your terminal, then restart your browser.",
|
|
296
|
-
disabled: !step1Done,
|
|
297
|
-
copyCmd: (!step2Done && step1Done) ? installCmd : null
|
|
298
|
-
}));
|
|
299
|
-
|
|
300
|
-
// Step 3: Configure MCP Servers
|
|
301
|
-
var step3Done = hasServers;
|
|
302
|
-
var step3Desc = "";
|
|
303
|
-
if (step3Done) {
|
|
304
|
-
step3Desc = available.length + " server" + (available.length === 1 ? "" : "s") + " enabled";
|
|
305
|
-
} else if (step2Done) {
|
|
306
|
-
step3Desc = "Add servers from the Clay Chrome Extension popup using the + button, or import an existing config file.";
|
|
307
|
-
} else {
|
|
308
|
-
step3Desc = "Configure after installing the bridge.";
|
|
309
|
-
}
|
|
310
|
-
steps.appendChild(renderStep({
|
|
311
|
-
num: 3,
|
|
312
|
-
done: step3Done,
|
|
313
|
-
title: "Add MCP Servers",
|
|
314
|
-
desc: step3Desc,
|
|
315
|
-
disabled: !step2Done,
|
|
316
|
-
action: (!step3Done && step2Done) ? {
|
|
317
|
-
label: "Open Extension popup to add servers",
|
|
318
|
-
icon: "puzzle",
|
|
319
|
-
onClick: function () {
|
|
320
|
-
var extPill = document.getElementById("ext-pill");
|
|
321
|
-
if (extPill && extPill.getAttribute("aria-expanded") !== "true") extPill.click();
|
|
322
|
-
}
|
|
323
|
-
} : null
|
|
324
|
-
}));
|
|
325
|
-
|
|
326
|
-
contentEl.appendChild(steps);
|
|
327
|
-
refreshIcons(contentEl);
|
|
328
|
-
}
|
|
329
|
-
|
|
330
|
-
function renderStep(opts) {
|
|
331
|
-
var el = document.createElement("div");
|
|
332
|
-
el.className = "mcp-step" + (opts.done ? " done" : "") + (opts.disabled ? " disabled" : "");
|
|
333
|
-
|
|
334
|
-
var icon = opts.done ? "check-circle-2" : "circle";
|
|
335
|
-
var iconClass = opts.done ? "mcp-step-icon done" : "mcp-step-icon";
|
|
336
|
-
|
|
337
|
-
var html = '<div class="' + iconClass + '"><i data-lucide="' + icon + '"></i></div>'
|
|
338
|
-
+ '<div class="mcp-step-body">'
|
|
339
|
-
+ '<div class="mcp-step-title">' + opts.title + '</div>'
|
|
340
|
-
+ '<div class="mcp-step-desc">' + opts.desc + '</div>';
|
|
341
|
-
|
|
342
|
-
if (opts.copyCmd) {
|
|
343
|
-
html += '<div class="mcp-install-cmd-row">'
|
|
344
|
-
+ '<code class="mcp-install-cmd">' + opts.copyCmd + '</code>'
|
|
345
|
-
+ '<button class="mcp-install-copy-btn" type="button"><i data-lucide="copy"></i></button>'
|
|
346
|
-
+ '</div>';
|
|
347
|
-
}
|
|
348
|
-
|
|
349
|
-
html += '</div>';
|
|
350
|
-
el.innerHTML = html;
|
|
351
|
-
|
|
352
|
-
if (opts.action && !opts.disabled) {
|
|
353
|
-
var btn = document.createElement("button");
|
|
354
|
-
btn.className = "mcp-ext-setup-btn";
|
|
355
|
-
btn.type = "button";
|
|
356
|
-
btn.innerHTML = '<i data-lucide="' + opts.action.icon + '"></i> ' + opts.action.label;
|
|
357
|
-
btn.addEventListener("click", opts.action.onClick);
|
|
358
|
-
el.querySelector(".mcp-step-body").appendChild(btn);
|
|
359
|
-
}
|
|
360
|
-
|
|
361
|
-
if (opts.copyCmd) {
|
|
362
|
-
var copyBtn = el.querySelector(".mcp-install-copy-btn");
|
|
363
|
-
copyBtn.addEventListener("click", function () {
|
|
364
|
-
navigator.clipboard.writeText(opts.copyCmd).then(function () {
|
|
365
|
-
copyBtn.innerHTML = '<i data-lucide="check"></i>';
|
|
366
|
-
refreshIcons(copyBtn);
|
|
367
|
-
setTimeout(function () {
|
|
368
|
-
copyBtn.innerHTML = '<i data-lucide="copy"></i>';
|
|
369
|
-
refreshIcons(copyBtn);
|
|
370
|
-
}, 1500);
|
|
371
|
-
});
|
|
372
|
-
});
|
|
373
|
-
}
|
|
374
|
-
|
|
375
|
-
return el;
|
|
376
|
-
}
|
|
377
|
-
|
|
378
|
-
var _toggleCooldown = false;
|
|
379
|
-
|
|
380
|
-
function onToggle(e) {
|
|
381
|
-
var name = e.target.dataset.serverName;
|
|
382
|
-
var enabled = e.target.checked;
|
|
383
|
-
|
|
384
|
-
// Optimistic update: apply locally so incoming broadcasts don't revert
|
|
385
|
-
for (var i = 0; i < _mcpServers.length; i++) {
|
|
386
|
-
if (_mcpServers[i].name === name) {
|
|
387
|
-
_mcpServers[i].projectEnabled = enabled;
|
|
388
|
-
break;
|
|
389
|
-
}
|
|
390
|
-
}
|
|
391
|
-
|
|
392
|
-
// Suppress re-renders from broadcasts for a short window
|
|
393
|
-
_toggleCooldown = true;
|
|
394
|
-
setTimeout(function () { _toggleCooldown = false; }, 1000);
|
|
395
|
-
|
|
396
|
-
var ws = getWs();
|
|
397
|
-
if (ws && ws.readyState === 1) {
|
|
398
|
-
ws.send(JSON.stringify({
|
|
399
|
-
type: "mcp_toggle_server",
|
|
400
|
-
name: name,
|
|
401
|
-
enabled: enabled,
|
|
402
|
-
}));
|
|
403
|
-
}
|
|
404
|
-
}
|
|
405
|
-
|
|
406
|
-
function onPermissionModeChange(e) {
|
|
407
|
-
var name = e.target.dataset.serverName;
|
|
408
|
-
sendMcpPermissionMode(store.get('currentSlug'), store.get('activeSessionId'), name, e.target.value);
|
|
409
|
-
}
|
|
410
|
-
|
|
411
|
-
export function sendMcpPermissionMode(projectSlug, sessionId, serverName, mode) {
|
|
412
|
-
var state = store.snap();
|
|
413
|
-
var applicability = mcpPermissionModeApplicability(state);
|
|
414
|
-
var ws = getWs();
|
|
415
|
-
if (!state.connected || state.currentSlug !== projectSlug || state.activeSessionId !== sessionId || !applicability.applicable || !ws || ws.readyState !== 1) {
|
|
416
|
-
showToast(applicability.applicable ? "Reconnect before changing AI review policy." : applicability.reason, "error");
|
|
417
|
-
return false;
|
|
418
|
-
}
|
|
419
|
-
var pending = pendingMap();
|
|
420
|
-
var pendingKeys = Object.keys(pending);
|
|
421
|
-
for (var i = 0; i < pendingKeys.length; i++) {
|
|
422
|
-
var item = pending[pendingKeys[i]];
|
|
423
|
-
if (item.projectSlug === projectSlug && item.sessionId === sessionId && item.serverName === serverName) return false;
|
|
424
|
-
}
|
|
425
|
-
var requestId = typeof crypto !== "undefined" && crypto.randomUUID ? crypto.randomUUID() : String(Date.now());
|
|
426
|
-
var key = pendingKey(projectSlug, sessionId, requestId);
|
|
427
|
-
var overrides = state.mcpPermissionModeOverrides || {};
|
|
428
|
-
var next = Object.assign({}, pending);
|
|
429
|
-
next[key] = {
|
|
430
|
-
projectSlug: projectSlug,
|
|
431
|
-
sessionId: sessionId,
|
|
432
|
-
requestId: requestId,
|
|
433
|
-
serverName: serverName,
|
|
434
|
-
mode: mode || "",
|
|
435
|
-
previousMode: overrides[serverName] || ""
|
|
436
|
-
};
|
|
437
|
-
setPendingMap(next);
|
|
438
|
-
_permissionModeTimers[requestId] = setTimeout(function () {
|
|
439
|
-
if (!clearPendingRequest(requestId)) return;
|
|
440
|
-
showToast("Changing AI review policy timed out.", "error");
|
|
441
|
-
}, MCP_PERMISSION_TIMEOUT_MS);
|
|
442
|
-
try {
|
|
443
|
-
var result = ws.send(JSON.stringify({
|
|
444
|
-
type: "set_mcp_permission_mode_override",
|
|
445
|
-
projectSlug: projectSlug,
|
|
446
|
-
sessionId: sessionId,
|
|
447
|
-
requestId: requestId,
|
|
448
|
-
serverName: serverName,
|
|
449
|
-
mode: mode || null,
|
|
450
|
-
}));
|
|
451
|
-
if (result === false) throw new Error("WebSocket send failed");
|
|
452
|
-
} catch (error) {
|
|
453
|
-
clearPendingRequest(requestId);
|
|
454
|
-
showToast("Failed to send the AI review policy change.", "error");
|
|
455
|
-
return false;
|
|
456
|
-
}
|
|
457
|
-
return true;
|
|
458
|
-
}
|
|
459
|
-
|
|
460
|
-
export function handleMcpPermissionModeResult(msg) {
|
|
461
|
-
var found = findPending(msg.requestId);
|
|
462
|
-
if (!found) return;
|
|
463
|
-
var pending = found.value;
|
|
464
|
-
if (msg.projectSlug && msg.projectSlug !== pending.projectSlug) return;
|
|
465
|
-
if (msg.sessionId !== pending.sessionId || msg.serverName !== pending.serverName) return;
|
|
466
|
-
clearPendingRequest(msg.requestId);
|
|
467
|
-
if (pending.projectSlug !== store.get('currentSlug') || pending.sessionId !== store.get('activeSessionId')) return;
|
|
468
|
-
if (!msg.ok) {
|
|
469
|
-
showToast(msg.error || "Failed to change AI review policy.", "error");
|
|
470
|
-
return;
|
|
471
|
-
}
|
|
472
|
-
store.set({ mcpPermissionModeOverrides: msg.mcpPermissionModeOverrides || {} });
|
|
68
|
+
if (msg.slug && msg.slug !== store.get('currentSlug')) return;
|
|
69
|
+
patch({ servers: msg.servers || [], loaded: true, extensionConnected: !!msg.extensionConnected, hostConnected: !!msg.hostConnected, extensionId: msg.extensionId || null });
|
|
70
|
+
}
|
|
71
|
+
export function setExtensionConnected(value) { patch({ extensionConnected: !!value }); }
|
|
72
|
+
export function getMcpServers() { return state().servers; }
|
|
73
|
+
export function handleMcpConnectionMessage(msg) {
|
|
74
|
+
if (msg.slug && msg.slug !== store.get('currentSlug')) return false;
|
|
75
|
+
if (msg.type === 'mcp_action_result') {
|
|
76
|
+
var pending = state().pending;
|
|
77
|
+
if (msg.requestId && (!pending || msg.requestId !== pending.requestId)) return true;
|
|
78
|
+
clearActionTimer();
|
|
79
|
+
patch({ pending: null, error: msg.ok ? '' : msg.error || 'Could not update connection.' });
|
|
80
|
+
if (msg.ok && msg.action === 'add') { store.set({ mcpConnectionDraft: '' }); navigate('detail', msg.id); }
|
|
81
|
+
if (msg.ok && msg.action === 'remove') navigate('list');
|
|
82
|
+
if (msg.ok && msg.action === 'enable') patch({ notice: 'Saved. Tool availability updates on the next turn; some runtimes may need a new session.' });
|
|
83
|
+
return true;
|
|
84
|
+
}
|
|
85
|
+
if (msg.type === 'mcp_login_ready') {
|
|
86
|
+
import('./vendor-login-browser.js').then(function (viewer) { if (msg.slug && msg.slug !== store.get('currentSlug')) return; viewer.openLoginBrowser(Object.assign({}, msg, { kind: 'mcp', vendor: 'mcp' })); }); return true;
|
|
87
|
+
}
|
|
88
|
+
if (msg.type === 'mcp_login_browser_frame' || msg.type === 'mcp_login_browser_status') {
|
|
89
|
+
import('./vendor-login-browser.js').then(function (viewer) { viewer.handleLoginBrowserMessage(Object.assign({}, msg, { type: msg.type.replace('mcp_login_', 'vendor_login_') })); }); return true;
|
|
90
|
+
}
|
|
91
|
+
if (msg.type === 'mcp_login_closed') {
|
|
92
|
+
var view = store.get('vendorLoginBrowser');
|
|
93
|
+
if (view && view.kind === 'mcp' && view.browserId === msg.browserId) import('./vendor-login-browser.js').then(function (viewer) { var live = store.get('vendorLoginBrowser'); if (live && live.browserId === msg.browserId) viewer.closeLoginBrowser(); });
|
|
94
|
+
return true;
|
|
95
|
+
}
|
|
96
|
+
return false;
|
|
473
97
|
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { store } from './store.js';
|
|
2
|
+
import { mountWireframeViewport } from './wireframe-viewport.js';
|
|
3
|
+
import { sketchBrand, sketchCredit } from './wireframe-style.js';
|
|
4
|
+
|
|
5
|
+
export function isPlantUmlFile(path) {
|
|
6
|
+
return /\.(puml|plantuml|pu|uml|salt)$/i.test(String(path || ""));
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export function cancelPlantUmlPreview() {
|
|
10
|
+
var job = store.get('plantUmlPreviewJob');
|
|
11
|
+
if (!job) return;
|
|
12
|
+
clearTimeout(job.timer);
|
|
13
|
+
job.observer.disconnect();
|
|
14
|
+
job.controller.abort();
|
|
15
|
+
store.set({ plantUmlPreviewJob: null });
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function renderPlantUmlPreview(body, source, path) {
|
|
19
|
+
cancelPlantUmlPreview();
|
|
20
|
+
body.innerHTML = "";
|
|
21
|
+
var preview = document.createElement("div");
|
|
22
|
+
preview.className = "file-viewer-plantuml-preview";
|
|
23
|
+
var status = document.createElement("div");
|
|
24
|
+
status.className = "file-tree-loading";
|
|
25
|
+
status.setAttribute("role", "status");
|
|
26
|
+
status.textContent = "Rendering diagram…";
|
|
27
|
+
preview.appendChild(status);
|
|
28
|
+
body.appendChild(preview);
|
|
29
|
+
|
|
30
|
+
function showError(message) {
|
|
31
|
+
status.className = "file-tree-error";
|
|
32
|
+
status.textContent = message + " Use Show source to inspect the file.";
|
|
33
|
+
}
|
|
34
|
+
if (!String(source || "").trim()) { showError("This diagram is empty."); return; }
|
|
35
|
+
if (new TextEncoder().encode(source).length > 100000) { showError("Diagram is too large to preview (100 KB maximum)."); return; }
|
|
36
|
+
|
|
37
|
+
var job = { controller: new AbortController(), observer: null, timer: null };
|
|
38
|
+
job.observer = new MutationObserver(function () {
|
|
39
|
+
if (!preview.isConnected && store.get('plantUmlPreviewJob') === job) cancelPlantUmlPreview();
|
|
40
|
+
});
|
|
41
|
+
job.observer.observe(body, { childList: true });
|
|
42
|
+
job.timer = setTimeout(function () {
|
|
43
|
+
if (store.get('plantUmlPreviewJob') !== job) return;
|
|
44
|
+
showError("The diagram preview timed out.");
|
|
45
|
+
cancelPlantUmlPreview();
|
|
46
|
+
}, 20000);
|
|
47
|
+
store.set({ plantUmlPreviewJob: job });
|
|
48
|
+
fetch("api/file/plantuml?path=" + encodeURIComponent(path), { signal: job.controller.signal, cache: "no-store" }).then(function (response) {
|
|
49
|
+
return response.text().then(function (text) {
|
|
50
|
+
if (!response.ok) throw new Error(text || "Unable to render this diagram.");
|
|
51
|
+
return text;
|
|
52
|
+
});
|
|
53
|
+
}).then(function (svg) {
|
|
54
|
+
if (store.get('plantUmlPreviewJob') !== job || !preview.isConnected) return;
|
|
55
|
+
if (svg.length > 8 * 1024 * 1024 || !/<svg[\s>]/i.test(svg)) throw new Error("The renderer returned an invalid diagram.");
|
|
56
|
+
var image = document.createElement("img");
|
|
57
|
+
image.alt = "Diagram preview of " + path;
|
|
58
|
+
image.draggable = false;
|
|
59
|
+
// An image context disables scripts, links and external SVG resources.
|
|
60
|
+
image.src = "data:image/svg+xml;charset=utf-8," + encodeURIComponent(svg);
|
|
61
|
+
image.addEventListener("error", function () { showError("The diagram image could not be displayed."); });
|
|
62
|
+
status.textContent = "";
|
|
63
|
+
var heading = document.createElement('header'); heading.className = 'sketch-tools';
|
|
64
|
+
heading.innerHTML = '<div class="sketch-brand">' + sketchBrand() + '</div>' + sketchCredit();
|
|
65
|
+
preview.appendChild(heading);
|
|
66
|
+
var toolbar = document.createElement('div');
|
|
67
|
+
var viewport = document.createElement('div'); viewport.className = 'wireframe-file-canvas';
|
|
68
|
+
preview.appendChild(toolbar); preview.appendChild(viewport); viewport.appendChild(image);
|
|
69
|
+
mountWireframeViewport(viewport, image, toolbar);
|
|
70
|
+
|
|
71
|
+
}).catch(function (error) {
|
|
72
|
+
if (store.get('plantUmlPreviewJob') === job && error.name !== "AbortError") showError(error.message);
|
|
73
|
+
}).finally(function () {
|
|
74
|
+
if (store.get('plantUmlPreviewJob') === job) cancelPlantUmlPreview();
|
|
75
|
+
});
|
|
76
|
+
}
|