@steerable/agent-shell 0.6.52 → 0.6.55

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 (60) hide show
  1. package/dist/brand.js +1 -1
  2. package/dist/host/pack-assembly.d.ts +6 -16
  3. package/dist/host/pack-assembly.js +3 -4
  4. package/dist/host/runtime.js +2 -0
  5. package/dist/i18n.d.ts +23 -0
  6. package/dist/i18n.js +87 -0
  7. package/dist/image-attachment.d.ts +31 -4
  8. package/dist/image-attachment.js +13 -18
  9. package/dist/json-store.js +3 -6
  10. package/dist/local-backend/portable-bundle.d.ts +16 -2
  11. package/dist/local-backend/portable-bundle.js +29 -16
  12. package/dist/local-backend/portable-service.js +25 -11
  13. package/dist/local-backend/router.js +56 -17
  14. package/dist/local-backend/skills/10-goal/SKILL.md +1 -1
  15. package/dist/local-backend/skills/11-loop/SKILL.md +1 -1
  16. package/dist/local-backend/skills/12-create-skill/SKILL.md +2 -2
  17. package/dist/local-executor.d.ts +22 -0
  18. package/dist/local-executor.js +118 -1
  19. package/dist/local-script-registry.js +2 -2
  20. package/dist/mcp-executor.d.ts +42 -10
  21. package/dist/mcp-executor.js +248 -41
  22. package/dist/mcp-server-registry.d.ts +59 -11
  23. package/dist/mcp-server-registry.js +193 -28
  24. package/dist/product-config.d.ts +24 -14
  25. package/dist/project-registry.d.ts +3 -3
  26. package/dist/project-registry.js +2 -2
  27. package/dist/runtime.d.ts +8 -54
  28. package/dist/runtime.js +15 -129
  29. package/dist/scenario/pack.d.ts +1 -1
  30. package/dist/scenario/registry.d.ts +4 -5
  31. package/dist/scenario/registry.js +2 -10
  32. package/dist/server/start.js +1 -2
  33. package/dist/shell-exec.d.ts +62 -0
  34. package/dist/shell-exec.js +147 -0
  35. package/dist/shell-process.d.ts +3 -0
  36. package/dist/shell-process.js +137 -0
  37. package/dist/sidecar/boot.js +4 -0
  38. package/dist/sidecar/reverse-spawn.js +6 -15
  39. package/dist/sidecar/supervisor.d.ts +0 -2
  40. package/dist/sidecar/supervisor.js +7 -30
  41. package/dist/storage/index.js +1 -1
  42. package/dist/storage/write-lease-error.d.ts +2 -11
  43. package/dist/storage/write-lease-error.js +3 -8
  44. package/dist/tool-router.js +21 -6
  45. package/package.json +7 -14
  46. package/dist/app-update-electron.d.ts +0 -5
  47. package/dist/app-update-electron.js +0 -66
  48. package/dist/app-update.d.ts +0 -92
  49. package/dist/app-update.js +0 -213
  50. package/dist/host/ipc.d.ts +0 -11
  51. package/dist/host/ipc.js +0 -20
  52. package/dist/main.d.ts +0 -1
  53. package/dist/main.js +0 -967
  54. package/dist/preload-default.d.ts +0 -1
  55. package/dist/preload-default.js +0 -9
  56. package/dist/preload.cjs +0 -425
  57. package/dist/preload.d.ts +0 -20
  58. package/dist/preload.js +0 -442
  59. package/dist/single-instance.d.ts +0 -11
  60. package/dist/single-instance.js +0 -21
@@ -1,7 +1,10 @@
1
1
  import { execFileSync } from 'child_process';
2
+ import { createHash } from 'node:crypto';
2
3
  import { createRequire } from 'module';
3
4
  import { Client } from '@modelcontextprotocol/sdk/client/index.js';
4
5
  import { StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js';
6
+ import { StreamableHTTPClientTransport, StreamableHTTPError, } from '@modelcontextprotocol/sdk/client/streamableHttp.js';
7
+ import { ToolListChangedNotificationSchema } from '@modelcontextprotocol/sdk/types.js';
5
8
  // In ESM there is no `__filename` global; use the module URL to seed a local
6
9
  // CommonJS require() so we can `require.resolve()` MCP server packages that
7
10
  // ship as CJS (npx -y <pkg> targets, e.g. apple-notes-mcp).
@@ -14,6 +17,14 @@ const IDLE_TIMEOUT_MS = 5 * 60 * 1000;
14
17
  * timed out"(2026-07-31 用户实测遇到)。放宽到 3 分钟覆盖冷启动下载。
15
18
  */
16
19
  const MCP_REQUEST_TIMEOUT_MS = 3 * 60 * 1000;
20
+ export const MAX_MCP_SERVER_TOOLS = 64;
21
+ const CONNECT_RETRY_DELAYS_MS = [0, 250, 1_000];
22
+ const DEFAULT_RECONNECTION = {
23
+ initialReconnectionDelay: 500,
24
+ maxReconnectionDelay: 30_000,
25
+ reconnectionDelayGrowFactor: 2,
26
+ maxRetries: 10,
27
+ };
17
28
  /**
18
29
  * 给超时类错误追加可操作的排查提示。独立成纯函数便于单测。
19
30
  */
@@ -54,6 +65,7 @@ function tryResolveBundled(config) {
54
65
  try {
55
66
  const entryPath = localRequire.resolve(pkgName);
56
67
  return {
68
+ transport: 'stdio',
57
69
  command: process.execPath,
58
70
  args: [entryPath, ...extraArgs],
59
71
  env: { ...config.env },
@@ -64,8 +76,43 @@ function tryResolveBundled(config) {
64
76
  return null;
65
77
  }
66
78
  }
67
- function configKey(cfg) {
68
- return `${cfg.command}::${(cfg.args ?? []).join(' ')}`;
79
+ export function resolveHttpHeaders(config, env = process.env) {
80
+ const headers = { ...(config.headers ?? {}) };
81
+ for (const [header, variable] of Object.entries(config.headersFromEnv ?? {})) {
82
+ const value = env[variable];
83
+ if (!value)
84
+ throw new Error(`MCP header environment variable "${variable}" is not set`);
85
+ headers[header] = value;
86
+ }
87
+ if (config.bearerTokenEnvVar) {
88
+ const token = env[config.bearerTokenEnvVar];
89
+ if (!token) {
90
+ throw new Error(`MCP bearer token environment variable "${config.bearerTokenEnvVar}" is not set`);
91
+ }
92
+ headers.Authorization = `Bearer ${token}`;
93
+ }
94
+ return headers;
95
+ }
96
+ export function mcpConfigKey(rawConfig, env = process.env) {
97
+ const config = normalizeExecutorConfig(rawConfig);
98
+ const material = config.transport === 'stdio'
99
+ ? {
100
+ transport: config.transport,
101
+ command: config.command,
102
+ args: config.args ?? [],
103
+ env: config.env ?? {},
104
+ cwd: config.cwd ?? '',
105
+ }
106
+ : {
107
+ transport: config.transport,
108
+ url: config.url,
109
+ headers: resolveHttpHeaders(config, env),
110
+ reconnect: config.reconnect ?? {},
111
+ };
112
+ return createHash('sha256').update(JSON.stringify(material)).digest('hex');
113
+ }
114
+ function normalizeExecutorConfig(config) {
115
+ return 'transport' in config ? config : { ...config, transport: 'stdio' };
69
116
  }
70
117
  /**
71
118
  * Pure eviction predicate, split out so it's unit-testable without spinning
@@ -77,6 +124,83 @@ export function shouldEvictMcpConnection(entry, now, idleTimeoutMs) {
77
124
  return false;
78
125
  return now - entry.lastUsed > idleTimeoutMs;
79
126
  }
127
+ function startupError(config, message) {
128
+ const prefix = config.transport === 'stdio' ? 'MCP server startup failed' : 'MCP HTTP connection failed';
129
+ return config.transport === 'stdio'
130
+ ? withMcpTroubleshootHint(`${prefix}: ${message}`)
131
+ : `${prefix}: ${message}`;
132
+ }
133
+ function parseHttpUrl(raw) {
134
+ let url;
135
+ try {
136
+ url = new URL(raw);
137
+ }
138
+ catch {
139
+ throw new Error(`invalid MCP HTTP URL: ${raw}`);
140
+ }
141
+ if (url.protocol !== 'http:' && url.protocol !== 'https:') {
142
+ throw new Error('MCP HTTP URL must use http or https');
143
+ }
144
+ if (url.username || url.password) {
145
+ throw new Error('MCP HTTP URL must not contain credentials');
146
+ }
147
+ return url;
148
+ }
149
+ function sameOriginFetch(origin) {
150
+ return async (input, init) => {
151
+ let current = new URL(String(input), origin);
152
+ for (let redirects = 0; redirects <= 5; redirects += 1) {
153
+ const response = await fetch(current, { ...init, redirect: 'manual' });
154
+ if (response.status < 300 || response.status >= 400)
155
+ return response;
156
+ const location = response.headers.get('location');
157
+ if (!location)
158
+ return response;
159
+ const next = new URL(location, current);
160
+ if (next.origin !== origin.origin) {
161
+ throw new Error(`MCP HTTP redirect crossed origin: ${origin.origin} -> ${next.origin}`);
162
+ }
163
+ current = next;
164
+ }
165
+ throw new Error('MCP HTTP redirect limit exceeded');
166
+ };
167
+ }
168
+ async function connectWithRetry(create) {
169
+ let lastError;
170
+ for (const delayMs of CONNECT_RETRY_DELAYS_MS) {
171
+ if (delayMs > 0)
172
+ await new Promise((resolve) => setTimeout(resolve, delayMs));
173
+ const generation = create();
174
+ try {
175
+ await generation.client.connect(generation.transport, {
176
+ timeout: MCP_REQUEST_TIMEOUT_MS,
177
+ });
178
+ return generation;
179
+ }
180
+ catch (error) {
181
+ lastError = error;
182
+ try {
183
+ await generation.transport.close();
184
+ }
185
+ catch {
186
+ // Failed negotiation can leave an already-closed transport.
187
+ }
188
+ if (!isRetryableConnectError(error))
189
+ break;
190
+ }
191
+ }
192
+ throw lastError;
193
+ }
194
+ function isRetryableConnectError(error) {
195
+ if (error instanceof StreamableHTTPError) {
196
+ return error.code !== undefined && [408, 429, 500, 502, 503, 504].includes(error.code);
197
+ }
198
+ const message = error instanceof Error ? error.message : String(error);
199
+ if (/401|403|unauthorized|forbidden|invalid|protocol|parse|deserialize/i.test(message)) {
200
+ return false;
201
+ }
202
+ return /408|429|500|502|503|504|fetch failed|ECONN|ENOTFOUND|ETIMEDOUT|closed/i.test(message);
203
+ }
80
204
  function coerceArgs(args, schema) {
81
205
  if (!schema?.inputSchema?.properties)
82
206
  return args;
@@ -100,20 +224,24 @@ function coerceArgs(args, schema) {
100
224
  }
101
225
  return out;
102
226
  }
103
- class McpExecutor {
227
+ export class McpExecutor {
104
228
  pool = new Map();
105
229
  cleanupTimer = null;
106
230
  constructor() {
107
231
  this.cleanupTimer = setInterval(() => this.evictIdle(), 60_000);
108
232
  }
109
- async executeTool(config, toolName, toolArgs) {
233
+ async executeTool(rawConfig, toolName, toolArgs) {
234
+ const config = normalizeExecutorConfig(rawConfig);
110
235
  let entry;
111
236
  try {
112
237
  entry = await this.getOrCreate(config);
113
238
  }
114
239
  catch (err) {
115
240
  const msg = err instanceof Error ? err.message : String(err);
116
- return { success: false, error: withMcpTroubleshootHint(`MCP server startup failed: ${msg}`) };
241
+ return {
242
+ success: false,
243
+ error: startupError(config, msg),
244
+ };
117
245
  }
118
246
  entry.inflight += 1;
119
247
  try {
@@ -136,7 +264,7 @@ class McpExecutor {
136
264
  }
137
265
  catch (err) {
138
266
  const msg = err instanceof Error ? err.message : String(err);
139
- const key = configKey(config);
267
+ const key = mcpConfigKey(config);
140
268
  await this.closeEntry(key);
141
269
  return { success: false, error: withMcpTroubleshootHint(`Tool execution failed: ${msg}`) };
142
270
  }
@@ -145,26 +273,31 @@ class McpExecutor {
145
273
  entry.lastUsed = Date.now();
146
274
  }
147
275
  }
148
- async listTools(config) {
276
+ async listTools(rawConfig, onChanged, onUnavailable) {
277
+ const config = normalizeExecutorConfig(rawConfig);
149
278
  let entry;
150
279
  try {
151
280
  entry = await this.getOrCreate(config);
152
281
  }
153
282
  catch (err) {
154
283
  const msg = err instanceof Error ? err.message : String(err);
155
- return { success: false, error: withMcpTroubleshootHint(`MCP server startup failed: ${msg}`) };
284
+ return { success: false, error: startupError(config, msg) };
156
285
  }
157
286
  entry.inflight += 1;
287
+ if (onChanged)
288
+ entry.listeners.add(onChanged);
289
+ if (onUnavailable)
290
+ entry.unavailableListeners.add(onUnavailable);
158
291
  try {
159
- const listing = await entry.client.listTools(undefined, { timeout: MCP_REQUEST_TIMEOUT_MS });
292
+ const tools = await this.refreshEntryTools(entry);
160
293
  return {
161
294
  success: true,
162
- tools: listing.tools.map(t => ({ name: t.name, description: t.description ?? '', inputSchema: t.inputSchema })),
295
+ tools,
163
296
  };
164
297
  }
165
298
  catch (err) {
166
299
  const msg = err instanceof Error ? err.message : String(err);
167
- const key = configKey(config);
300
+ const key = mcpConfigKey(config);
168
301
  await this.closeEntry(key);
169
302
  return { success: false, error: withMcpTroubleshootHint(`List tools failed: ${msg}`) };
170
303
  }
@@ -174,54 +307,128 @@ class McpExecutor {
174
307
  }
175
308
  }
176
309
  async getOrCreate(config) {
177
- const key = configKey(config);
310
+ const key = mcpConfigKey(config);
178
311
  const existing = this.pool.get(key);
179
312
  if (existing)
180
313
  return existing;
181
- const resolved = tryResolveBundled(config) ?? config;
182
- if (!commandExists(resolved.command)) {
183
- const hint = COMMAND_HINTS[config.command] || `请确保 ${config.command} 已安装并在 PATH 中`;
184
- throw new Error(`命令 "${config.command}" 不可用。${hint}`);
185
- }
186
- const transport = new StdioClientTransport({
187
- command: resolved.command,
188
- args: resolved.args,
189
- env: { ...process.env, ...(resolved.env ?? {}) },
190
- cwd: resolved.cwd ?? undefined,
191
- stderr: 'pipe',
192
- });
193
- const client = new Client({ name: 'agent-shell', version: '1.0.0' }, { capabilities: {} });
194
- await client.connect(transport, { timeout: MCP_REQUEST_TIMEOUT_MS });
195
- const toolSchemas = new Map();
196
- try {
197
- const listing = await client.listTools(undefined, { timeout: MCP_REQUEST_TIMEOUT_MS });
198
- for (const t of listing.tools) {
199
- toolSchemas.set(t.name, {
200
- name: t.name,
201
- inputSchema: t.inputSchema,
202
- });
314
+ const { client, transport } = await connectWithRetry(() => ({
315
+ transport: this.createTransport(config),
316
+ client: new Client({ name: 'agent-shell', version: '1.0.0' }, { capabilities: {} }),
317
+ }));
318
+ const entry = {
319
+ client,
320
+ transport,
321
+ httpTransport: transport instanceof StreamableHTTPClientTransport ? transport : undefined,
322
+ lastUsed: Date.now(),
323
+ key,
324
+ toolSchemas: new Map(),
325
+ inflight: 0,
326
+ listeners: new Set(),
327
+ unavailableListeners: new Set(),
328
+ syncChain: Promise.resolve(),
329
+ };
330
+ await this.refreshEntryTools(entry);
331
+ client.setNotificationHandler(ToolListChangedNotificationSchema, async () => {
332
+ try {
333
+ const tools = await this.refreshEntryTools(entry);
334
+ for (const listener of entry.listeners)
335
+ listener(tools);
203
336
  }
204
- }
205
- catch {
206
- // no-op
207
- }
208
- const entry = { client, transport, lastUsed: Date.now(), key, toolSchemas, inflight: 0 };
337
+ catch {
338
+ // Preserve the previous generation; a later notification/manual refresh can recover.
339
+ }
340
+ });
209
341
  this.pool.set(key, entry);
210
- transport.onclose = () => {
342
+ client.onclose = () => {
343
+ if (this.pool.get(key) !== entry)
344
+ return;
211
345
  this.pool.delete(key);
346
+ for (const listener of entry.unavailableListeners) {
347
+ listener('MCP connection closed after reconnect attempts were exhausted');
348
+ }
212
349
  };
213
350
  return entry;
214
351
  }
352
+ createTransport(config) {
353
+ if (config.transport === 'stdio') {
354
+ const resolved = tryResolveBundled(config) ?? config;
355
+ if (!commandExists(resolved.command)) {
356
+ const hint = COMMAND_HINTS[config.command] || `请确保 ${config.command} 已安装并在 PATH 中`;
357
+ throw new Error(`命令 "${config.command}" 不可用。${hint}`);
358
+ }
359
+ return new StdioClientTransport({
360
+ command: resolved.command,
361
+ args: resolved.args,
362
+ env: { ...process.env, ...(resolved.env ?? {}) },
363
+ cwd: resolved.cwd ?? undefined,
364
+ stderr: 'pipe',
365
+ });
366
+ }
367
+ const url = parseHttpUrl(config.url);
368
+ const headers = resolveHttpHeaders(config);
369
+ return new StreamableHTTPClientTransport(url, {
370
+ requestInit: { headers },
371
+ fetch: sameOriginFetch(url),
372
+ reconnectionOptions: {
373
+ ...DEFAULT_RECONNECTION,
374
+ ...(config.reconnect ?? {}),
375
+ },
376
+ });
377
+ }
378
+ async refreshEntryTools(entry) {
379
+ let result;
380
+ const run = entry.syncChain.then(async () => {
381
+ result = await this.fetchEntryTools(entry);
382
+ });
383
+ entry.syncChain = run.catch(() => { });
384
+ await run;
385
+ return result ?? [];
386
+ }
387
+ async fetchEntryTools(entry) {
388
+ const tools = [];
389
+ let cursor;
390
+ do {
391
+ const listing = await entry.client.listTools(cursor ? { cursor } : undefined, { timeout: MCP_REQUEST_TIMEOUT_MS });
392
+ for (const tool of listing.tools) {
393
+ tools.push({
394
+ name: tool.name,
395
+ description: tool.description ?? '',
396
+ inputSchema: tool.inputSchema,
397
+ });
398
+ if (tools.length > MAX_MCP_SERVER_TOOLS) {
399
+ throw new Error(`MCP server advertises more than the per-server cap of ${MAX_MCP_SERVER_TOOLS} tools`);
400
+ }
401
+ }
402
+ cursor = listing.nextCursor;
403
+ } while (cursor);
404
+ const nextSchemas = new Map();
405
+ for (const tool of tools) {
406
+ nextSchemas.set(tool.name, {
407
+ name: tool.name,
408
+ inputSchema: tool.inputSchema,
409
+ });
410
+ }
411
+ entry.toolSchemas = nextSchemas;
412
+ return tools;
413
+ }
215
414
  async closeEntry(key) {
216
415
  const entry = this.pool.get(key);
217
416
  if (!entry)
218
417
  return;
219
418
  this.pool.delete(key);
419
+ try {
420
+ if (entry.httpTransport?.sessionId) {
421
+ await entry.httpTransport.terminateSession();
422
+ }
423
+ }
424
+ catch {
425
+ // Session DELETE is advisory; transport closure still must happen.
426
+ }
220
427
  try {
221
428
  await entry.client.close();
222
429
  }
223
430
  catch {
224
- // ignore
431
+ // An already-closed transport needs no further cleanup.
225
432
  }
226
433
  }
227
434
  /** 关闭连接池中的所有连接(应用退出 / 测试收尾时调用)。 */
@@ -5,31 +5,76 @@
5
5
  * 持久化在 userData/agent-mcp-servers.json。已启用服务的工具会经
6
6
  * ToolRouter 以 `mcp__<serverKey>__<toolName>` 一等工具身份暴露给模型。
7
7
  *
8
- * 存储通过 {@link McpServerKvStore} 接口注入:main.ts 用 electron-store
9
- * 实现,单测用内存实现——本模块不 import electron,保持纯 Node 可测。
8
+ * 存储通过 {@link McpServerKvStore} 接口注入:宿主用 json-store 实现,
9
+ * 单测用内存实现。
10
10
  */
11
- import { type McpServerConfig } from './mcp-executor.js';
12
- export interface McpServerEntry {
11
+ import { type McpExecutorLike, type McpServerConfig } from './mcp-executor.js';
12
+ interface McpServerCommon {
13
13
  id: string;
14
14
  /** 用户可见名称,也用于生成工具前缀(如 "filesystem")。 */
15
15
  name: string;
16
+ enabled: boolean;
17
+ createdAt: string;
18
+ updatedAt: string;
19
+ }
20
+ export interface StdioMcpServerEntry extends McpServerCommon {
21
+ transport: 'stdio';
16
22
  command: string;
17
23
  args: string[];
18
24
  env: Record<string, string>;
19
25
  cwd?: string;
20
- enabled: boolean;
21
- createdAt: string;
22
- updatedAt: string;
23
26
  }
24
- export interface CreateMcpServerInput {
27
+ export interface StreamableHttpMcpServerEntry extends McpServerCommon {
28
+ transport: 'streamable-http';
29
+ url: string;
30
+ headers: Record<string, string>;
31
+ headersFromEnv: Record<string, string>;
32
+ bearerTokenEnvVar?: string;
33
+ reconnect?: {
34
+ initialReconnectionDelay?: number;
35
+ maxReconnectionDelay?: number;
36
+ reconnectionDelayGrowFactor?: number;
37
+ maxRetries?: number;
38
+ };
39
+ }
40
+ export type McpServerEntry = StdioMcpServerEntry | StreamableHttpMcpServerEntry;
41
+ interface CreateMcpServerCommon {
25
42
  name: string;
43
+ enabled?: boolean;
44
+ }
45
+ export interface CreateStdioMcpServerInput extends CreateMcpServerCommon {
46
+ /** Omitted remains compatible with existing callers and stored product configs. */
47
+ transport?: 'stdio';
26
48
  command: string;
27
49
  args?: string[];
28
50
  env?: Record<string, string>;
29
51
  cwd?: string;
52
+ }
53
+ export interface CreateStreamableHttpMcpServerInput extends CreateMcpServerCommon {
54
+ transport: 'streamable-http';
55
+ url: string;
56
+ headers?: Record<string, string>;
57
+ headersFromEnv?: Record<string, string>;
58
+ bearerTokenEnvVar?: string;
59
+ reconnect?: StreamableHttpMcpServerEntry['reconnect'];
60
+ command?: never;
61
+ }
62
+ export type CreateMcpServerInput = CreateStdioMcpServerInput | CreateStreamableHttpMcpServerInput;
63
+ export interface UpdateMcpServerInput {
64
+ name?: string;
65
+ transport?: 'stdio' | 'streamable-http';
66
+ command?: string;
67
+ args?: string[];
68
+ env?: Record<string, string>;
69
+ cwd?: string;
70
+ url?: string;
71
+ headers?: Record<string, string>;
72
+ headersFromEnv?: Record<string, string>;
73
+ bearerTokenEnvVar?: string;
74
+ reconnect?: StreamableHttpMcpServerEntry['reconnect'];
30
75
  enabled?: boolean;
31
76
  }
32
- /** 最小 KV 存储接口,避免本模块直接依赖 electron-store。 */
77
+ /** 最小 KV 存储接口,避免本模块直接依赖具体存储实现。 */
33
78
  export interface McpServerKvStore {
34
79
  get(key: 'mcpServers'): McpServerEntry[] | undefined;
35
80
  set(key: 'mcpServers', value: McpServerEntry[]): void;
@@ -65,15 +110,16 @@ export interface McpToolEntry {
65
110
  export declare function sanitizeServerKey(name: string, id: string): string;
66
111
  export declare class McpServerRegistry {
67
112
  private readonly store;
113
+ private readonly executor;
68
114
  /** serverId → 最近一次 listTools 的结果(内存缓存,重启后由后台刷新重建)。 */
69
115
  private readonly toolCache;
70
- constructor(store: McpServerKvStore);
116
+ constructor(store: McpServerKvStore, executor?: McpExecutorLike);
71
117
  list(): McpServerEntry[];
72
118
  get(idOrName: string): McpServerEntry | null;
73
119
  /** 产品预置 MCP:缺省不引入;已有同名服务不覆盖。 */
74
120
  seedProductServers(specs: CreateMcpServerInput[] | undefined): void;
75
121
  create(input: CreateMcpServerInput): McpServerEntry;
76
- update(id: string, updates: Partial<CreateMcpServerInput>): McpServerEntry;
122
+ update(id: string, updates: UpdateMcpServerInput): McpServerEntry;
77
123
  delete(id: string): boolean;
78
124
  /**
79
125
  * 解析 Claude Desktop 风格的配置并批量导入:
@@ -88,6 +134,7 @@ export declare class McpServerRegistry {
88
134
  /** 工具名前缀(同一名称多实例撞名时追加 id 后缀保证唯一)。 */
89
135
  serverKey(entry: McpServerEntry): string;
90
136
  toExecutorConfig(entry: McpServerEntry): McpServerConfig;
137
+ toPublicEntry(entry: McpServerEntry): Record<string, unknown>;
91
138
  getCachedTools(serverId: string): CachedToolList | null;
92
139
  /**
93
140
  * 所有「已启用 + 工具缓存非空」服务的工具扁平清单("/" 选择器数据源)。
@@ -104,3 +151,4 @@ export declare class McpServerRegistry {
104
151
  /** 后台刷新所有已启用服务的工具列表(启动时调用,不阻塞、不抛错)。 */
105
152
  refreshAllEnabled(): Promise<void>;
106
153
  }
154
+ export {};