@nextclaw/mcp 0.1.64 → 0.1.66

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.
Files changed (3) hide show
  1. package/dist/index.d.ts +186 -176
  2. package/dist/index.js +563 -679
  3. package/package.json +3 -4
package/dist/index.js CHANGED
@@ -1,723 +1,607 @@
1
- // src/types.ts
1
+ import { Agent, fetch } from "undici";
2
+ import { Client } from "@modelcontextprotocol/sdk/client/index.js";
3
+ import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
4
+ import { SSEClientTransport } from "@modelcontextprotocol/sdk/client/sse.js";
5
+ import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
6
+ //#region src/types.ts
2
7
  function normalizeMcpServerName(name) {
3
- const normalized = name.trim();
4
- if (!normalized) {
5
- throw new Error("MCP server name is required.");
6
- }
7
- if (!/^[A-Za-z0-9._-]+$/.test(normalized)) {
8
- throw new Error("MCP server name may only contain letters, numbers, dot, underscore, and dash.");
9
- }
10
- return normalized;
8
+ const normalized = name.trim();
9
+ if (!normalized) throw new Error("MCP server name is required.");
10
+ if (!/^[A-Za-z0-9._-]+$/.test(normalized)) throw new Error("MCP server name may only contain letters, numbers, dot, underscore, and dash.");
11
+ return normalized;
11
12
  }
12
13
  function readDefaultAgentId(config) {
13
- return config.agents.list.find((entry) => entry.default)?.id?.trim() || config.agents.list[0]?.id?.trim() || "main";
14
+ return config.agents.list.find((entry) => entry.default)?.id?.trim() || config.agents.list[0]?.id?.trim() || "main";
14
15
  }
15
16
  function isServerAccessibleToAgent(params) {
16
- if (params.scope.allAgents) {
17
- return true;
18
- }
19
- const targetAgentId = params.agentId?.trim() || readDefaultAgentId(params.config);
20
- const explicitAgents = params.scope.agents.map((agentId) => agentId.trim()).filter(Boolean);
21
- if (explicitAgents.length > 0) {
22
- return explicitAgents.includes(targetAgentId);
23
- }
24
- return targetAgentId === readDefaultAgentId(params.config);
17
+ if (params.scope.allAgents) return true;
18
+ const targetAgentId = params.agentId?.trim() || readDefaultAgentId(params.config);
19
+ const explicitAgents = params.scope.agents.map((agentId) => agentId.trim()).filter(Boolean);
20
+ if (explicitAgents.length > 0) return explicitAgents.includes(targetAgentId);
21
+ return targetAgentId === readDefaultAgentId(params.config);
25
22
  }
26
23
  function buildQualifiedMcpToolName(serverName, toolName) {
27
- return `mcp_${sanitizeMcpSegment(serverName)}__${sanitizeMcpSegment(toolName)}`;
24
+ return `mcp_${sanitizeMcpSegment(serverName)}__${sanitizeMcpSegment(toolName)}`;
28
25
  }
29
26
  function sanitizeMcpSegment(value) {
30
- const sanitized = value.trim().replace(/[^A-Za-z0-9_]+/g, "_").replace(/^_+|_+$/g, "");
31
- return sanitized || "unnamed";
27
+ return value.trim().replace(/[^A-Za-z0-9_]+/g, "_").replace(/^_+|_+$/g, "") || "unnamed";
32
28
  }
33
-
34
- // src/config/mcp-config-normalizer.ts
29
+ //#endregion
30
+ //#region src/config/mcp-config-normalizer.ts
35
31
  function listMcpServers(config) {
36
- return Object.entries(config.mcp.servers).map(([name, definition]) => ({
37
- name: normalizeMcpServerName(name),
38
- definition
39
- })).sort((left, right) => left.name.localeCompare(right.name));
32
+ return Object.entries(config.mcp.servers).map(([name, definition]) => ({
33
+ name: normalizeMcpServerName(name),
34
+ definition
35
+ })).sort((left, right) => left.name.localeCompare(right.name));
40
36
  }
41
37
  function getMcpServer(config, name) {
42
- const normalizedName = normalizeMcpServerName(name);
43
- const definition = config.mcp.servers[normalizedName];
44
- if (!definition) {
45
- return void 0;
46
- }
47
- return {
48
- name: normalizedName,
49
- definition
50
- };
38
+ const normalizedName = normalizeMcpServerName(name);
39
+ const definition = config.mcp.servers[normalizedName];
40
+ if (!definition) return;
41
+ return {
42
+ name: normalizedName,
43
+ definition
44
+ };
51
45
  }
52
-
53
- // src/client/mcp-client-factory.ts
54
- import { Agent, fetch as undiciFetch } from "undici";
55
- import { Client } from "@modelcontextprotocol/sdk/client/index.js";
56
- import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
57
- import { SSEClientTransport } from "@modelcontextprotocol/sdk/client/sse.js";
58
- import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
46
+ //#endregion
47
+ //#region src/client/mcp-client-factory.ts
59
48
  function createClient() {
60
- return new Client({
61
- name: "nextclaw-mcp-client",
62
- version: "0.1.0"
63
- });
49
+ return new Client({
50
+ name: "nextclaw-mcp-client",
51
+ version: "0.1.0"
52
+ });
64
53
  }
65
54
  function buildFetch(transport) {
66
- const timeoutMs = transport.timeoutMs;
67
- const insecureAgent = transport.verifyTls ? null : new Agent({
68
- connect: {
69
- rejectUnauthorized: false
70
- }
71
- });
72
- return async (input, init) => {
73
- const controller = new AbortController();
74
- const timeout = setTimeout(() => controller.abort(), timeoutMs);
75
- try {
76
- return await undiciFetch(input, {
77
- ...init,
78
- dispatcher: insecureAgent ?? init?.dispatcher,
79
- signal: controller.signal
80
- });
81
- } finally {
82
- clearTimeout(timeout);
83
- }
84
- };
55
+ const timeoutMs = transport.timeoutMs;
56
+ const insecureAgent = transport.verifyTls ? null : new Agent({ connect: { rejectUnauthorized: false } });
57
+ return async (input, init) => {
58
+ const controller = new AbortController();
59
+ const timeout = setTimeout(() => controller.abort(), timeoutMs);
60
+ try {
61
+ return await fetch(input, {
62
+ ...init,
63
+ dispatcher: insecureAgent ?? init?.dispatcher,
64
+ signal: controller.signal
65
+ });
66
+ } finally {
67
+ clearTimeout(timeout);
68
+ }
69
+ };
85
70
  }
86
71
  function createStdioTransport(transport) {
87
- return new StdioClientTransport({
88
- command: transport.command,
89
- args: transport.args,
90
- cwd: transport.cwd,
91
- env: Object.keys(transport.env).length > 0 ? transport.env : void 0,
92
- stderr: transport.stderr
93
- });
72
+ return new StdioClientTransport({
73
+ command: transport.command,
74
+ args: transport.args,
75
+ cwd: transport.cwd,
76
+ env: Object.keys(transport.env).length > 0 ? transport.env : void 0,
77
+ stderr: transport.stderr
78
+ });
94
79
  }
95
80
  function createHttpTransport(transport) {
96
- return new StreamableHTTPClientTransport(new URL(transport.url), {
97
- requestInit: {
98
- headers: transport.headers
99
- },
100
- fetch: buildFetch(transport)
101
- });
81
+ return new StreamableHTTPClientTransport(new URL(transport.url), {
82
+ requestInit: { headers: transport.headers },
83
+ fetch: buildFetch(transport)
84
+ });
102
85
  }
103
86
  function createSseTransport(transport) {
104
- return new SSEClientTransport(new URL(transport.url), {
105
- requestInit: {
106
- headers: transport.headers
107
- },
108
- eventSourceInit: {
109
- fetch: buildFetch(transport),
110
- headers: transport.headers
111
- },
112
- fetch: buildFetch(transport)
113
- });
87
+ return new SSEClientTransport(new URL(transport.url), {
88
+ requestInit: { headers: transport.headers },
89
+ eventSourceInit: {
90
+ fetch: buildFetch(transport),
91
+ headers: transport.headers
92
+ },
93
+ fetch: buildFetch(transport)
94
+ });
114
95
  }
115
96
  var McpClientFactory = class {
116
- constructor(_config) {
117
- this._config = _config;
118
- void this._config;
119
- }
120
- create(record) {
121
- const transport = (() => {
122
- switch (record.definition.transport.type) {
123
- case "stdio":
124
- return createStdioTransport(record.definition.transport);
125
- case "http":
126
- return createHttpTransport(record.definition.transport);
127
- case "sse":
128
- return createSseTransport(record.definition.transport);
129
- default:
130
- throw new Error(`Unsupported MCP transport: ${String(record.definition.transport.type)}`);
131
- }
132
- })();
133
- return {
134
- client: createClient(),
135
- transport
136
- };
137
- }
97
+ constructor(_config) {
98
+ this._config = _config;
99
+ this._config;
100
+ }
101
+ create(record) {
102
+ const transport = (() => {
103
+ switch (record.definition.transport.type) {
104
+ case "stdio": return createStdioTransport(record.definition.transport);
105
+ case "http": return createHttpTransport(record.definition.transport);
106
+ case "sse": return createSseTransport(record.definition.transport);
107
+ default: throw new Error(`Unsupported MCP transport: ${String(record.definition.transport.type)}`);
108
+ }
109
+ })();
110
+ return {
111
+ client: createClient(),
112
+ transport
113
+ };
114
+ }
138
115
  };
139
-
140
- // src/lifecycle/mcp-server-lifecycle-manager.ts
116
+ //#endregion
117
+ //#region src/lifecycle/mcp-server-lifecycle-manager.ts
141
118
  var McpServerLifecycleManager = class {
142
- constructor(options) {
143
- this.options = options;
144
- }
145
- pendingConnections = /* @__PURE__ */ new Map();
146
- readyConnections = /* @__PURE__ */ new Map();
147
- async warmServer(record) {
148
- const connection = await this.ensureConnection(record);
149
- return this.stripConnection(connection);
150
- }
151
- getCachedCatalog(serverName) {
152
- return this.readyConnections.get(serverName)?.tools ?? [];
153
- }
154
- getCachedState(serverName) {
155
- const ready = this.readyConnections.get(serverName);
156
- if (!ready) {
157
- return void 0;
158
- }
159
- return this.stripConnection(ready);
160
- }
161
- async callTool(record, toolName, args) {
162
- const connection = await this.ensureConnection(record);
163
- const result = await connection.client.callTool({
164
- name: toolName,
165
- arguments: args
166
- });
167
- return this.normalizeToolResult(result);
168
- }
169
- async closeAll() {
170
- const pending = await Promise.allSettled(this.pendingConnections.values());
171
- for (const result of pending) {
172
- if (result.status === "fulfilled") {
173
- this.readyConnections.set(result.value.record.name, result.value);
174
- }
175
- }
176
- for (const connection of this.readyConnections.values()) {
177
- await connection.transport.close().catch(() => {
178
- });
179
- }
180
- this.pendingConnections.clear();
181
- this.readyConnections.clear();
182
- }
183
- async closeServer(serverName) {
184
- const ready = this.readyConnections.get(serverName);
185
- this.readyConnections.delete(serverName);
186
- const pending = this.pendingConnections.get(serverName);
187
- this.pendingConnections.delete(serverName);
188
- if (ready) {
189
- await ready.transport.close().catch(() => {
190
- });
191
- }
192
- if (!pending) {
193
- return;
194
- }
195
- const result = await Promise.allSettled([pending]);
196
- const settled = result[0];
197
- if (settled?.status === "fulfilled") {
198
- await settled.value.transport.close().catch(() => {
199
- });
200
- }
201
- this.readyConnections.delete(serverName);
202
- this.pendingConnections.delete(serverName);
203
- }
204
- async ensureConnection(record) {
205
- const ready = this.readyConnections.get(record.name);
206
- if (ready) {
207
- return ready;
208
- }
209
- const existing = this.pendingConnections.get(record.name);
210
- if (existing) {
211
- return existing;
212
- }
213
- const task = this.connect(record);
214
- this.pendingConnections.set(record.name, task);
215
- try {
216
- const connection = await task;
217
- this.readyConnections.set(record.name, connection);
218
- this.pendingConnections.delete(record.name);
219
- return connection;
220
- } catch (error) {
221
- this.pendingConnections.delete(record.name);
222
- throw error;
223
- }
224
- }
225
- async connect(record) {
226
- const config = this.options.getConfig();
227
- const clientFactory = this.options.clientFactory ?? new McpClientFactory(config);
228
- const { client, transport } = clientFactory.create(record);
229
- await client.connect(transport);
230
- const listedTools = await client.listTools();
231
- const tools = listedTools.tools.map((tool) => ({
232
- qualifiedName: buildQualifiedMcpToolName(record.name, tool.name),
233
- serverName: record.name,
234
- toolName: tool.name,
235
- description: tool.description,
236
- parameters: tool.inputSchema
237
- }));
238
- const now = (this.options.now ?? (() => /* @__PURE__ */ new Date()))().toISOString();
239
- return {
240
- record,
241
- tools,
242
- clientReady: true,
243
- lastReadyAt: now,
244
- client,
245
- transport
246
- };
247
- }
248
- stripConnection(connection) {
249
- return {
250
- record: connection.record,
251
- tools: connection.tools,
252
- clientReady: connection.clientReady,
253
- lastReadyAt: connection.lastReadyAt,
254
- lastError: connection.lastError
255
- };
256
- }
257
- normalizeToolResult(result) {
258
- if ("toolResult" in result) {
259
- return result.toolResult;
260
- }
261
- return {
262
- content: result.content,
263
- structuredContent: result.structuredContent,
264
- isError: result.isError ?? false
265
- };
266
- }
119
+ pendingConnections = /* @__PURE__ */ new Map();
120
+ readyConnections = /* @__PURE__ */ new Map();
121
+ constructor(options) {
122
+ this.options = options;
123
+ }
124
+ async warmServer(record) {
125
+ const connection = await this.ensureConnection(record);
126
+ return this.stripConnection(connection);
127
+ }
128
+ getCachedCatalog(serverName) {
129
+ return this.readyConnections.get(serverName)?.tools ?? [];
130
+ }
131
+ getCachedState(serverName) {
132
+ const ready = this.readyConnections.get(serverName);
133
+ if (!ready) return;
134
+ return this.stripConnection(ready);
135
+ }
136
+ async callTool(record, toolName, args) {
137
+ const result = await (await this.ensureConnection(record)).client.callTool({
138
+ name: toolName,
139
+ arguments: args
140
+ });
141
+ return this.normalizeToolResult(result);
142
+ }
143
+ async closeAll() {
144
+ const pending = await Promise.allSettled(this.pendingConnections.values());
145
+ for (const result of pending) if (result.status === "fulfilled") this.readyConnections.set(result.value.record.name, result.value);
146
+ for (const connection of this.readyConnections.values()) await connection.transport.close().catch(() => {});
147
+ this.pendingConnections.clear();
148
+ this.readyConnections.clear();
149
+ }
150
+ async closeServer(serverName) {
151
+ const ready = this.readyConnections.get(serverName);
152
+ this.readyConnections.delete(serverName);
153
+ const pending = this.pendingConnections.get(serverName);
154
+ this.pendingConnections.delete(serverName);
155
+ if (ready) await ready.transport.close().catch(() => {});
156
+ if (!pending) return;
157
+ const settled = (await Promise.allSettled([pending]))[0];
158
+ if (settled?.status === "fulfilled") await settled.value.transport.close().catch(() => {});
159
+ this.readyConnections.delete(serverName);
160
+ this.pendingConnections.delete(serverName);
161
+ }
162
+ async ensureConnection(record) {
163
+ const ready = this.readyConnections.get(record.name);
164
+ if (ready) return ready;
165
+ const existing = this.pendingConnections.get(record.name);
166
+ if (existing) return existing;
167
+ const task = this.connect(record);
168
+ this.pendingConnections.set(record.name, task);
169
+ try {
170
+ const connection = await task;
171
+ this.readyConnections.set(record.name, connection);
172
+ this.pendingConnections.delete(record.name);
173
+ return connection;
174
+ } catch (error) {
175
+ this.pendingConnections.delete(record.name);
176
+ throw error;
177
+ }
178
+ }
179
+ async connect(record) {
180
+ const config = this.options.getConfig();
181
+ const { client, transport } = (this.options.clientFactory ?? new McpClientFactory(config)).create(record);
182
+ await client.connect(transport);
183
+ return {
184
+ record,
185
+ tools: (await client.listTools()).tools.map((tool) => ({
186
+ qualifiedName: buildQualifiedMcpToolName(record.name, tool.name),
187
+ serverName: record.name,
188
+ toolName: tool.name,
189
+ description: tool.description,
190
+ parameters: tool.inputSchema
191
+ })),
192
+ clientReady: true,
193
+ lastReadyAt: (this.options.now ?? (() => /* @__PURE__ */ new Date()))().toISOString(),
194
+ client,
195
+ transport
196
+ };
197
+ }
198
+ stripConnection(connection) {
199
+ return {
200
+ record: connection.record,
201
+ tools: connection.tools,
202
+ clientReady: connection.clientReady,
203
+ lastReadyAt: connection.lastReadyAt,
204
+ lastError: connection.lastError
205
+ };
206
+ }
207
+ normalizeToolResult(result) {
208
+ if ("toolResult" in result) return result.toolResult;
209
+ return {
210
+ content: result.content,
211
+ structuredContent: result.structuredContent,
212
+ isError: result.isError ?? false
213
+ };
214
+ }
267
215
  };
268
-
269
- // src/registry/mcp-registry-service.ts
216
+ //#endregion
217
+ //#region src/registry/mcp-registry-service.ts
270
218
  var McpRegistryService = class {
271
- constructor(options) {
272
- this.options = options;
273
- this.lifecycleManager = this.options.lifecycleManager ?? new McpServerLifecycleManager({
274
- getConfig: this.options.getConfig
275
- });
276
- }
277
- lifecycleManager;
278
- listServers() {
279
- return listMcpServers(this.options.getConfig());
280
- }
281
- getServer(name) {
282
- return getMcpServer(this.options.getConfig(), name);
283
- }
284
- async prewarmEnabledServers() {
285
- const results = await Promise.all(
286
- this.listServers().filter((record) => record.definition.enabled).map(async (record) => {
287
- try {
288
- const state = await this.lifecycleManager.warmServer(record);
289
- return {
290
- name: record.name,
291
- ok: true,
292
- toolCount: state.tools.length
293
- };
294
- } catch (error) {
295
- return {
296
- name: record.name,
297
- ok: false,
298
- toolCount: 0,
299
- error: toErrorMessage(error)
300
- };
301
- }
302
- })
303
- );
304
- return results.sort((left, right) => left.name.localeCompare(right.name));
305
- }
306
- async warmServer(name) {
307
- const server = this.getServer(name);
308
- if (!server) {
309
- throw new Error(`Unknown MCP server: ${name}`);
310
- }
311
- try {
312
- const state = await this.lifecycleManager.warmServer(server);
313
- return {
314
- name: server.name,
315
- ok: true,
316
- toolCount: state.tools.length
317
- };
318
- } catch (error) {
319
- return {
320
- name: server.name,
321
- ok: false,
322
- toolCount: 0,
323
- error: toErrorMessage(error)
324
- };
325
- }
326
- }
327
- async reconcileConfig(params) {
328
- const previousServers = new Map(listMcpServers(params.prevConfig).map((record) => [record.name, record]));
329
- const nextServers = new Map(listMcpServers(params.nextConfig).map((record) => [record.name, record]));
330
- const serverNames = Array.from(/* @__PURE__ */ new Set([...previousServers.keys(), ...nextServers.keys()])).sort(
331
- (left, right) => left.localeCompare(right)
332
- );
333
- const result = {
334
- added: [],
335
- removed: [],
336
- enabled: [],
337
- disabled: [],
338
- restarted: [],
339
- warmed: []
340
- };
341
- for (const serverName of serverNames) {
342
- const previous = previousServers.get(serverName);
343
- const next = nextServers.get(serverName);
344
- if (!next) {
345
- await this.lifecycleManager.closeServer(serverName);
346
- result.removed.push(serverName);
347
- continue;
348
- }
349
- if (!previous) {
350
- result.added.push(serverName);
351
- if (next.definition.enabled) {
352
- result.warmed.push(await this.warmRecord(next));
353
- }
354
- continue;
355
- }
356
- if (previous.definition.enabled && !next.definition.enabled) {
357
- await this.lifecycleManager.closeServer(serverName);
358
- result.disabled.push(serverName);
359
- continue;
360
- }
361
- if (!previous.definition.enabled && next.definition.enabled) {
362
- result.enabled.push(serverName);
363
- result.warmed.push(await this.warmRecord(next));
364
- continue;
365
- }
366
- if (hasReconnectRelevantChange(previous.definition, next.definition)) {
367
- await this.lifecycleManager.closeServer(serverName);
368
- result.restarted.push(serverName);
369
- if (next.definition.enabled) {
370
- result.warmed.push(await this.warmRecord(next));
371
- }
372
- }
373
- }
374
- return result;
375
- }
376
- listAccessibleTools(filter = {}) {
377
- const config = this.options.getConfig();
378
- return this.listServers().filter((record) => record.definition.enabled).filter(
379
- (record) => isServerAccessibleToAgent({
380
- config,
381
- scope: record.definition.scope,
382
- agentId: filter.agentId
383
- })
384
- ).flatMap((record) => this.lifecycleManager.getCachedCatalog(record.name)).sort((left, right) => left.qualifiedName.localeCompare(right.qualifiedName));
385
- }
386
- getCachedState(serverName) {
387
- return this.lifecycleManager.getCachedState(serverName);
388
- }
389
- async callTool(params) {
390
- const server = this.getServer(params.serverName);
391
- if (!server) {
392
- throw new Error(`Unknown MCP server: ${params.serverName}`);
393
- }
394
- return this.lifecycleManager.callTool(server, params.toolName, params.args);
395
- }
396
- async close() {
397
- await this.lifecycleManager.closeAll();
398
- }
399
- async warmRecord(record) {
400
- try {
401
- const state = await this.lifecycleManager.warmServer(record);
402
- return {
403
- name: record.name,
404
- ok: true,
405
- toolCount: state.tools.length
406
- };
407
- } catch (error) {
408
- return {
409
- name: record.name,
410
- ok: false,
411
- toolCount: 0,
412
- error: toErrorMessage(error)
413
- };
414
- }
415
- }
219
+ lifecycleManager;
220
+ constructor(options) {
221
+ this.options = options;
222
+ this.lifecycleManager = this.options.lifecycleManager ?? new McpServerLifecycleManager({ getConfig: this.options.getConfig });
223
+ }
224
+ listServers() {
225
+ return listMcpServers(this.options.getConfig());
226
+ }
227
+ getServer(name) {
228
+ return getMcpServer(this.options.getConfig(), name);
229
+ }
230
+ async prewarmEnabledServers() {
231
+ return (await Promise.all(this.listServers().filter((record) => record.definition.enabled).map(async (record) => {
232
+ try {
233
+ const state = await this.lifecycleManager.warmServer(record);
234
+ return {
235
+ name: record.name,
236
+ ok: true,
237
+ toolCount: state.tools.length
238
+ };
239
+ } catch (error) {
240
+ return {
241
+ name: record.name,
242
+ ok: false,
243
+ toolCount: 0,
244
+ error: toErrorMessage(error)
245
+ };
246
+ }
247
+ }))).sort((left, right) => left.name.localeCompare(right.name));
248
+ }
249
+ async warmServer(name) {
250
+ const server = this.getServer(name);
251
+ if (!server) throw new Error(`Unknown MCP server: ${name}`);
252
+ try {
253
+ const state = await this.lifecycleManager.warmServer(server);
254
+ return {
255
+ name: server.name,
256
+ ok: true,
257
+ toolCount: state.tools.length
258
+ };
259
+ } catch (error) {
260
+ return {
261
+ name: server.name,
262
+ ok: false,
263
+ toolCount: 0,
264
+ error: toErrorMessage(error)
265
+ };
266
+ }
267
+ }
268
+ async reconcileConfig(params) {
269
+ const previousServers = new Map(listMcpServers(params.prevConfig).map((record) => [record.name, record]));
270
+ const nextServers = new Map(listMcpServers(params.nextConfig).map((record) => [record.name, record]));
271
+ const serverNames = Array.from(new Set([...previousServers.keys(), ...nextServers.keys()])).sort((left, right) => left.localeCompare(right));
272
+ const result = {
273
+ added: [],
274
+ removed: [],
275
+ enabled: [],
276
+ disabled: [],
277
+ restarted: [],
278
+ warmed: []
279
+ };
280
+ for (const serverName of serverNames) {
281
+ const previous = previousServers.get(serverName);
282
+ const next = nextServers.get(serverName);
283
+ if (!next) {
284
+ await this.lifecycleManager.closeServer(serverName);
285
+ result.removed.push(serverName);
286
+ continue;
287
+ }
288
+ if (!previous) {
289
+ result.added.push(serverName);
290
+ if (next.definition.enabled) result.warmed.push(await this.warmRecord(next));
291
+ continue;
292
+ }
293
+ if (previous.definition.enabled && !next.definition.enabled) {
294
+ await this.lifecycleManager.closeServer(serverName);
295
+ result.disabled.push(serverName);
296
+ continue;
297
+ }
298
+ if (!previous.definition.enabled && next.definition.enabled) {
299
+ result.enabled.push(serverName);
300
+ result.warmed.push(await this.warmRecord(next));
301
+ continue;
302
+ }
303
+ if (hasReconnectRelevantChange(previous.definition, next.definition)) {
304
+ await this.lifecycleManager.closeServer(serverName);
305
+ result.restarted.push(serverName);
306
+ if (next.definition.enabled) result.warmed.push(await this.warmRecord(next));
307
+ }
308
+ }
309
+ return result;
310
+ }
311
+ listAccessibleTools(filter = {}) {
312
+ const config = this.options.getConfig();
313
+ return this.listServers().filter((record) => record.definition.enabled).filter((record) => isServerAccessibleToAgent({
314
+ config,
315
+ scope: record.definition.scope,
316
+ agentId: filter.agentId
317
+ })).flatMap((record) => this.lifecycleManager.getCachedCatalog(record.name)).sort((left, right) => left.qualifiedName.localeCompare(right.qualifiedName));
318
+ }
319
+ getCachedState(serverName) {
320
+ return this.lifecycleManager.getCachedState(serverName);
321
+ }
322
+ async callTool(params) {
323
+ const server = this.getServer(params.serverName);
324
+ if (!server) throw new Error(`Unknown MCP server: ${params.serverName}`);
325
+ return this.lifecycleManager.callTool(server, params.toolName, params.args);
326
+ }
327
+ async close() {
328
+ await this.lifecycleManager.closeAll();
329
+ }
330
+ async warmRecord(record) {
331
+ try {
332
+ const state = await this.lifecycleManager.warmServer(record);
333
+ return {
334
+ name: record.name,
335
+ ok: true,
336
+ toolCount: state.tools.length
337
+ };
338
+ } catch (error) {
339
+ return {
340
+ name: record.name,
341
+ ok: false,
342
+ toolCount: 0,
343
+ error: toErrorMessage(error)
344
+ };
345
+ }
346
+ }
416
347
  };
417
348
  function toErrorMessage(error) {
418
- return error instanceof Error ? error.message : String(error);
349
+ return error instanceof Error ? error.message : String(error);
419
350
  }
420
351
  function hasReconnectRelevantChange(left, right) {
421
- return JSON.stringify(left.transport) !== JSON.stringify(right.transport);
352
+ return JSON.stringify(left.transport) !== JSON.stringify(right.transport);
422
353
  }
423
-
424
- // src/doctor/mcp-doctor-service.ts
354
+ //#endregion
355
+ //#region src/doctor/mcp-doctor-service.ts
425
356
  var McpDoctorService = class {
426
- constructor(options) {
427
- this.options = options;
428
- }
429
- async inspect(name) {
430
- const registry = this.options.registryService ?? new McpRegistryService({
431
- getConfig: this.options.getConfig
432
- });
433
- const specificServer = name ? registry.getServer(name) : void 0;
434
- const servers = specificServer ? [specificServer] : name ? [] : registry.listServers();
435
- return await Promise.all(
436
- servers.map(async (server) => {
437
- const warmResult = await registry.warmServer(server.name);
438
- return {
439
- name: server.name,
440
- enabled: server.definition.enabled,
441
- transport: server.definition.transport.type,
442
- accessible: warmResult.ok,
443
- toolCount: warmResult.toolCount,
444
- ...warmResult.error ? { error: warmResult.error } : {}
445
- };
446
- })
447
- );
448
- }
357
+ constructor(options) {
358
+ this.options = options;
359
+ }
360
+ async inspect(name) {
361
+ const registry = this.options.registryService ?? new McpRegistryService({ getConfig: this.options.getConfig });
362
+ const specificServer = name ? registry.getServer(name) : void 0;
363
+ const servers = specificServer ? [specificServer] : name ? [] : registry.listServers();
364
+ return await Promise.all(servers.map(async (server) => {
365
+ const warmResult = await registry.warmServer(server.name);
366
+ return {
367
+ name: server.name,
368
+ enabled: server.definition.enabled,
369
+ transport: server.definition.transport.type,
370
+ accessible: warmResult.ok,
371
+ toolCount: warmResult.toolCount,
372
+ ...warmResult.error ? { error: warmResult.error } : {}
373
+ };
374
+ }));
375
+ }
449
376
  };
450
-
451
- // src/doctor/mcp-doctor-facade.ts
377
+ //#endregion
378
+ //#region src/doctor/mcp-doctor-facade.ts
452
379
  var McpDoctorFacade = class {
453
- constructor(options) {
454
- this.options = options;
455
- this.doctorService = new McpDoctorService({
456
- getConfig: this.options.getConfig,
457
- registryService: this.options.registryService
458
- });
459
- }
460
- doctorService;
461
- async inspect(name) {
462
- return await this.doctorService.inspect(name);
463
- }
464
- async inspectOne(name) {
465
- const reports = await this.inspect(name);
466
- return reports[0] ?? null;
467
- }
380
+ doctorService;
381
+ constructor(options) {
382
+ this.options = options;
383
+ this.doctorService = new McpDoctorService({
384
+ getConfig: this.options.getConfig,
385
+ registryService: this.options.registryService
386
+ });
387
+ }
388
+ async inspect(name) {
389
+ return await this.doctorService.inspect(name);
390
+ }
391
+ async inspectOne(name) {
392
+ return (await this.inspect(name))[0] ?? null;
393
+ }
468
394
  };
469
-
470
- // src/install/mcp-install-template-materializer.ts
395
+ //#endregion
396
+ //#region src/install/mcp-install-template-materializer.ts
471
397
  var McpInstallTemplateMaterializer = class {
472
- materialize(params) {
473
- const name = normalizeMcpServerName(params.name?.trim() || params.template.defaultName);
474
- const inputs = params.inputs ?? {};
475
- const definition = this.applyInputs(params.template.template, inputs);
476
- definition.enabled = params.enabled ?? definition.enabled;
477
- definition.scope = this.mergeScope(definition.scope, params.scope);
478
- definition.metadata = this.mergeMetadata(definition.metadata, params.template, params.metadata);
479
- return { name, definition };
480
- }
481
- applyInputs(definition, inputs) {
482
- const replacer = (value) => value.replace(/\{\{\s*([A-Za-z0-9._-]+)\s*\}\}/g, (_, key) => inputs[key]?.trim() ?? "");
483
- const transform = (value) => {
484
- if (typeof value === "string") {
485
- return replacer(value);
486
- }
487
- if (Array.isArray(value)) {
488
- return value.map((entry) => transform(entry));
489
- }
490
- if (!value || typeof value !== "object") {
491
- return value;
492
- }
493
- return Object.fromEntries(
494
- Object.entries(value).map(([key, entry]) => [key, transform(entry)])
495
- );
496
- };
497
- return transform(structuredClone(definition));
498
- }
499
- mergeScope(current, override) {
500
- if (!override) {
501
- return current;
502
- }
503
- const agents = Array.from(new Set((override.agents ?? current.agents ?? []).map((entry) => entry.trim()).filter(Boolean)));
504
- const allAgents = override.allAgents ?? current.allAgents ?? agents.length === 0;
505
- return {
506
- allAgents,
507
- agents: allAgents ? [] : agents
508
- };
509
- }
510
- mergeMetadata(current, template, override) {
511
- return {
512
- ...current,
513
- source: "marketplace",
514
- catalogSlug: template.spec,
515
- displayName: override?.displayName ?? current?.displayName,
516
- vendor: override?.vendor ?? current?.vendor,
517
- docsUrl: override?.docsUrl ?? current?.docsUrl,
518
- homepage: override?.homepage ?? current?.homepage,
519
- trustLevel: override?.trustLevel ?? current?.trustLevel ?? "official",
520
- installedAt: override?.installedAt ?? current?.installedAt ?? (/* @__PURE__ */ new Date()).toISOString(),
521
- ...override ?? {}
522
- };
523
- }
398
+ materialize(params) {
399
+ const name = normalizeMcpServerName(params.name?.trim() || params.template.defaultName);
400
+ const inputs = params.inputs ?? {};
401
+ const definition = this.applyInputs(params.template.template, inputs);
402
+ definition.enabled = params.enabled ?? definition.enabled;
403
+ definition.scope = this.mergeScope(definition.scope, params.scope);
404
+ definition.metadata = this.mergeMetadata(definition.metadata, params.template, params.metadata);
405
+ return {
406
+ name,
407
+ definition
408
+ };
409
+ }
410
+ applyInputs(definition, inputs) {
411
+ const replacer = (value) => value.replace(/\{\{\s*([A-Za-z0-9._-]+)\s*\}\}/g, (_, key) => inputs[key]?.trim() ?? "");
412
+ const transform = (value) => {
413
+ if (typeof value === "string") return replacer(value);
414
+ if (Array.isArray(value)) return value.map((entry) => transform(entry));
415
+ if (!value || typeof value !== "object") return value;
416
+ return Object.fromEntries(Object.entries(value).map(([key, entry]) => [key, transform(entry)]));
417
+ };
418
+ return transform(structuredClone(definition));
419
+ }
420
+ mergeScope(current, override) {
421
+ if (!override) return current;
422
+ const agents = Array.from(new Set((override.agents ?? current.agents ?? []).map((entry) => entry.trim()).filter(Boolean)));
423
+ const allAgents = override.allAgents ?? current.allAgents ?? agents.length === 0;
424
+ return {
425
+ allAgents,
426
+ agents: allAgents ? [] : agents
427
+ };
428
+ }
429
+ mergeMetadata(current, template, override) {
430
+ return {
431
+ ...current,
432
+ source: "marketplace",
433
+ catalogSlug: template.spec,
434
+ displayName: override?.displayName ?? current?.displayName,
435
+ vendor: override?.vendor ?? current?.vendor,
436
+ docsUrl: override?.docsUrl ?? current?.docsUrl,
437
+ homepage: override?.homepage ?? current?.homepage,
438
+ trustLevel: override?.trustLevel ?? current?.trustLevel ?? "official",
439
+ installedAt: override?.installedAt ?? current?.installedAt ?? (/* @__PURE__ */ new Date()).toISOString(),
440
+ ...override ?? {}
441
+ };
442
+ }
524
443
  };
525
-
526
- // src/manage/mcp-mutation-service.ts
444
+ //#endregion
445
+ //#region src/manage/mcp-mutation-service.ts
527
446
  var McpMutationService = class {
528
- constructor(options) {
529
- this.options = options;
530
- this.materializer = this.options.materializer ?? new McpInstallTemplateMaterializer();
531
- }
532
- materializer;
533
- listServerNames() {
534
- return listMcpServers(this.options.getConfig()).map((record) => record.name);
535
- }
536
- addServer(name, definition) {
537
- const normalizedName = normalizeMcpServerName(name);
538
- const config = this.options.getConfig();
539
- if (config.mcp.servers[normalizedName]) {
540
- return {
541
- changed: false,
542
- name: normalizedName,
543
- message: `MCP server already exists: ${normalizedName}. Use 'mcp list' or remove it first.`,
544
- definition: config.mcp.servers[normalizedName]
545
- };
546
- }
547
- config.mcp.servers[normalizedName] = definition;
548
- this.options.saveConfig(config);
549
- return {
550
- changed: true,
551
- name: normalizedName,
552
- message: `Added MCP server ${normalizedName}.`,
553
- definition
554
- };
555
- }
556
- updateServer(name, definition) {
557
- const normalizedName = normalizeMcpServerName(name);
558
- const config = this.options.getConfig();
559
- if (!config.mcp.servers[normalizedName]) {
560
- return {
561
- changed: false,
562
- name: normalizedName,
563
- message: `Unknown MCP server: ${normalizedName}`
564
- };
565
- }
566
- config.mcp.servers[normalizedName] = definition;
567
- this.options.saveConfig(config);
568
- return {
569
- changed: true,
570
- name: normalizedName,
571
- message: `Updated MCP server ${normalizedName}.`,
572
- definition
573
- };
574
- }
575
- removeServer(name) {
576
- const normalizedName = normalizeMcpServerName(name);
577
- const config = this.options.getConfig();
578
- if (!config.mcp.servers[normalizedName]) {
579
- return {
580
- changed: false,
581
- name: normalizedName,
582
- message: `Unknown MCP server: ${normalizedName}`
583
- };
584
- }
585
- delete config.mcp.servers[normalizedName];
586
- this.options.saveConfig(config);
587
- return {
588
- changed: true,
589
- name: normalizedName,
590
- message: `Removed MCP server ${normalizedName}.`
591
- };
592
- }
593
- toggleEnabled(name, enabled) {
594
- const normalizedName = normalizeMcpServerName(name);
595
- const config = this.options.getConfig();
596
- const current = config.mcp.servers[normalizedName];
597
- if (!current) {
598
- return {
599
- changed: false,
600
- name: normalizedName,
601
- message: `Unknown MCP server: ${normalizedName}`
602
- };
603
- }
604
- current.enabled = enabled;
605
- this.options.saveConfig(config);
606
- return {
607
- changed: true,
608
- name: normalizedName,
609
- message: `${enabled ? "Enabled" : "Disabled"} MCP server ${normalizedName}.`,
610
- definition: current
611
- };
612
- }
613
- installFromTemplate(params) {
614
- const materialized = this.materializer.materialize(params);
615
- return this.addServer(materialized.name, materialized.definition);
616
- }
617
- duplicateServer(sourceName, targetName) {
618
- const source = getMcpServer(this.options.getConfig(), sourceName);
619
- if (!source) {
620
- return {
621
- changed: false,
622
- name: normalizeMcpServerName(sourceName),
623
- message: `Unknown MCP server: ${normalizeMcpServerName(sourceName)}`
624
- };
625
- }
626
- const definition = structuredClone(source.definition);
627
- if (definition.metadata) {
628
- definition.metadata = {
629
- ...definition.metadata,
630
- installedAt: (/* @__PURE__ */ new Date()).toISOString()
631
- };
632
- }
633
- return this.addServer(targetName, definition);
634
- }
635
- renameServer(sourceName, targetName) {
636
- const source = getMcpServer(this.options.getConfig(), sourceName);
637
- if (!source) {
638
- return {
639
- changed: false,
640
- name: normalizeMcpServerName(sourceName),
641
- message: `Unknown MCP server: ${normalizeMcpServerName(sourceName)}`
642
- };
643
- }
644
- const nextName = normalizeMcpServerName(targetName);
645
- const config = this.options.getConfig();
646
- if (source.name === nextName) {
647
- return {
648
- changed: false,
649
- name: nextName,
650
- message: `MCP server already named ${nextName}.`,
651
- definition: source.definition
652
- };
653
- }
654
- if (config.mcp.servers[nextName]) {
655
- return {
656
- changed: false,
657
- name: nextName,
658
- message: `MCP server already exists: ${nextName}. Use another name.`
659
- };
660
- }
661
- delete config.mcp.servers[source.name];
662
- config.mcp.servers[nextName] = source.definition;
663
- this.options.saveConfig(config);
664
- return {
665
- changed: true,
666
- name: nextName,
667
- message: `Renamed MCP server ${source.name} -> ${nextName}.`,
668
- definition: source.definition
669
- };
670
- }
447
+ materializer;
448
+ constructor(options) {
449
+ this.options = options;
450
+ this.materializer = this.options.materializer ?? new McpInstallTemplateMaterializer();
451
+ }
452
+ listServerNames() {
453
+ return listMcpServers(this.options.getConfig()).map((record) => record.name);
454
+ }
455
+ addServer(name, definition) {
456
+ const normalizedName = normalizeMcpServerName(name);
457
+ const config = this.options.getConfig();
458
+ if (config.mcp.servers[normalizedName]) return {
459
+ changed: false,
460
+ name: normalizedName,
461
+ message: `MCP server already exists: ${normalizedName}. Use 'mcp list' or remove it first.`,
462
+ definition: config.mcp.servers[normalizedName]
463
+ };
464
+ config.mcp.servers[normalizedName] = definition;
465
+ this.options.saveConfig(config);
466
+ return {
467
+ changed: true,
468
+ name: normalizedName,
469
+ message: `Added MCP server ${normalizedName}.`,
470
+ definition
471
+ };
472
+ }
473
+ updateServer(name, definition) {
474
+ const normalizedName = normalizeMcpServerName(name);
475
+ const config = this.options.getConfig();
476
+ if (!config.mcp.servers[normalizedName]) return {
477
+ changed: false,
478
+ name: normalizedName,
479
+ message: `Unknown MCP server: ${normalizedName}`
480
+ };
481
+ config.mcp.servers[normalizedName] = definition;
482
+ this.options.saveConfig(config);
483
+ return {
484
+ changed: true,
485
+ name: normalizedName,
486
+ message: `Updated MCP server ${normalizedName}.`,
487
+ definition
488
+ };
489
+ }
490
+ removeServer(name) {
491
+ const normalizedName = normalizeMcpServerName(name);
492
+ const config = this.options.getConfig();
493
+ if (!config.mcp.servers[normalizedName]) return {
494
+ changed: false,
495
+ name: normalizedName,
496
+ message: `Unknown MCP server: ${normalizedName}`
497
+ };
498
+ delete config.mcp.servers[normalizedName];
499
+ this.options.saveConfig(config);
500
+ return {
501
+ changed: true,
502
+ name: normalizedName,
503
+ message: `Removed MCP server ${normalizedName}.`
504
+ };
505
+ }
506
+ toggleEnabled(name, enabled) {
507
+ const normalizedName = normalizeMcpServerName(name);
508
+ const config = this.options.getConfig();
509
+ const current = config.mcp.servers[normalizedName];
510
+ if (!current) return {
511
+ changed: false,
512
+ name: normalizedName,
513
+ message: `Unknown MCP server: ${normalizedName}`
514
+ };
515
+ current.enabled = enabled;
516
+ this.options.saveConfig(config);
517
+ return {
518
+ changed: true,
519
+ name: normalizedName,
520
+ message: `${enabled ? "Enabled" : "Disabled"} MCP server ${normalizedName}.`,
521
+ definition: current
522
+ };
523
+ }
524
+ installFromTemplate(params) {
525
+ const materialized = this.materializer.materialize(params);
526
+ return this.addServer(materialized.name, materialized.definition);
527
+ }
528
+ duplicateServer(sourceName, targetName) {
529
+ const source = getMcpServer(this.options.getConfig(), sourceName);
530
+ if (!source) return {
531
+ changed: false,
532
+ name: normalizeMcpServerName(sourceName),
533
+ message: `Unknown MCP server: ${normalizeMcpServerName(sourceName)}`
534
+ };
535
+ const definition = structuredClone(source.definition);
536
+ if (definition.metadata) definition.metadata = {
537
+ ...definition.metadata,
538
+ installedAt: (/* @__PURE__ */ new Date()).toISOString()
539
+ };
540
+ return this.addServer(targetName, definition);
541
+ }
542
+ renameServer(sourceName, targetName) {
543
+ const source = getMcpServer(this.options.getConfig(), sourceName);
544
+ if (!source) return {
545
+ changed: false,
546
+ name: normalizeMcpServerName(sourceName),
547
+ message: `Unknown MCP server: ${normalizeMcpServerName(sourceName)}`
548
+ };
549
+ const nextName = normalizeMcpServerName(targetName);
550
+ const config = this.options.getConfig();
551
+ if (source.name === nextName) return {
552
+ changed: false,
553
+ name: nextName,
554
+ message: `MCP server already named ${nextName}.`,
555
+ definition: source.definition
556
+ };
557
+ if (config.mcp.servers[nextName]) return {
558
+ changed: false,
559
+ name: nextName,
560
+ message: `MCP server already exists: ${nextName}. Use another name.`
561
+ };
562
+ delete config.mcp.servers[source.name];
563
+ config.mcp.servers[nextName] = source.definition;
564
+ this.options.saveConfig(config);
565
+ return {
566
+ changed: true,
567
+ name: nextName,
568
+ message: `Renamed MCP server ${source.name} -> ${nextName}.`,
569
+ definition: source.definition
570
+ };
571
+ }
671
572
  };
672
-
673
- // src/view/mcp-installed-view-service.ts
573
+ //#endregion
574
+ //#region src/view/mcp-installed-view-service.ts
674
575
  var McpInstalledViewService = class {
675
- constructor(options) {
676
- this.options = options;
677
- this.registryService = this.options.registryService ?? new McpRegistryService({
678
- getConfig: this.options.getConfig
679
- });
680
- }
681
- registryService;
682
- listInstalled() {
683
- return this.registryService.listServers().map((server) => {
684
- const metadata = server.definition.metadata;
685
- const cached = this.registryService.getCachedState(server.name);
686
- return {
687
- name: server.name,
688
- enabled: server.definition.enabled,
689
- transport: server.definition.transport.type,
690
- scope: server.definition.scope,
691
- source: metadata?.source === "marketplace" ? "marketplace" : "manual",
692
- catalogSlug: metadata?.catalogSlug,
693
- displayName: metadata?.displayName,
694
- vendor: metadata?.vendor,
695
- docsUrl: metadata?.docsUrl,
696
- homepage: metadata?.homepage,
697
- trustLevel: metadata?.trustLevel,
698
- installedAt: metadata?.installedAt,
699
- accessible: cached?.clientReady,
700
- toolCount: cached?.tools.length,
701
- lastReadyAt: cached?.lastReadyAt,
702
- lastError: cached?.lastError
703
- };
704
- });
705
- }
706
- };
707
- export {
708
- McpClientFactory,
709
- McpDoctorFacade,
710
- McpDoctorService,
711
- McpInstallTemplateMaterializer,
712
- McpInstalledViewService,
713
- McpMutationService,
714
- McpRegistryService,
715
- McpServerLifecycleManager,
716
- buildQualifiedMcpToolName,
717
- getMcpServer,
718
- isServerAccessibleToAgent,
719
- listMcpServers,
720
- normalizeMcpServerName,
721
- readDefaultAgentId,
722
- sanitizeMcpSegment
576
+ registryService;
577
+ constructor(options) {
578
+ this.options = options;
579
+ this.registryService = this.options.registryService ?? new McpRegistryService({ getConfig: this.options.getConfig });
580
+ }
581
+ listInstalled() {
582
+ return this.registryService.listServers().map((server) => {
583
+ const metadata = server.definition.metadata;
584
+ const cached = this.registryService.getCachedState(server.name);
585
+ return {
586
+ name: server.name,
587
+ enabled: server.definition.enabled,
588
+ transport: server.definition.transport.type,
589
+ scope: server.definition.scope,
590
+ source: metadata?.source === "marketplace" ? "marketplace" : "manual",
591
+ catalogSlug: metadata?.catalogSlug,
592
+ displayName: metadata?.displayName,
593
+ vendor: metadata?.vendor,
594
+ docsUrl: metadata?.docsUrl,
595
+ homepage: metadata?.homepage,
596
+ trustLevel: metadata?.trustLevel,
597
+ installedAt: metadata?.installedAt,
598
+ accessible: cached?.clientReady,
599
+ toolCount: cached?.tools.length,
600
+ lastReadyAt: cached?.lastReadyAt,
601
+ lastError: cached?.lastError
602
+ };
603
+ });
604
+ }
723
605
  };
606
+ //#endregion
607
+ export { McpClientFactory, McpDoctorFacade, McpDoctorService, McpInstallTemplateMaterializer, McpInstalledViewService, McpMutationService, McpRegistryService, McpServerLifecycleManager, buildQualifiedMcpToolName, getMcpServer, isServerAccessibleToAgent, listMcpServers, normalizeMcpServerName, readDefaultAgentId, sanitizeMcpSegment };