@fitchmultz/pi-mcp-adapter 5.0.0 → 7.0.0

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 (129) hide show
  1. package/CHANGELOG.md +50 -0
  2. package/OAUTH.md +3 -3
  3. package/README.md +160 -88
  4. package/dist/app-bridge.bundle.js +75 -33
  5. package/dist/commands.js +9 -6
  6. package/dist/config.js +41 -16
  7. package/dist/direct-tools.d.ts +2 -4
  8. package/dist/direct-tools.js +21 -319
  9. package/dist/gateway-arguments.d.ts +33 -0
  10. package/dist/gateway-arguments.js +63 -0
  11. package/dist/index.js +181 -276
  12. package/dist/init.js +6 -20
  13. package/dist/lifecycle.d.ts +2 -2
  14. package/dist/lifecycle.js +2 -2
  15. package/dist/mcp-auth-flow.js +2 -2
  16. package/dist/mcp-auth.d.ts +1 -1
  17. package/dist/mcp-auth.js +3 -3
  18. package/dist/mcp-code.d.ts +7 -6
  19. package/dist/mcp-code.js +98 -51
  20. package/dist/mcp-oauth-provider.d.ts +5 -5
  21. package/dist/mcp-oauth-provider.js +5 -5
  22. package/dist/mcp-output-guard.d.ts +57 -2
  23. package/dist/mcp-output-guard.js +129 -24
  24. package/dist/mcp-panel.d.ts +3 -3
  25. package/dist/mcp-panel.js +36 -99
  26. package/dist/mcp-script-worker.mjs +5 -2
  27. package/dist/mcp-setup-panel.d.ts +5 -4
  28. package/dist/mcp-setup-panel.js +15 -7
  29. package/dist/mcp-status.js +2 -1
  30. package/dist/mcp-trace.d.ts +1 -1
  31. package/dist/mcp-trace.js +1 -1
  32. package/dist/metadata-cache.d.ts +1 -1
  33. package/dist/metadata-cache.js +25 -88
  34. package/dist/npx-resolver.js +8 -4
  35. package/dist/prompts.d.ts +1 -1
  36. package/dist/prompts.js +1 -1
  37. package/dist/proxy-modes.d.ts +41 -7
  38. package/dist/proxy-modes.js +275 -490
  39. package/dist/resource-tools.d.ts +15 -0
  40. package/dist/resource-tools.js +26 -1
  41. package/dist/sampling-handler.js +5 -1
  42. package/dist/search-ranking.d.ts +0 -2
  43. package/dist/search-ranking.js +70 -137
  44. package/dist/server-manager.d.ts +2 -2
  45. package/dist/server-manager.js +44 -27
  46. package/dist/session-recovery.d.ts +7 -4
  47. package/dist/session-recovery.js +76 -10
  48. package/dist/state.d.ts +1 -1
  49. package/dist/tool-loader.d.ts +32 -0
  50. package/dist/tool-loader.js +152 -0
  51. package/dist/tool-metadata.d.ts +67 -0
  52. package/dist/tool-metadata.js +23 -17
  53. package/dist/tool-registrar.d.ts +4 -4
  54. package/dist/tool-registrar.js +18 -13
  55. package/dist/tool-result-renderer.d.ts +6 -3
  56. package/dist/tool-result-renderer.js +11 -6
  57. package/dist/types.d.ts +11 -36
  58. package/dist/ui-resource-handler.d.ts +2 -2
  59. package/dist/ui-resource-handler.js +2 -2
  60. package/dist/ui-server.js +4 -3
  61. package/dist/ui-stream-types.d.ts +8 -8
  62. package/dist/unix-socket-transport.d.ts +1 -1
  63. package/dist/unix-socket-transport.js +1 -1
  64. package/dist/utils.d.ts +1 -0
  65. package/dist/utils.js +16 -4
  66. package/package.json +37 -96
  67. package/skills/mcp-scripting/SKILL.md +67 -20
  68. package/abort.ts +0 -34
  69. package/agent-dir.ts +0 -24
  70. package/app-bridge.bundle.js +0 -67
  71. package/banner.png +0 -0
  72. package/checkpoint.ts +0 -55
  73. package/commands.ts +0 -619
  74. package/config.ts +0 -1204
  75. package/consent-manager.ts +0 -25
  76. package/direct-tools.ts +0 -408
  77. package/dist/ts-shape.d.ts +0 -2
  78. package/dist/ts-shape.js +0 -147
  79. package/elicitation-handler.ts +0 -349
  80. package/error-signal.ts +0 -21
  81. package/glimpse-ui.ts +0 -80
  82. package/host-html-template.ts +0 -456
  83. package/index.ts +0 -900
  84. package/init.ts +0 -627
  85. package/legacy-migration.ts +0 -47
  86. package/lifecycle.ts +0 -165
  87. package/logger.ts +0 -127
  88. package/mcp-auth-flow.ts +0 -975
  89. package/mcp-auth.ts +0 -793
  90. package/mcp-callback-server.ts +0 -450
  91. package/mcp-code.ts +0 -386
  92. package/mcp-keyring-helper.cjs +0 -89
  93. package/mcp-oauth-provider.ts +0 -774
  94. package/mcp-output-guard.ts +0 -412
  95. package/mcp-panel.ts +0 -978
  96. package/mcp-script-worker.mjs +0 -128
  97. package/mcp-setup-panel.ts +0 -666
  98. package/mcp-status.ts +0 -109
  99. package/mcp-trace.ts +0 -295
  100. package/metadata-cache.ts +0 -333
  101. package/npx-resolver.ts +0 -488
  102. package/onboarding-state.ts +0 -58
  103. package/panel-keys.ts +0 -37
  104. package/prompts.ts +0 -337
  105. package/proxy-modes.ts +0 -1361
  106. package/resource-tools.ts +0 -17
  107. package/runtime-owner.ts +0 -123
  108. package/sampling-handler.ts +0 -273
  109. package/scripts/build.mjs +0 -131
  110. package/scripts/prepare.mjs +0 -102
  111. package/search-ranking.ts +0 -196
  112. package/server-manager.ts +0 -1320
  113. package/session-recovery.ts +0 -316
  114. package/state.ts +0 -64
  115. package/tool-approval.ts +0 -78
  116. package/tool-metadata.ts +0 -235
  117. package/tool-registrar.ts +0 -69
  118. package/tool-result-renderer.ts +0 -286
  119. package/ts-shape.ts +0 -145
  120. package/tsconfig.build.json +0 -13
  121. package/tsconfig.json +0 -16
  122. package/types.ts +0 -810
  123. package/ui-resource-handler.ts +0 -249
  124. package/ui-server.ts +0 -808
  125. package/ui-session.ts +0 -568
  126. package/ui-stream-types.ts +0 -89
  127. package/ui-tool-visibility.ts +0 -25
  128. package/unix-socket-transport.ts +0 -85
  129. package/utils.ts +0 -309
package/index.ts DELETED
@@ -1,900 +0,0 @@
1
- import type { AgentToolUpdateCallback, ExtensionAPI, ExtensionContext, ToolInfo } from "@earendil-works/pi-coding-agent";
2
- import type { McpExtensionState } from "./state.ts";
3
- import type { DirectToolSpec, McpAdapterOptions, McpConfig, PromptMetadata } from "./types.ts";
4
- import type { McpOAuthRuntime } from "./mcp-auth-flow.ts";
5
- import { Type } from "typebox";
6
- import { showStatus, showTools, showPrompts, reconnectServer, reconnectServers, authenticateServer, logoutServer, openMcpAuthPanel, openMcpPanel, openMcpSetup } from "./commands.ts";
7
- import { cloneMcpConfig, isPathInsideProject, loadMcpConfig, writeProjectServerDisabledOverride } from "./config.ts";
8
- import { buildProxyDescription, createDirectToolExecutor, getMissingConfiguredDirectToolServers, resolveDirectTools } from "./direct-tools.ts";
9
- import { flushMetadataCache, initializeMcp, updateStatusBar } from "./init.ts";
10
- import { getMetadataCachePath, loadMetadataCache, type MetadataCache } from "./metadata-cache.ts";
11
- import { createPromptCommand, resolveCachedPrompts } from "./prompts.ts";
12
- import { logger } from "./logger.ts";
13
- import { executeAuthComplete, executeAuthStart, executeCall, executeConnect, executeDescribe, executeInstructions, executeList, executeSearch, executeStatus, executeUiMessages } from "./proxy-modes.ts";
14
- import { formatTerminalError, normalizeDirectToolInputSchema, truncateAtWord } from "./utils.ts";
15
- import { createOAuthRuntime, shutdownOAuth } from "./mcp-auth-flow.ts";
16
- import { createMcpDirectToolCallRenderer, renderMcpProxyToolCall, renderMcpToolResult } from "./tool-result-renderer.ts";
17
- import { toolErrorOverride } from "./error-signal.ts";
18
- import { createMcpRuntimeOwner, createOwnedUi, isAbortError, type McpRuntimeOwner } from "./runtime-owner.ts";
19
- import { publishMcpStatusShutdown } from "./mcp-status.ts";
20
- import { DEFAULT_MCP_SCRIPT_TIMEOUT_MS, runMcpScript } from "./mcp-code.ts";
21
- import { MAX_PAGE_SIZE, MAX_TOOL_NAME_LENGTH } from "./search-ranking.ts";
22
- import { abortable } from "./abort.ts";
23
- import { onSessionCheckpoint, prepareMcpCheckpoint } from "./checkpoint.ts";
24
-
25
- export type { McpAdapterOptions, McpOperationContext, McpToolCallEvent, McpToolCallIdentity } from "./types.ts";
26
- export {
27
- MCP_STATUS_EVENT,
28
- MCP_STATUS_SNAPSHOT_VERSION,
29
- type McpServerRuntimeStatus,
30
- type McpServerStatusSnapshot,
31
- type McpStatusSnapshot,
32
- } from "./types.ts";
33
-
34
- const INIT_WAIT_TIMEOUT_MS = 30_000;
35
- const INIT_WAIT_TIMED_OUT: unique symbol = Symbol("init-wait-timed-out");
36
- const inactiveDirectToolsKey = Symbol.for("fitch-mcp-adapter.inactive-direct-tools");
37
- const inactiveDirectToolsBySession = (() => {
38
- const shared = globalThis as typeof globalThis & {
39
- [inactiveDirectToolsKey]?: Map<string, Set<string>>;
40
- };
41
- return shared[inactiveDirectToolsKey] ??= new Map<string, Set<string>>();
42
- })();
43
-
44
- async function awaitWithTimeout<T>(promise: Promise<T>, timeoutMs: number): Promise<T | typeof INIT_WAIT_TIMED_OUT> {
45
- let timer: ReturnType<typeof setTimeout> | undefined;
46
- try {
47
- return await Promise.race([
48
- promise,
49
- new Promise<typeof INIT_WAIT_TIMED_OUT>((resolve) => {
50
- timer = setTimeout(() => resolve(INIT_WAIT_TIMED_OUT), timeoutMs);
51
- timer.unref?.();
52
- }),
53
- ]);
54
- } finally {
55
- clearTimeout(timer);
56
- }
57
- }
58
-
59
- function installMcpAdapter(pi: ExtensionAPI, options: McpAdapterOptions) {
60
- const beforeExecute = options.beforeExecute;
61
- const sessionConfig = options.config !== undefined ? cloneMcpConfig(options.config) : undefined;
62
- const programmaticConfig = sessionConfig !== undefined;
63
- let state: McpExtensionState | null = null;
64
- let initPromise: Promise<McpExtensionState> | null = null;
65
- let currentOwner: McpRuntimeOwner | null = null;
66
- let currentOAuthRuntime: McpOAuthRuntime | null = null;
67
- let lifecycleGeneration = 0;
68
- let currentConfigPath: string | undefined;
69
-
70
- async function shutdownState(currentState: McpExtensionState | null, reason: string): Promise<void> {
71
- if (!currentState) {
72
- publishMcpStatusShutdown(pi.events);
73
- return;
74
- }
75
-
76
- const failures: unknown[] = [];
77
- try { publishMcpStatusShutdown(currentState.statusEvents); } catch (error) { failures.push(error); }
78
- try { currentState.uiServer?.close(reason); } catch (error) { failures.push(error); }
79
- currentState.uiServer = null;
80
- try { flushMetadataCache(currentState); } catch (error) { failures.push(error); }
81
- try {
82
- if (currentState.owner) await currentState.owner.stop(reason);
83
- else await currentState.lifecycle.gracefulShutdown();
84
- } catch (error) { failures.push(error); }
85
- if (failures.length) throw new AggregateError(failures, "MCP state persistence/cleanup failed");
86
- }
87
-
88
- const earlyConfig = programmaticConfig
89
- ? cloneMcpConfig(sessionConfig)
90
- : { mcpServers: {} };
91
- const earlyCache: MetadataCache | null = null;
92
- const envRaw = process.env.MCP_DIRECT_TOOLS;
93
- const envDirectToolOverride = envRaw?.split(",").map(s => s.trim()).filter(Boolean);
94
- const registeredDirectTools = new Map<string, string>();
95
- const deactivatedTools = new Set<string>();
96
- const preservedInactiveTools = new Set<string>();
97
- let activationKey: string | null = null;
98
- let proxyToolRegistered = false;
99
- let proxyToolDescription: string | null = null;
100
- let directToolsFrozen = false;
101
-
102
- function directToolFingerprint(spec: DirectToolSpec): string {
103
- return JSON.stringify({
104
- serverName: spec.serverName,
105
- originalName: spec.originalName,
106
- prefixedName: spec.prefixedName,
107
- description: spec.description,
108
- inputSchema: spec.inputSchema,
109
- annotations: spec.annotations,
110
- resourceUri: spec.resourceUri,
111
- uiResourceUri: spec.uiResourceUri,
112
- uiStreamMode: spec.uiStreamMode,
113
- });
114
- }
115
-
116
- function registerDirectTool(spec: DirectToolSpec): void {
117
- const execute = createDirectToolExecutor(() => state, () => initPromise, spec, beforeExecute);
118
- pi.registerTool({
119
- ...(beforeExecute ? { executionMode: "sequential" as const } : {}),
120
- name: spec.prefixedName,
121
- label: `MCP: ${spec.originalName}`,
122
- description: spec.description || "(no description)",
123
- promptSnippet: truncateAtWord(spec.description, 100) || `MCP tool from ${spec.serverName}`,
124
- parameters: Type.Unsafe<Record<string, unknown>>(normalizeDirectToolInputSchema(spec.inputSchema)),
125
- execute,
126
- renderCall: createMcpDirectToolCallRenderer(spec.prefixedName),
127
- renderResult: renderMcpToolResult,
128
- });
129
- if (preservedInactiveTools.has(spec.prefixedName)) {
130
- const activeTools = pi.getActiveTools();
131
- if (activeTools.includes(spec.prefixedName)) {
132
- pi.setActiveTools(activeTools.filter((name) => name !== spec.prefixedName));
133
- }
134
- }
135
- }
136
-
137
- function resolveCurrentDirectTools(config: McpConfig, cache: MetadataCache | null): DirectToolSpec[] {
138
- if (envRaw === "__none__") return [];
139
- const prefix = config.settings?.toolPrefix ?? "server";
140
- return resolveDirectTools(config, cache, prefix, envDirectToolOverride);
141
- }
142
-
143
- function deactivateTools(toolNames: string[]): void {
144
- if (toolNames.length === 0) return;
145
- const remove = new Set(toolNames);
146
- const activeTools = pi.getActiveTools();
147
- for (const toolName of toolNames) {
148
- if (activeTools.includes(toolName)) deactivatedTools.add(toolName);
149
- }
150
- const nextActiveTools = activeTools.filter((name) => !remove.has(name));
151
- if (nextActiveTools.length !== activeTools.length) {
152
- pi.setActiveTools(nextActiveTools);
153
- }
154
- }
155
-
156
- function syncDirectTools(config: McpConfig, cache: MetadataCache | null): {
157
- specs: DirectToolSpec[];
158
- added: string[];
159
- updated: string[];
160
- deactivated: string[];
161
- } {
162
- const specs = resolveCurrentDirectTools(config, cache);
163
- const nextNames = new Set(specs.map((spec) => spec.prefixedName));
164
- const added: string[] = [];
165
- const updated: string[] = [];
166
- const deactivated: string[] = [];
167
-
168
- for (const spec of specs) {
169
- const fingerprint = directToolFingerprint(spec);
170
- const previous = registeredDirectTools.get(spec.prefixedName);
171
- if (previous !== fingerprint) {
172
- registerDirectTool(spec);
173
- registeredDirectTools.set(spec.prefixedName, fingerprint);
174
- if (deactivatedTools.delete(spec.prefixedName)) {
175
- const activeTools = pi.getActiveTools();
176
- if (!activeTools.includes(spec.prefixedName)) {
177
- pi.setActiveTools([...activeTools, spec.prefixedName]);
178
- }
179
- }
180
- (previous ? updated : added).push(spec.prefixedName);
181
- }
182
- }
183
-
184
- for (const toolName of [...registeredDirectTools.keys()]) {
185
- if (nextNames.has(toolName)) continue;
186
- registeredDirectTools.delete(toolName);
187
- deactivated.push(toolName);
188
- }
189
-
190
- deactivateTools(deactivated);
191
- return { specs, added, updated, deactivated };
192
- }
193
-
194
- function applyDirectToolConfigChanges(changes: Map<string, true | string[] | false>): void {
195
- if (!state) return;
196
- for (const [serverName, value] of changes) {
197
- const definition = state.config.mcpServers[serverName];
198
- if (!definition) continue;
199
- state.config.mcpServers[serverName] = { ...definition, directTools: value };
200
- }
201
- }
202
-
203
- function syncToolSurface(ctx?: ExtensionContext): void {
204
- const config = state?.config ?? earlyConfig;
205
- const cache = loadMetadataCache(state?.metadataCacheEnabled ?? false);
206
- const result = syncDirectTools(config, cache);
207
- syncProxyTool(config, cache, result.specs);
208
- syncScriptTool(config);
209
- const changed = result.added.length + result.updated.length + result.deactivated.length;
210
- if (changed > 0 && ctx?.hasUI) {
211
- ctx.ui.notify(
212
- `MCP: direct tools refreshed (+${result.added.length}, ~${result.updated.length}, -${result.deactivated.length})`,
213
- "info",
214
- );
215
- }
216
- }
217
-
218
- const registeredPromptCommands = new Set<string>();
219
-
220
- function registerPromptCommands(specs: Iterable<PromptMetadata>): void {
221
- for (const spec of specs) {
222
- if (registeredPromptCommands.has(spec.commandName)) {
223
- logger.debug(`MCP: prompt "${spec.originalName}" on ${spec.serverName} skipped; /${spec.commandName} is already registered`);
224
- continue;
225
- }
226
- registeredPromptCommands.add(spec.commandName);
227
- pi.registerCommand(spec.commandName, createPromptCommand(pi, () => state, spec));
228
- }
229
- }
230
-
231
- function syncPromptCommands(): void {
232
- registerPromptCommands([...(state?.promptMetadata?.values() ?? [])].flat());
233
- }
234
-
235
- if (!options.transformConfig) registerPromptCommands(resolveCachedPrompts(earlyConfig, false));
236
-
237
- const getPiTools = (): ToolInfo[] => pi.getAllTools();
238
-
239
- pi.registerFlag("mcp-config", {
240
- description: "Path to MCP config file",
241
- type: "string",
242
- });
243
-
244
- function startInitialization(
245
- ctx: ExtensionContext,
246
- owner: McpRuntimeOwner,
247
- oauthRuntime: McpOAuthRuntime,
248
- runtimeConfig: McpConfig,
249
- generation: number,
250
- ): Promise<void> {
251
- const promise = initializeMcp(pi, ctx, owner, {
252
- ...(programmaticConfig ? { config: runtimeConfig } : { resolvedConfig: runtimeConfig }),
253
- ...(options.outputDirectory !== undefined ? { outputDirectory: options.outputDirectory } : {}),
254
- oauthRuntime,
255
- statusEvents: pi.events,
256
- ...(options.onToolCall ? { onToolCall: options.onToolCall } : {}),
257
- });
258
- initPromise = promise;
259
-
260
- return promise.then(async (nextState) => {
261
- if (!owner.isActive() || generation !== lifecycleGeneration || initPromise !== promise) {
262
- try {
263
- await shutdownState(nextState, "stale_session_start");
264
- } catch (error) {
265
- console.error(`MCP: failed to clean stale initialization state: ${formatTerminalError(error)}`);
266
- }
267
- return;
268
- }
269
-
270
- state = nextState;
271
- nextState.onToolMetadataUpdated = (_serverName, _reason) => {
272
- if (state !== nextState || !owner.isActive()) return;
273
- syncPromptCommands();
274
- if (directToolsFrozen) {
275
- logger.debug(`MCP: metadata update for ${_serverName} (${_reason}) skipped — directTools frozen`);
276
- return;
277
- }
278
- syncToolSurface(ctx);
279
- };
280
- syncPromptCommands();
281
- syncToolSurface(ctx);
282
- updateStatusBar(nextState);
283
- initPromise = null;
284
- if (nextState.config.settings?.freezeDirectTools === true) {
285
- directToolsFrozen = true;
286
- logger.info("MCP: direct tools frozen after initial sync — reconnects won't rebuild the system prompt; use mcp({ connect: \"server\" }) to rediscover");
287
- }
288
- }).catch(async err => {
289
- if (!owner.isActive() || generation !== lifecycleGeneration) {
290
- return;
291
- }
292
- if (initPromise !== promise && initPromise !== null) {
293
- return;
294
- }
295
- console.error(`MCP initialization failed: ${formatTerminalError(err)}`);
296
- initPromise = null;
297
- if (state) return;
298
-
299
- try {
300
- await Promise.all([
301
- owner.stop("MCP initialization failed"),
302
- shutdownOAuth(oauthRuntime),
303
- ]);
304
- } catch (error) {
305
- console.error(`MCP: failed to clean rejected initialization: ${formatTerminalError(error)}`);
306
- }
307
- });
308
- }
309
-
310
- pi.on("session_start", async (_event, ctx) => {
311
- const generation = ++lifecycleGeneration;
312
- const previousState = state;
313
- const previousOwner = currentOwner;
314
- const previousOAuthRuntime = currentOAuthRuntime;
315
- const owner = createMcpRuntimeOwner();
316
- const oauthRuntime = createOAuthRuntime(owner.signal);
317
- currentOwner = owner;
318
- currentOAuthRuntime = oauthRuntime;
319
- state = null;
320
- initPromise = null;
321
-
322
- // Abort synchronously before awaiting cleanup so old callbacks and startup
323
- // work cannot resume into a stale ExtensionContext.
324
- const stopPrevious = previousOwner?.stop("MCP extension session restarted") ?? Promise.resolve();
325
- try {
326
- await Promise.all([
327
- stopPrevious,
328
- shutdownState(previousState, "session_restart"),
329
- previousOAuthRuntime ? shutdownOAuth(previousOAuthRuntime) : Promise.resolve(),
330
- ]);
331
- } catch (error) {
332
- console.error(`MCP: failed to shut down previous session state: ${formatTerminalError(error)}`);
333
- }
334
-
335
- if (generation !== lifecycleGeneration || !owner.isActive()) return;
336
-
337
- let runtimeConfig: McpConfig;
338
- if (programmaticConfig) {
339
- currentConfigPath = undefined;
340
- runtimeConfig = cloneMcpConfig(sessionConfig);
341
- } else {
342
- const registeredConfigPath = pi.getFlag("mcp-config");
343
- currentConfigPath = options.configPath
344
- ?? (typeof registeredConfigPath === "string" ? registeredConfigPath : undefined);
345
- runtimeConfig = loadMcpConfig(
346
- currentConfigPath,
347
- ctx.cwd,
348
- { includeProject: ctx.isProjectTrusted() },
349
- );
350
- }
351
- if (options.transformConfig) {
352
- runtimeConfig = cloneMcpConfig(options.transformConfig(cloneMcpConfig(runtimeConfig), ctx));
353
- }
354
- const metadataCacheEnabled = ctx.isProjectTrusted()
355
- || !isPathInsideProject(getMetadataCachePath(), ctx.cwd);
356
- activationKey = `${ctx.cwd}\0${currentConfigPath ?? ""}`;
357
- preservedInactiveTools.clear();
358
- for (const toolName of inactiveDirectToolsBySession.get(activationKey) ?? []) {
359
- preservedInactiveTools.add(toolName);
360
- }
361
- registerPromptCommands(resolveCachedPrompts(runtimeConfig, metadataCacheEnabled));
362
- const runtimeCache = loadMetadataCache(metadataCacheEnabled);
363
- const cachedDirectTools = syncDirectTools(runtimeConfig, runtimeCache).specs;
364
- syncProxyTool(runtimeConfig, runtimeCache, cachedDirectTools);
365
- syncScriptTool(runtimeConfig);
366
-
367
- const initialization = startInitialization(ctx, owner, oauthRuntime, runtimeConfig, generation);
368
- if (envRaw !== undefined && envRaw !== "__none__") {
369
- const missingEnvDirectTools = getMissingConfiguredDirectToolServers(
370
- runtimeConfig,
371
- runtimeCache,
372
- envDirectToolOverride,
373
- );
374
- if (missingEnvDirectTools.length > 0) {
375
- await initialization;
376
- }
377
- }
378
- });
379
-
380
- onSessionCheckpoint(pi, async event => {
381
- if (initPromise) return { sleepReady: false, reason: "MCP initialization is active" };
382
- if (!state || !currentOwner?.isActive()) return { sleepReady: false, reason: "MCP runtime is not initialized" };
383
- return prepareMcpCheckpoint(state, event);
384
- });
385
-
386
- pi.on("session_shutdown", async () => {
387
- if (activationKey) {
388
- const activeTools = new Set(pi.getActiveTools());
389
- const inactiveTools = new Set(
390
- [...registeredDirectTools.keys()].filter((name) => !activeTools.has(name)),
391
- );
392
- if (inactiveTools.size > 0) inactiveDirectToolsBySession.set(activationKey, inactiveTools);
393
- else inactiveDirectToolsBySession.delete(activationKey);
394
- }
395
- ++lifecycleGeneration;
396
- const currentState = state;
397
- const owner = currentOwner;
398
- const oauthRuntime = currentOAuthRuntime;
399
- currentOwner = null;
400
- currentOAuthRuntime = null;
401
- state = null;
402
- initPromise = null;
403
-
404
- // Abort before awaiting cleanup so delayed initialization cannot touch stale
405
- // Pi context after session shutdown.
406
- const stopOwner = owner?.stop("MCP extension session shutdown") ?? Promise.resolve();
407
- const results = await Promise.allSettled([
408
- stopOwner,
409
- shutdownState(currentState, "session_shutdown"),
410
- oauthRuntime ? shutdownOAuth(oauthRuntime) : Promise.resolve(),
411
- ]);
412
- const failures = results.flatMap(result => result.status === "rejected" ? [result.reason] : []);
413
- // Ordinary extension error handling owns reporting. Strict clean-exit hosts must see failure.
414
- if (failures.length) throw new AggregateError(failures, "MCP session shutdown persistence/cleanup failed");
415
- });
416
-
417
- // Re-flag returned MCP tool failures so pi registers them as errors (see toolErrorOverride).
418
- pi.on("tool_result", (event) => toolErrorOverride(event.details));
419
-
420
- function createCommandContext(ctx: ExtensionContext): {
421
- owner: McpRuntimeOwner | null;
422
- context: ExtensionContext;
423
- } {
424
- const owner = currentOwner;
425
- const hasUI = ctx.hasUI;
426
- const projectTrusted = ctx.isProjectTrusted();
427
- return {
428
- owner,
429
- context: {
430
- hasUI,
431
- ui: hasUI ? (owner ? createOwnedUi(ctx.ui, owner) : ctx.ui) : undefined,
432
- cwd: ctx.cwd,
433
- mode: ctx.mode,
434
- signal: owner?.signal ?? ctx.signal,
435
- isProjectTrusted: () => projectTrusted,
436
- } as unknown as ExtensionContext,
437
- };
438
- }
439
-
440
- async function ensureCommandState(owner: McpRuntimeOwner | null, ctx: ExtensionContext): Promise<McpExtensionState | null> {
441
- if (!state && initPromise) {
442
- try {
443
- const initialized = await initPromise;
444
- owner?.throwIfInactive();
445
- state = initialized;
446
- } catch (error) {
447
- const message = error instanceof Error ? error.message : String(error);
448
- ctx.ui?.notify(`MCP initialization failed: ${message}`, "error");
449
- return null;
450
- }
451
- }
452
- if (!state) ctx.ui?.notify("MCP not initialized", "error");
453
- return state;
454
- }
455
-
456
- pi.registerCommand("mcp", {
457
- description: "Show MCP server status",
458
- getArgumentCompletions: (prefix: string) => {
459
- const normalized = prefix.trimStart();
460
- const argumentMatch = normalized.match(/^(\S+)\s+(.*)$/);
461
- if (!argumentMatch) {
462
- const subcommands = [
463
- { value: "reconnect", label: "reconnect — Reconnect servers" },
464
- { value: "tools", label: "tools — List all tools" },
465
- { value: "prompts", label: "prompts — List all MCP prompts" },
466
- { value: "setup", label: "setup — Configure MCP servers" },
467
- { value: "logout", label: "logout — Clear server credentials" },
468
- { value: "disable", label: "disable — Disable a server" },
469
- { value: "enable", label: "enable — Enable a server" },
470
- { value: "status", label: "status — Show server status" },
471
- ].filter(({ value }) => value.startsWith(normalized));
472
- return subcommands.length > 0 ? subcommands : null;
473
- }
474
-
475
- const [, subcommand, argumentPrefix] = argumentMatch;
476
- if (
477
- (subcommand !== "reconnect" && subcommand !== "logout" && subcommand !== "disable" && subcommand !== "enable")
478
- || argumentPrefix === undefined
479
- || !state
480
- ) return null;
481
-
482
- const servers = Object.keys(state.config.mcpServers)
483
- .filter((serverName) => serverName.startsWith(argumentPrefix.trimStart()))
484
- .map((serverName) => ({ value: `${subcommand} ${serverName}`, label: serverName }));
485
- return servers.length > 0 ? servers : null;
486
- },
487
- handler: async (args, ctx) => {
488
- const { owner: commandOwner, context: commandCtx } = createCommandContext(ctx);
489
- const commandProjectTrusted = commandCtx.isProjectTrusted();
490
- const commandState = await ensureCommandState(commandOwner, commandCtx);
491
- if (!commandState) return;
492
-
493
- const parts = args?.trim()?.split(/\s+/) ?? [];
494
- const subcommand = parts[0] ?? "";
495
- const targetServer = parts[1];
496
- const rest = parts.slice(1).join(" ");
497
-
498
- switch (subcommand) {
499
- case "reconnect":
500
- commandOwner?.throwIfInactive();
501
- await reconnectServers(commandState, commandCtx, targetServer);
502
- if (directToolsFrozen) syncToolSurface(commandCtx);
503
- break;
504
- case "tools":
505
- await showTools(commandState, commandCtx);
506
- break;
507
- case "prompts":
508
- await showPrompts(commandState, commandCtx);
509
- break;
510
- case "setup": {
511
- commandOwner?.throwIfInactive();
512
- if (programmaticConfig) {
513
- commandCtx.ui?.notify("MCP setup is unavailable when config is supplied by createMcpAdapter().", "info");
514
- break;
515
- }
516
- const result = await openMcpSetup(commandState, pi, commandCtx, currentConfigPath, "setup");
517
- if (result?.configChanged) {
518
- commandOwner?.throwIfInactive();
519
- await ctx.reload();
520
- return;
521
- }
522
- break;
523
- }
524
- case "logout": {
525
- const serverName = rest;
526
- if (!serverName) {
527
- if (commandCtx.hasUI) commandCtx.ui?.notify("Usage: /mcp logout <server>", "error");
528
- return;
529
- }
530
- commandOwner?.throwIfInactive();
531
- await logoutServer(serverName, commandState, commandCtx);
532
- break;
533
- }
534
- case "disable":
535
- case "enable": {
536
- const serverName = rest;
537
- if (!commandProjectTrusted) {
538
- commandCtx.ui?.notify("Project MCP changes are unavailable until this project is trusted.", "warning");
539
- break;
540
- }
541
- if (programmaticConfig) {
542
- commandCtx.ui?.notify(`/mcp ${subcommand} is unavailable when config is supplied by createMcpAdapter().`, "info");
543
- break;
544
- }
545
- if (!serverName) {
546
- commandCtx.ui?.notify(`Usage: /mcp ${subcommand} <server>`, "error");
547
- break;
548
- }
549
- if (!commandState.config.mcpServers[serverName]) {
550
- commandCtx.ui?.notify(`Server "${serverName}" not found in effective config`, "error");
551
- break;
552
- }
553
- commandOwner?.throwIfInactive();
554
- const result = writeProjectServerDisabledOverride(currentConfigPath, commandCtx.cwd, serverName, subcommand === "disable");
555
- if (result.changed) {
556
- commandCtx.ui?.notify(`${subcommand === "disable" ? "Disabled" : "Enabled"} server "${serverName}" in ${result.path} — run /reload to apply`, "info");
557
- } else {
558
- commandCtx.ui?.notify(`Server "${serverName}" is already ${subcommand === "disable" ? "disabled" : "enabled"}`, "info");
559
- }
560
- break;
561
- }
562
- case "status":
563
- case "":
564
- default:
565
- if (commandCtx.hasUI) {
566
- commandOwner?.throwIfInactive();
567
- if (programmaticConfig) {
568
- commandCtx.ui?.notify("MCP status is shown from the in-memory SDK config; configuration discovery is unavailable.", "info");
569
- await showStatus(commandState, commandCtx);
570
- break;
571
- }
572
- const result = await openMcpPanel(commandState, pi, commandCtx, currentConfigPath, (changes) => {
573
- applyDirectToolConfigChanges(changes);
574
- syncToolSurface(commandCtx);
575
- });
576
- if (result?.configChanged) {
577
- commandOwner?.throwIfInactive();
578
- await ctx.reload();
579
- return;
580
- }
581
- } else {
582
- await showStatus(commandState, commandCtx);
583
- }
584
- break;
585
- }
586
- },
587
- });
588
-
589
- pi.registerCommand("mcp-auth", {
590
- description: "Authenticate with an MCP server (OAuth)",
591
- handler: async (args, ctx) => {
592
- const { owner: commandOwner, context: commandCtx } = createCommandContext(ctx);
593
- const serverName = args?.trim();
594
- if (!serverName && !commandCtx.hasUI) {
595
- return;
596
- }
597
-
598
- const commandState = await ensureCommandState(commandOwner, commandCtx);
599
- if (!commandState) return;
600
-
601
- if (!serverName) {
602
- if (programmaticConfig) {
603
- commandCtx.ui?.notify("Use /mcp-auth <server> to authenticate a server from the in-memory SDK config.", "info");
604
- return;
605
- }
606
- await openMcpAuthPanel(commandState, commandCtx, currentConfigPath);
607
- return;
608
- }
609
-
610
- const result = await authenticateServer(serverName, commandState.config, commandCtx, commandCtx.signal, commandState.oauthRuntime);
611
- if (result.ok) {
612
- commandOwner?.throwIfInactive();
613
- await reconnectServer(commandState, commandCtx, serverName, true);
614
- }
615
- },
616
- });
617
-
618
- let scriptToolRegistered = false;
619
-
620
- function registerScriptTool(): void {
621
- if (scriptToolRegistered) return;
622
- pi.registerTool({
623
- ...(beforeExecute ? { executionMode: "sequential" as const } : {}),
624
- name: "mcp_script",
625
- label: "MCP Script",
626
- description: "Run trusted JavaScript that makes multiple MCP tool calls in one request — loop, filter, chain, or fan out between calls. For a single MCP call, search, describe, status check, or auth action, use the mcp tool instead. Discover with await tools.search({ query }) — resolves to { items: [{ path, name, server, description? }], total, hasMore, nextOffset }, not an { ok, data } envelope. Inspect with await tools.describe({ path }) — resolves to the tool descriptor with inputTypeScript when representable, otherwise the original JSON inputSchema, or { path, error: { code, message, suggestions } }. Then call tools.call(path, args) — resolves to { ok: true, data } or { ok: false, error: { code, message } }; data is the raw MCP result: tool calls usually return { content, structuredContent? }, while resource reads return { contents }. The sandbox has no Node, filesystem, or network globals. Use direct flat calls when the name is already known; use emit(value) for user-visible output. Load the mcp-scripting skill for the full workflow guide.",
627
- promptSnippet: "Batch multiple MCP tool calls in one JavaScript request (loop, filter, chain)",
628
- parameters: Type.Object({
629
- code: Type.String({ description: "Trusted JavaScript MCP script. Use tools.<prefixedToolName>(args) and emit(value)." }),
630
- timeoutMs: Type.Optional(Type.Number({ minimum: 1, description: options.defaultScriptTimeoutMs === null
631
- ? "Execution timeout in milliseconds (no default deadline)"
632
- : `Execution timeout in milliseconds (default: ${options.defaultScriptTimeoutMs ?? DEFAULT_MCP_SCRIPT_TIMEOUT_MS})` })),
633
- }),
634
- renderResult: renderMcpToolResult,
635
- async execute(toolCallId: string, params: { code: string; timeoutMs?: number }, signal: AbortSignal | undefined, _onUpdate: AgentToolUpdateCallback<Record<string, unknown>> | undefined, ctx: ExtensionContext) {
636
- const executeOwner = currentOwner;
637
- if (!state && initPromise) {
638
- try {
639
- const initialized = await awaitWithTimeout(abortable(initPromise, signal), INIT_WAIT_TIMEOUT_MS);
640
- if (initialized === INIT_WAIT_TIMED_OUT) {
641
- return {
642
- content: [{ type: "text" as const, text: "MCP initialization is still in progress. Try again shortly." }],
643
- details: { mode: "script", error: "init_timeout", timeoutMs: INIT_WAIT_TIMEOUT_MS },
644
- };
645
- }
646
- executeOwner?.throwIfInactive();
647
- state = initialized;
648
- } catch (error) {
649
- if (signal?.aborted || (executeOwner && isAbortError(error, executeOwner.signal))) throw error;
650
- const message = error instanceof Error ? error.message : String(error);
651
- return {
652
- content: [{ type: "text" as const, text: `MCP initialization failed: ${message}` }],
653
- details: { mode: "script", error: "init_failed", message },
654
- };
655
- }
656
- }
657
- if (!state) {
658
- return {
659
- content: [{ type: "text" as const, text: "MCP not initialized" }],
660
- details: { mode: "script", error: "not_initialized" },
661
- };
662
- }
663
- executeOwner?.throwIfInactive();
664
- return runMcpScript(state, params.code, params.timeoutMs ?? options.defaultScriptTimeoutMs, getPiTools, signal, toolCallId,
665
- beforeExecute ? (callSignal, operation) => beforeExecute(toolCallId, { ...ctx, signal: callSignal }, operation) : undefined);
666
- },
667
- });
668
- scriptToolRegistered = true;
669
- }
670
-
671
- function syncScriptTool(config: McpConfig): void {
672
- if (config.settings?.scriptMode === false) {
673
- if (scriptToolRegistered) deactivateTools(["mcp_script"]);
674
- return;
675
- }
676
-
677
- registerScriptTool();
678
- if (deactivatedTools.delete("mcp_script")) {
679
- const activeTools = pi.getActiveTools();
680
- if (!activeTools.includes("mcp_script")) pi.setActiveTools([...activeTools, "mcp_script"]);
681
- }
682
- }
683
-
684
- function registerProxyTool(description: string): void {
685
- pi.registerTool({
686
- ...(beforeExecute ? { executionMode: "sequential" as const } : {}),
687
- name: "mcp",
688
- label: "MCP",
689
- description,
690
- promptSnippet: "MCP gateway — status, search, describe, auth, and single MCP tool calls",
691
- renderCall: renderMcpProxyToolCall,
692
- parameters: Type.Object({
693
- tool: Type.Optional(Type.String({ maxLength: MAX_TOOL_NAME_LENGTH, description: "Tool name to call (e.g., 'xcodebuild_list_sims')" })),
694
- args: Type.Optional(Type.Union([
695
- Type.String({ description: "Arguments as a JSON string (e.g., '{\"key\": \"value\"}')" }),
696
- Type.Object({}, {
697
- additionalProperties: true,
698
- description: 'Arguments as a JSON object (e.g., { "key": "value" })',
699
- }),
700
- ], { description: "Tool arguments as a JSON object, or as a JSON string encoding one" })),
701
- connect: Type.Optional(Type.String({ description: "Server name to connect (lazy connect + metadata refresh)" })),
702
- describe: Type.Optional(Type.String({ maxLength: MAX_TOOL_NAME_LENGTH, description: "Tool name to describe (shows parameters)" })),
703
- instructions: Type.Optional(Type.String({ description: "Server name to show that server's usage instructions" })),
704
- search: Type.Optional(Type.String({ description: "Search tools by name/description" })),
705
- includeSchemas: Type.Optional(Type.Boolean({ description: "Include parameter schemas in search results (default: true)" })),
706
- limit: Type.Optional(Type.Number({ minimum: 1, maximum: MAX_PAGE_SIZE, description: "Maximum search or server-list results to return (default: 12, max: 100)" })),
707
- offset: Type.Optional(Type.Number({ minimum: 0, description: "Search or server-list result offset (default: 0)" })),
708
- server: Type.Optional(Type.String({ description: "Filter to specific server (also disambiguates tool calls)" })),
709
- action: Type.Optional(Type.String({ description: "Action: 'ui-messages', 'auth-start', or 'auth-complete'" })),
710
- }),
711
- renderResult: renderMcpToolResult,
712
- async execute(toolCallId: string, params: {
713
- tool?: string;
714
- args?: string | Record<string, unknown>;
715
- connect?: string;
716
- describe?: string;
717
- instructions?: string;
718
- search?: string;
719
- includeSchemas?: boolean;
720
- limit?: number;
721
- offset?: number;
722
- server?: string;
723
- action?: string;
724
- }, signal: AbortSignal | undefined, _onUpdate: AgentToolUpdateCallback<Record<string, unknown>> | undefined, ctx: ExtensionContext) {
725
- // Non-call modes have no resolved MCP operation; hosts keep their conservative policy.
726
- if (!params.tool || params.action === "ui-messages" || params.action === "auth-start" || params.action === "auth-complete") {
727
- if (beforeExecute) {
728
- if (currentOwner) await currentOwner.runCallback("beforeExecute", () => beforeExecute(toolCallId, ctx));
729
- else await beforeExecute(toolCallId, ctx);
730
- }
731
- }
732
- const executeOwner = currentOwner;
733
- let parsedArgs: Record<string, unknown> | undefined;
734
- if (params.args !== undefined && params.args !== "") {
735
- let args: unknown;
736
- if (typeof params.args === "string") {
737
- try {
738
- args = JSON.parse(params.args);
739
- } catch (error) {
740
- if (error instanceof SyntaxError) {
741
- throw new Error(`Invalid args JSON: ${error.message}`, { cause: error });
742
- }
743
- throw error;
744
- }
745
- } else {
746
- args = params.args;
747
- }
748
-
749
- if (typeof args !== "object" || args === null || Array.isArray(args)) {
750
- const gotType = Array.isArray(args) ? "array" : args === null ? "null" : typeof args;
751
- throw new Error(`Invalid args: expected a JSON object, got ${gotType}`);
752
- }
753
- parsedArgs = args as Record<string, unknown>;
754
- }
755
-
756
- if (!state && initPromise) {
757
- try {
758
- const initialized = await awaitWithTimeout(abortable(initPromise, signal), INIT_WAIT_TIMEOUT_MS);
759
- if (initialized === INIT_WAIT_TIMED_OUT) {
760
- return {
761
- content: [{ type: "text" as const, text: "MCP initialization is still in progress. Try again shortly." }],
762
- details: { error: "init_timeout", timeoutMs: INIT_WAIT_TIMEOUT_MS },
763
- };
764
- }
765
- executeOwner?.throwIfInactive();
766
- state = initialized;
767
- } catch (error) {
768
- if (signal?.aborted || (executeOwner && isAbortError(error, executeOwner.signal))) throw error;
769
- const message = error instanceof Error ? error.message : String(error);
770
- return {
771
- content: [{ type: "text" as const, text: `MCP initialization failed: ${message}` }],
772
- details: { error: "init_failed", message },
773
- };
774
- }
775
- }
776
- if (!state) {
777
- return {
778
- content: [{ type: "text" as const, text: "MCP not initialized" }],
779
- details: { error: "not_initialized" },
780
- };
781
- }
782
- executeOwner?.throwIfInactive();
783
-
784
- if (params.action === "ui-messages") {
785
- return executeUiMessages(state);
786
- }
787
- if (params.action === "auth-start") {
788
- if (!params.server) {
789
- return {
790
- content: [{ type: "text" as const, text: "auth-start requires `server`. Example: mcp({ action: \"auth-start\", server: \"linear-server\" })" }],
791
- details: { mode: "auth-start", error: "missing_server" },
792
- };
793
- }
794
- return signal
795
- ? executeAuthStart(state, params.server, signal)
796
- : executeAuthStart(state, params.server);
797
- }
798
- if (params.action === "auth-complete") {
799
- if (!params.server) {
800
- return {
801
- content: [{ type: "text" as const, text: "auth-complete requires `server`." }],
802
- details: { mode: "auth-complete", error: "missing_server" },
803
- };
804
- }
805
- const input = parsedArgs?.redirectUrl ?? parsedArgs?.code ?? parsedArgs?.input;
806
- if (input !== undefined && (typeof input !== "string" || input.trim().length === 0)) {
807
- return {
808
- content: [{ type: "text" as const, text: "auth-complete accepts a non-empty `redirectUrl`, `code`, or `input`, or no args to use the browser callback." }],
809
- details: { mode: "auth-complete", error: "missing_input" },
810
- };
811
- }
812
- return signal
813
- ? executeAuthComplete(state, params.server, input, signal)
814
- : executeAuthComplete(state, params.server, input);
815
- }
816
- if (params.tool) {
817
- return executeCall(state, params.tool, parsedArgs, params.server, getPiTools, signal, undefined, { toolCallId },
818
- beforeExecute ? (callSignal, operation) => beforeExecute(toolCallId, { ...ctx, signal: callSignal }, operation) : undefined);
819
- }
820
- if (params.connect) {
821
- const result = await executeConnect(state, params.connect, signal, params.limit, params.offset);
822
- syncToolSurface(ctx);
823
- return result;
824
- }
825
- if (params.describe) {
826
- return executeDescribe(state, params.describe);
827
- }
828
- if (params.instructions) {
829
- return executeInstructions(state, params.instructions);
830
- }
831
- if (params.search !== undefined) {
832
- return executeSearch(state, params.search, params.server, params.includeSchemas, params.limit, params.offset);
833
- }
834
- if (params.server) {
835
- return executeList(state, params.server, params.limit, params.offset);
836
- }
837
- return executeStatus(state);
838
- },
839
- });
840
- proxyToolRegistered = true;
841
- proxyToolDescription = description;
842
- }
843
-
844
- function syncProxyTool(config: McpConfig, cache: MetadataCache | null, directSpecs: DirectToolSpec[]): void {
845
- const missingConfiguredDirectToolServers = getMissingConfiguredDirectToolServers(
846
- config,
847
- cache,
848
- envRaw === undefined || envRaw === "__none__" ? undefined : envDirectToolOverride,
849
- );
850
- const shouldRegisterProxyTool =
851
- config.settings?.disableProxyTool !== true
852
- || directSpecs.length === 0
853
- || missingConfiguredDirectToolServers.length > 0;
854
-
855
- if (shouldRegisterProxyTool) {
856
- const description = buildProxyDescription(config);
857
- if (!proxyToolRegistered || proxyToolDescription !== description) {
858
- registerProxyTool(description);
859
- }
860
- if (deactivatedTools.delete("mcp")) {
861
- const activeTools = pi.getActiveTools();
862
- if (!activeTools.includes("mcp")) {
863
- pi.setActiveTools([...activeTools, "mcp"]);
864
- }
865
- }
866
- return;
867
- }
868
-
869
- if (proxyToolRegistered) deactivateTools(["mcp"]);
870
- }
871
-
872
- // A transform needs the session context before any configured tool surface is registered.
873
- if (!options.transformConfig) {
874
- const initialDirectTools = syncDirectTools(earlyConfig, earlyCache).specs;
875
- syncProxyTool(earlyConfig, earlyCache, initialDirectTools);
876
- syncScriptTool(earlyConfig);
877
- }
878
- }
879
-
880
- export function createMcpAdapter(options: McpAdapterOptions = {}) {
881
- const { defaultScriptTimeoutMs } = options;
882
- if (defaultScriptTimeoutMs !== undefined && defaultScriptTimeoutMs !== null
883
- && (!Number.isInteger(defaultScriptTimeoutMs) || defaultScriptTimeoutMs < 1 || defaultScriptTimeoutMs > 2_147_483_647)) {
884
- throw new RangeError("defaultScriptTimeoutMs must be an integer from 1 to 2147483647, or null");
885
- }
886
- const factoryConfig = options.config !== undefined ? cloneMcpConfig(options.config) : undefined;
887
- return function mcpAdapter(pi: ExtensionAPI) {
888
- installMcpAdapter(pi, {
889
- ...(options.configPath !== undefined ? { configPath: options.configPath } : {}),
890
- ...(defaultScriptTimeoutMs !== undefined ? { defaultScriptTimeoutMs } : {}),
891
- ...(options.outputDirectory !== undefined ? { outputDirectory: options.outputDirectory } : {}),
892
- ...(factoryConfig !== undefined ? { config: cloneMcpConfig(factoryConfig) } : {}),
893
- ...(options.beforeExecute ? { beforeExecute: options.beforeExecute } : {}),
894
- ...(options.transformConfig ? { transformConfig: options.transformConfig } : {}),
895
- ...(options.onToolCall ? { onToolCall: options.onToolCall } : {}),
896
- });
897
- };
898
- }
899
-
900
- export default createMcpAdapter();