clay-server 2.28.0-beta.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-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 +46 -3
- package/lib/server.js +6 -0
- package/lib/ws-schema.js +10 -0
- package/package.json +1 -1
|
@@ -0,0 +1,371 @@
|
|
|
1
|
+
var crypto = require("crypto");
|
|
2
|
+
|
|
3
|
+
// MCP Bridge module: manages remote MCP servers reported by the Chrome Extension.
|
|
4
|
+
// Creates proxy MCP server objects that forward tool calls over WebSocket.
|
|
5
|
+
// Follows the attachXxx(ctx) pattern per MODULE_MAP.md.
|
|
6
|
+
|
|
7
|
+
function attachMcp(ctx) {
|
|
8
|
+
var send = ctx.send;
|
|
9
|
+
var sendTo = ctx.sendTo;
|
|
10
|
+
var slug = ctx.slug;
|
|
11
|
+
var isMate = ctx.isMate;
|
|
12
|
+
var getEnabledMcpServers = ctx.getEnabledMcpServers;
|
|
13
|
+
var setEnabledMcpServers = ctx.setEnabledMcpServers;
|
|
14
|
+
var getExtensionWs = ctx.getExtensionWs;
|
|
15
|
+
var getExtensionId = ctx.getExtensionId || function () { return null; };
|
|
16
|
+
var localMcp = ctx.localMcp || null; // mcp-local instance for localhost clients
|
|
17
|
+
|
|
18
|
+
// Available servers reported by extension: { name -> { name, transport, tools, enabled } }
|
|
19
|
+
var _availableServers = {};
|
|
20
|
+
|
|
21
|
+
// Proxy MCP server objects for the SDK: { name -> sdkMcpServerConfig }
|
|
22
|
+
var _proxyServers = {};
|
|
23
|
+
|
|
24
|
+
// Pending tool calls: { callId -> { resolve, reject, timer } }
|
|
25
|
+
var _pendingCalls = {};
|
|
26
|
+
|
|
27
|
+
var TOOL_TIMEOUT_MS = 30000;
|
|
28
|
+
|
|
29
|
+
// ---------- Message Handler ----------
|
|
30
|
+
|
|
31
|
+
function handleMcpMessage(ws, msg) {
|
|
32
|
+
if (msg.type === "mcp_servers_available") {
|
|
33
|
+
handleServersAvailable(ws, msg);
|
|
34
|
+
return true;
|
|
35
|
+
}
|
|
36
|
+
if (msg.type === "mcp_tool_result") {
|
|
37
|
+
handleToolResult(msg);
|
|
38
|
+
return true;
|
|
39
|
+
}
|
|
40
|
+
if (msg.type === "mcp_tool_error") {
|
|
41
|
+
handleToolError(msg);
|
|
42
|
+
return true;
|
|
43
|
+
}
|
|
44
|
+
if (msg.type === "mcp_toggle_server") {
|
|
45
|
+
handleToggleServer(ws, msg);
|
|
46
|
+
return true;
|
|
47
|
+
}
|
|
48
|
+
return false;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
var _remoteHostConnected = false;
|
|
52
|
+
|
|
53
|
+
function handleServersAvailable(ws, msg) {
|
|
54
|
+
var servers = msg.servers || [];
|
|
55
|
+
_remoteHostConnected = !!msg.hostConnected;
|
|
56
|
+
_availableServers = {};
|
|
57
|
+
for (var i = 0; i < servers.length; i++) {
|
|
58
|
+
var s = servers[i];
|
|
59
|
+
_availableServers[s.name] = {
|
|
60
|
+
name: s.name,
|
|
61
|
+
transport: s.transport || "stdio",
|
|
62
|
+
tools: s.tools || [],
|
|
63
|
+
enabled: s.enabled !== false,
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// Rebuild proxy servers based on project-level enabled list
|
|
68
|
+
rebuildProxyServers();
|
|
69
|
+
|
|
70
|
+
// Broadcast updated state to all clients
|
|
71
|
+
broadcastMcpState();
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function handleToolResult(msg) {
|
|
75
|
+
var callId = msg.callId;
|
|
76
|
+
var pending = _pendingCalls[callId];
|
|
77
|
+
if (!pending) return;
|
|
78
|
+
if (pending.timer) clearTimeout(pending.timer);
|
|
79
|
+
delete _pendingCalls[callId];
|
|
80
|
+
pending.resolve(msg.result || { content: [{ type: "text", text: "(empty result)" }] });
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function handleToolError(msg) {
|
|
84
|
+
var callId = msg.callId;
|
|
85
|
+
var pending = _pendingCalls[callId];
|
|
86
|
+
if (!pending) return;
|
|
87
|
+
if (pending.timer) clearTimeout(pending.timer);
|
|
88
|
+
delete _pendingCalls[callId];
|
|
89
|
+
pending.reject(new Error(msg.error || "MCP tool call failed"));
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function handleToggleServer(ws, msg) {
|
|
93
|
+
var name = msg.name;
|
|
94
|
+
var enabled = !!msg.enabled;
|
|
95
|
+
|
|
96
|
+
var list = getEnabledMcpServers() || [];
|
|
97
|
+
var idx = list.indexOf(name);
|
|
98
|
+
|
|
99
|
+
if (enabled && idx === -1) {
|
|
100
|
+
list.push(name);
|
|
101
|
+
} else if (!enabled && idx !== -1) {
|
|
102
|
+
list.splice(idx, 1);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
setEnabledMcpServers(list);
|
|
106
|
+
rebuildProxyServers();
|
|
107
|
+
broadcastMcpState();
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// ---------- Proxy Server Builder ----------
|
|
111
|
+
|
|
112
|
+
function rebuildProxyServers() {
|
|
113
|
+
_proxyServers = {};
|
|
114
|
+
|
|
115
|
+
var sdk;
|
|
116
|
+
try {
|
|
117
|
+
sdk = require("@anthropic-ai/claude-agent-sdk");
|
|
118
|
+
} catch (e) {
|
|
119
|
+
console.error("[mcp-bridge] Failed to load SDK:", e.message);
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
var createSdkMcpServer = sdk.createSdkMcpServer;
|
|
124
|
+
var tool = sdk.tool;
|
|
125
|
+
if (!createSdkMcpServer || !tool) {
|
|
126
|
+
console.error("[mcp-bridge] SDK missing createSdkMcpServer or tool helper");
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
var z;
|
|
131
|
+
try { z = require("zod").z; } catch (e) {
|
|
132
|
+
try { z = require("zod"); } catch (e2) {
|
|
133
|
+
console.error("[mcp-bridge] Failed to load zod:", e2.message);
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
var enabledList = getEnabledMcpServers() || [];
|
|
139
|
+
|
|
140
|
+
// --- Remote servers (via Extension) ---
|
|
141
|
+
var serverNames = Object.keys(_availableServers);
|
|
142
|
+
for (var si = 0; si < serverNames.length; si++) {
|
|
143
|
+
var serverName = serverNames[si];
|
|
144
|
+
var serverInfo = _availableServers[serverName];
|
|
145
|
+
|
|
146
|
+
if (!serverInfo.enabled) continue;
|
|
147
|
+
if (enabledList.indexOf(serverName) === -1) continue;
|
|
148
|
+
|
|
149
|
+
var tools = [];
|
|
150
|
+
var serverTools = serverInfo.tools || [];
|
|
151
|
+
|
|
152
|
+
for (var ti = 0; ti < serverTools.length; ti++) {
|
|
153
|
+
var mcpTool = serverTools[ti];
|
|
154
|
+
var toolName = mcpTool.name;
|
|
155
|
+
var toolDesc = mcpTool.description || toolName;
|
|
156
|
+
var shape = buildZodShape(z, mcpTool.inputSchema);
|
|
157
|
+
|
|
158
|
+
tools.push(tool(
|
|
159
|
+
toolName,
|
|
160
|
+
toolDesc,
|
|
161
|
+
shape,
|
|
162
|
+
createToolHandler(serverName, toolName)
|
|
163
|
+
));
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
if (tools.length > 0) {
|
|
167
|
+
var mcpServer = createSdkMcpServer({
|
|
168
|
+
name: serverName,
|
|
169
|
+
version: "1.0.0",
|
|
170
|
+
tools: tools,
|
|
171
|
+
});
|
|
172
|
+
_proxyServers[serverName] = mcpServer;
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// --- Local servers (direct process, localhost only) ---
|
|
177
|
+
if (localMcp && localMcp.isReady()) {
|
|
178
|
+
var localServers = localMcp.getAvailableServers();
|
|
179
|
+
for (var li = 0; li < localServers.length; li++) {
|
|
180
|
+
var ls = localServers[li];
|
|
181
|
+
if (!ls.ready) continue;
|
|
182
|
+
if (_proxyServers[ls.name]) continue; // remote takes precedence if same name
|
|
183
|
+
if (enabledList.indexOf(ls.name) === -1) continue;
|
|
184
|
+
|
|
185
|
+
var localTools = [];
|
|
186
|
+
for (var lti = 0; lti < ls.tools.length; lti++) {
|
|
187
|
+
var lt = ls.tools[lti];
|
|
188
|
+
var ltShape = buildZodShape(z, lt.inputSchema);
|
|
189
|
+
localTools.push(tool(
|
|
190
|
+
lt.name,
|
|
191
|
+
lt.description || lt.name,
|
|
192
|
+
ltShape,
|
|
193
|
+
createLocalToolHandler(ls.name, lt.name)
|
|
194
|
+
));
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
if (localTools.length > 0) {
|
|
198
|
+
_proxyServers[ls.name] = createSdkMcpServer({
|
|
199
|
+
name: ls.name,
|
|
200
|
+
version: "1.0.0",
|
|
201
|
+
tools: localTools,
|
|
202
|
+
});
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
function createLocalToolHandler(serverName, toolName) {
|
|
209
|
+
return function (args) {
|
|
210
|
+
return localMcp.callTool(serverName, toolName, args);
|
|
211
|
+
};
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
function createToolHandler(serverName, toolName) {
|
|
215
|
+
return function (args) {
|
|
216
|
+
return new Promise(function (resolve, reject) {
|
|
217
|
+
var extWs = getExtensionWs();
|
|
218
|
+
if (!extWs || extWs.readyState !== 1) {
|
|
219
|
+
reject(new Error("Browser extension not connected. Cannot reach MCP server: " + serverName));
|
|
220
|
+
return;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
var callId = "mc_" + Date.now() + "_" + crypto.randomUUID().slice(0, 8);
|
|
224
|
+
|
|
225
|
+
var timer = setTimeout(function () {
|
|
226
|
+
delete _pendingCalls[callId];
|
|
227
|
+
reject(new Error("MCP tool call timed out after " + (TOOL_TIMEOUT_MS / 1000) + "s"));
|
|
228
|
+
}, TOOL_TIMEOUT_MS);
|
|
229
|
+
|
|
230
|
+
_pendingCalls[callId] = { resolve: resolve, reject: reject, timer: timer };
|
|
231
|
+
|
|
232
|
+
sendTo(extWs, {
|
|
233
|
+
type: "mcp_tool_call",
|
|
234
|
+
callId: callId,
|
|
235
|
+
server: serverName,
|
|
236
|
+
method: "tools/call",
|
|
237
|
+
params: { name: toolName, arguments: args },
|
|
238
|
+
});
|
|
239
|
+
});
|
|
240
|
+
};
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
// Build a Zod shape from MCP JSON Schema inputSchema
|
|
244
|
+
function buildZodShape(z, inputSchema) {
|
|
245
|
+
if (!inputSchema || !inputSchema.properties) return {};
|
|
246
|
+
var shape = {};
|
|
247
|
+
var props = inputSchema.properties;
|
|
248
|
+
var required = inputSchema.required || [];
|
|
249
|
+
var keys = Object.keys(props);
|
|
250
|
+
|
|
251
|
+
for (var i = 0; i < keys.length; i++) {
|
|
252
|
+
var k = keys[i];
|
|
253
|
+
var p = props[k];
|
|
254
|
+
var field;
|
|
255
|
+
|
|
256
|
+
if (p.type === "number" || p.type === "integer") {
|
|
257
|
+
field = z.number();
|
|
258
|
+
} else if (p.type === "boolean") {
|
|
259
|
+
field = z.boolean();
|
|
260
|
+
} else if (p.type === "array") {
|
|
261
|
+
field = z.array(z.any());
|
|
262
|
+
} else if (p.type === "object") {
|
|
263
|
+
field = z.record(z.any());
|
|
264
|
+
} else if (p.enum) {
|
|
265
|
+
field = z.enum(p.enum);
|
|
266
|
+
} else {
|
|
267
|
+
field = z.string();
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
if (p.description) field = field.describe(p.description);
|
|
271
|
+
if (required.indexOf(k) === -1) field = field.optional();
|
|
272
|
+
shape[k] = field;
|
|
273
|
+
}
|
|
274
|
+
return shape;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
// ---------- State Broadcasting ----------
|
|
278
|
+
|
|
279
|
+
function broadcastMcpState() {
|
|
280
|
+
var state = buildMcpState();
|
|
281
|
+
send(state);
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
function sendConnectionState(ws) {
|
|
285
|
+
sendTo(ws, buildMcpState());
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
function buildMcpState() {
|
|
289
|
+
var enabledList = getEnabledMcpServers() || [];
|
|
290
|
+
var servers = [];
|
|
291
|
+
var seen = {};
|
|
292
|
+
|
|
293
|
+
// Remote servers (from Extension)
|
|
294
|
+
var names = Object.keys(_availableServers);
|
|
295
|
+
for (var i = 0; i < names.length; i++) {
|
|
296
|
+
var name = names[i];
|
|
297
|
+
var info = _availableServers[name];
|
|
298
|
+
seen[name] = true;
|
|
299
|
+
servers.push({
|
|
300
|
+
name: name,
|
|
301
|
+
transport: info.transport,
|
|
302
|
+
toolCount: (info.tools || []).length,
|
|
303
|
+
extensionEnabled: info.enabled,
|
|
304
|
+
projectEnabled: enabledList.indexOf(name) !== -1,
|
|
305
|
+
source: "remote",
|
|
306
|
+
});
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
// Local servers
|
|
310
|
+
if (localMcp && localMcp.isReady()) {
|
|
311
|
+
var localServers = localMcp.getAvailableServers();
|
|
312
|
+
for (var j = 0; j < localServers.length; j++) {
|
|
313
|
+
var ls = localServers[j];
|
|
314
|
+
if (seen[ls.name]) continue;
|
|
315
|
+
servers.push({
|
|
316
|
+
name: ls.name,
|
|
317
|
+
transport: ls.transport || "stdio",
|
|
318
|
+
toolCount: ls.toolCount || 0,
|
|
319
|
+
extensionEnabled: true,
|
|
320
|
+
projectEnabled: enabledList.indexOf(ls.name) !== -1,
|
|
321
|
+
source: "local",
|
|
322
|
+
});
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
return {
|
|
327
|
+
type: "mcp_servers_state",
|
|
328
|
+
servers: servers,
|
|
329
|
+
hostConnected: _remoteHostConnected || !!(localMcp && localMcp.isReady()),
|
|
330
|
+
extensionId: getExtensionId() || null,
|
|
331
|
+
};
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
// ---------- Public API ----------
|
|
335
|
+
|
|
336
|
+
function getMcpServers() {
|
|
337
|
+
return _proxyServers;
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
function cancelAllPending() {
|
|
341
|
+
var ids = Object.keys(_pendingCalls);
|
|
342
|
+
for (var i = 0; i < ids.length; i++) {
|
|
343
|
+
var pending = _pendingCalls[ids[i]];
|
|
344
|
+
if (pending.timer) clearTimeout(pending.timer);
|
|
345
|
+
pending.reject(new Error("MCP bridge disconnected"));
|
|
346
|
+
}
|
|
347
|
+
_pendingCalls = {};
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
function handleExtensionDisconnect() {
|
|
351
|
+
cancelAllPending();
|
|
352
|
+
_availableServers = {};
|
|
353
|
+
_proxyServers = {};
|
|
354
|
+
broadcastMcpState();
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
function rebuildAndBroadcast() {
|
|
358
|
+
rebuildProxyServers();
|
|
359
|
+
broadcastMcpState();
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
return {
|
|
363
|
+
handleMcpMessage: handleMcpMessage,
|
|
364
|
+
getMcpServers: getMcpServers,
|
|
365
|
+
sendConnectionState: sendConnectionState,
|
|
366
|
+
handleExtensionDisconnect: handleExtensionDisconnect,
|
|
367
|
+
rebuildAndBroadcast: rebuildAndBroadcast,
|
|
368
|
+
};
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
module.exports = { attachMcp: attachMcp };
|
|
@@ -210,6 +210,7 @@ function attachUserMessage(ctx) {
|
|
|
210
210
|
// --- Browser Extension ---
|
|
211
211
|
if (msg.type === "browser_tab_list") {
|
|
212
212
|
browserState._extensionWs = ws; // Track which client has the extension
|
|
213
|
+
if (msg.extensionId) browserState._extensionId = msg.extensionId;
|
|
213
214
|
var tabs = msg.tabs || [];
|
|
214
215
|
browserState._browserTabList = {};
|
|
215
216
|
for (var bti = 0; bti < tabs.length; bti++) {
|
package/lib/project.js
CHANGED
|
@@ -26,6 +26,8 @@ var { attachFilesystem } = require("./project-filesystem");
|
|
|
26
26
|
var { attachSessions } = require("./project-sessions");
|
|
27
27
|
var { attachUserMessage } = require("./project-user-message");
|
|
28
28
|
var { attachConnection } = require("./project-connection");
|
|
29
|
+
var { attachMcp } = require("./project-mcp");
|
|
30
|
+
var { createLocalMcp } = require("./mcp-local");
|
|
29
31
|
// project-notifications is attached globally in server.js, passed via opts.notificationsModule
|
|
30
32
|
|
|
31
33
|
// --- Context Sources persistence ---
|
|
@@ -218,22 +220,24 @@ function createProjectContext(opts) {
|
|
|
218
220
|
// --- Per-project clients ---
|
|
219
221
|
var clients = new Set();
|
|
220
222
|
|
|
221
|
-
// --- Browser extension state ---
|
|
222
|
-
var _browserTabList = {}; // tabId -> { id, url, title, favIconUrl }
|
|
223
|
+
// --- Browser extension state (shared mutable object) ---
|
|
223
224
|
var _pendingDebateProposals = {}; // proposalId -> { resolve, briefData }
|
|
224
|
-
var _extensionWs = null; // WebSocket of the client with the Chrome extension
|
|
225
225
|
var _extToken = crypto.randomUUID(); // Auth token for MCP server bridge
|
|
226
|
-
var
|
|
226
|
+
var browserState = {
|
|
227
|
+
_browserTabList: {},
|
|
228
|
+
_extensionWs: null,
|
|
229
|
+
pendingExtensionRequests: {}
|
|
230
|
+
};
|
|
227
231
|
|
|
228
232
|
function sendExtensionCommand(ws, command, args, timeout) {
|
|
229
233
|
return new Promise(function(resolve) {
|
|
230
234
|
var requestId = crypto.randomUUID();
|
|
231
235
|
var ms = timeout || 3000;
|
|
232
236
|
var timer = setTimeout(function() {
|
|
233
|
-
delete pendingExtensionRequests[requestId];
|
|
237
|
+
delete browserState.pendingExtensionRequests[requestId];
|
|
234
238
|
resolve(null);
|
|
235
239
|
}, ms);
|
|
236
|
-
pendingExtensionRequests[requestId] = { resolve: resolve, timer: timer };
|
|
240
|
+
browserState.pendingExtensionRequests[requestId] = { resolve: resolve, timer: timer };
|
|
237
241
|
sendTo(ws, {
|
|
238
242
|
type: "extension_command",
|
|
239
243
|
command: command,
|
|
@@ -245,10 +249,10 @@ function createProjectContext(opts) {
|
|
|
245
249
|
|
|
246
250
|
// Send extension command via the tracked extension client (for MCP bridge)
|
|
247
251
|
function sendExtensionCommandAny(command, args, timeout) {
|
|
248
|
-
if (!_extensionWs || _extensionWs.readyState !== 1) {
|
|
252
|
+
if (!browserState._extensionWs || browserState._extensionWs.readyState !== 1) {
|
|
249
253
|
return Promise.reject(new Error("Browser extension not connected"));
|
|
250
254
|
}
|
|
251
|
-
return sendExtensionCommand(_extensionWs, command, args, timeout);
|
|
255
|
+
return sendExtensionCommand(browserState._extensionWs, command, args, timeout);
|
|
252
256
|
}
|
|
253
257
|
|
|
254
258
|
function requestTabContext(ws, tabId) {
|
|
@@ -405,6 +409,29 @@ function createProjectContext(opts) {
|
|
|
405
409
|
// before the SDK has warmed up and fired system/init.
|
|
406
410
|
if (sm._savedDefaultModel) sm.currentModel = sm._savedDefaultModel;
|
|
407
411
|
|
|
412
|
+
// --- Local MCP (direct process management for localhost clients) ---
|
|
413
|
+
var _localMcp = createLocalMcp();
|
|
414
|
+
|
|
415
|
+
// --- MCP bridge (remote MCP servers via Chrome Extension) ---
|
|
416
|
+
var _mcp = attachMcp({
|
|
417
|
+
send: send,
|
|
418
|
+
sendTo: sendTo,
|
|
419
|
+
slug: slug,
|
|
420
|
+
isMate: isMate,
|
|
421
|
+
getExtensionWs: function () { return browserState._extensionWs; },
|
|
422
|
+
getExtensionId: function () { return browserState._extensionId || null; },
|
|
423
|
+
getEnabledMcpServers: function () {
|
|
424
|
+
return typeof opts.onGetProjectMcpServers === "function"
|
|
425
|
+
? opts.onGetProjectMcpServers(slug) : [];
|
|
426
|
+
},
|
|
427
|
+
setEnabledMcpServers: function (servers) {
|
|
428
|
+
if (typeof opts.onSetProjectMcpServers === "function") {
|
|
429
|
+
opts.onSetProjectMcpServers(slug, servers);
|
|
430
|
+
}
|
|
431
|
+
},
|
|
432
|
+
localMcp: _localMcp,
|
|
433
|
+
});
|
|
434
|
+
|
|
408
435
|
// --- SDK bridge ---
|
|
409
436
|
var sdk = createSDKBridge({
|
|
410
437
|
cwd: cwd,
|
|
@@ -445,7 +472,7 @@ function createProjectContext(opts) {
|
|
|
445
472
|
try {
|
|
446
473
|
var browserMcp = require("./browser-mcp-server");
|
|
447
474
|
var mcpConfig = browserMcp.create(sendExtensionCommandAny, function () {
|
|
448
|
-
return Object.values(_browserTabList || {});
|
|
475
|
+
return Object.values(browserState._browserTabList || {});
|
|
449
476
|
}, {
|
|
450
477
|
watchTab: function (tabId) {
|
|
451
478
|
var key = "tab:" + tabId;
|
|
@@ -479,6 +506,7 @@ function createProjectContext(opts) {
|
|
|
479
506
|
|
|
480
507
|
return Object.keys(servers).length > 0 ? servers : undefined;
|
|
481
508
|
})(),
|
|
509
|
+
getRemoteMcpServers: function () { return _mcp.getMcpServers(); },
|
|
482
510
|
onProcessingChanged: onProcessingChanged,
|
|
483
511
|
onTurnDone: isMate ? function (session, preview) {
|
|
484
512
|
digestDmTurn(session, preview);
|
|
@@ -549,6 +577,14 @@ function createProjectContext(opts) {
|
|
|
549
577
|
// --- WS connection handler (delegated to project-connection.js) ---
|
|
550
578
|
function handleConnection(ws, wsUser) {
|
|
551
579
|
_connection.handleConnection(ws, wsUser, handleMessage, handleDisconnection);
|
|
580
|
+
|
|
581
|
+
// Initialize local MCP when a localhost client connects
|
|
582
|
+
if (ws._clayLocal && _localMcp && !_localMcp.isReady()) {
|
|
583
|
+
_localMcp.initialize(function () {
|
|
584
|
+
// Rebuild proxy servers and broadcast state when local servers are ready
|
|
585
|
+
_mcp.rebuildAndBroadcast();
|
|
586
|
+
});
|
|
587
|
+
}
|
|
552
588
|
}
|
|
553
589
|
|
|
554
590
|
// --- WS message handler ---
|
|
@@ -697,6 +733,9 @@ function createProjectContext(opts) {
|
|
|
697
733
|
return;
|
|
698
734
|
}
|
|
699
735
|
|
|
736
|
+
// --- MCP bridge (remote MCP servers via extension) ---
|
|
737
|
+
if (_mcp.handleMcpMessage(ws, msg)) return;
|
|
738
|
+
|
|
700
739
|
// --- Knowledge file management (delegated to project-knowledge.js) ---
|
|
701
740
|
if (_knowledge.handleKnowledgeMessage(ws, msg)) return;
|
|
702
741
|
|
|
@@ -910,7 +949,7 @@ function createProjectContext(opts) {
|
|
|
910
949
|
imagesDir: imagesDir,
|
|
911
950
|
onProcessingChanged: onProcessingChanged,
|
|
912
951
|
_loop: _loop,
|
|
913
|
-
browserState:
|
|
952
|
+
browserState: browserState,
|
|
914
953
|
sendExtensionCommandAny: sendExtensionCommandAny,
|
|
915
954
|
requestTabContext: requestTabContext,
|
|
916
955
|
scheduleMessage: scheduleMessage,
|
|
@@ -964,7 +1003,7 @@ function createProjectContext(opts) {
|
|
|
964
1003
|
getOsUserInfoForReq: getOsUserInfoForReq,
|
|
965
1004
|
sendExtensionCommandAny: sendExtensionCommandAny,
|
|
966
1005
|
_extToken: _extToken,
|
|
967
|
-
_browserTabList: _browserTabList,
|
|
1006
|
+
_browserTabList: browserState._browserTabList,
|
|
968
1007
|
});
|
|
969
1008
|
var handleHTTP = _http.handleHTTP;
|
|
970
1009
|
|
|
@@ -986,6 +1025,7 @@ function createProjectContext(opts) {
|
|
|
986
1025
|
sendTo: sendTo,
|
|
987
1026
|
opts: opts,
|
|
988
1027
|
_loop: _loop,
|
|
1028
|
+
_mcp: _mcp,
|
|
989
1029
|
_notifications: _notifications,
|
|
990
1030
|
hydrateImageRefs: hydrateImageRefs,
|
|
991
1031
|
broadcastClientCount: broadcastClientCount,
|
package/lib/public/app.js
CHANGED
|
@@ -33,6 +33,7 @@ import { initTools, resetToolState, saveToolState, restoreToolState, renderAskUs
|
|
|
33
33
|
import { initServerSettings, updateSettingsStats, updateSettingsModels, updateDaemonConfig, handleSetPinResult, handleKeepAwakeChanged, handleAutoContinueChanged, handleRestartResult, handleShutdownResult, handleSharedEnv, handleSharedEnvSaved, handleGlobalClaudeMdRead, handleGlobalClaudeMdWrite } from './modules/server-settings.js';
|
|
34
34
|
import { initProjectSettings, handleInstructionsRead, handleInstructionsWrite, handleProjectEnv, handleProjectEnvSaved, isProjectSettingsOpen, handleProjectSharedEnv, handleProjectSharedEnvSaved, handleProjectOwnerChanged } from './modules/project-settings.js';
|
|
35
35
|
import { initSkills, handleSkillInstalled, handleSkillUninstalled } from './modules/skills.js';
|
|
36
|
+
import { initMcp } from './modules/mcp-ui.js';
|
|
36
37
|
import { initScheduler, resetScheduler, handleLoopRegistryUpdated, handleScheduleRunStarted, handleScheduleRunFinished, handleLoopScheduled, openSchedulerToTab, isSchedulerOpen, closeScheduler, enterCraftingMode, exitCraftingMode, handleLoopRegistryFiles, getUpcomingSchedules } from './modules/scheduler.js';
|
|
37
38
|
import { initAsciiLogo, startLogoAnimation, stopLogoAnimation } from './modules/ascii-logo.js';
|
|
38
39
|
import { initPlaybook, openPlaybook, getPlaybooks, getPlaybookForTip, isCompleted as isPlaybookCompleted } from './modules/playbook.js';
|
|
@@ -1080,6 +1081,9 @@ import { initDebate, handleDebatePreparing, handleDebateStarted, handleDebateRes
|
|
|
1080
1081
|
|
|
1081
1082
|
// --- Ralph Preview Modal (delegated to app-loop-ui.js) ---
|
|
1082
1083
|
|
|
1084
|
+
// --- MCP Servers ---
|
|
1085
|
+
initMcp();
|
|
1086
|
+
|
|
1083
1087
|
// --- Skills ---
|
|
1084
1088
|
initSkills({
|
|
1085
1089
|
get ws() { return ws; },
|