@fitchmultz/pi-mcp-adapter 6.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.
- package/CHANGELOG.md +26 -0
- package/README.md +12 -8
- package/dist/app-bridge.bundle.js +14 -14
- package/dist/config.js +41 -16
- package/dist/gateway-arguments.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/lifecycle.d.ts +2 -2
- package/dist/lifecycle.js +2 -2
- package/dist/mcp-auth-flow.js +2 -2
- package/dist/mcp-auth.d.ts +1 -1
- package/dist/mcp-auth.js +3 -3
- package/dist/mcp-code.d.ts +6 -6
- package/dist/mcp-code.js +11 -10
- package/dist/mcp-oauth-provider.d.ts +5 -5
- package/dist/mcp-oauth-provider.js +5 -5
- package/dist/mcp-output-guard.d.ts +1 -1
- package/dist/mcp-panel.d.ts +2 -2
- package/dist/mcp-panel.js +2 -2
- package/dist/mcp-setup-panel.d.ts +5 -4
- package/dist/mcp-setup-panel.js +15 -7
- package/dist/mcp-trace.d.ts +1 -1
- package/dist/mcp-trace.js +1 -1
- package/dist/npx-resolver.js +8 -4
- package/dist/proxy-modes.d.ts +2 -2
- package/dist/proxy-modes.js +9 -9
- package/dist/sampling-handler.js +1 -0
- package/dist/search-ranking.js +6 -2
- package/dist/server-manager.d.ts +2 -2
- package/dist/server-manager.js +44 -27
- package/dist/session-recovery.d.ts +7 -4
- package/dist/session-recovery.js +76 -10
- package/dist/tool-metadata.d.ts +43 -43
- package/dist/tool-result-renderer.d.ts +3 -3
- package/dist/tool-result-renderer.js +3 -3
- package/dist/ui-resource-handler.d.ts +2 -2
- package/dist/ui-resource-handler.js +2 -2
- package/dist/ui-server.js +4 -3
- package/dist/ui-stream-types.d.ts +8 -8
- package/dist/unix-socket-transport.d.ts +1 -1
- package/dist/unix-socket-transport.js +1 -1
- package/dist/utils.d.ts +1 -0
- package/dist/utils.js +14 -0
- package/package.json +34 -98
- package/abort.ts +0 -34
- package/agent-dir.ts +0 -24
- package/app-bridge.bundle.js +0 -109
- package/banner.png +0 -0
- package/checkpoint.ts +0 -55
- package/commands.ts +0 -622
- package/config.ts +0 -1204
- package/consent-manager.ts +0 -25
- package/direct-tools.ts +0 -75
- package/elicitation-handler.ts +0 -349
- package/error-signal.ts +0 -21
- package/gateway-arguments.ts +0 -73
- package/glimpse-ui.ts +0 -80
- package/host-html-template.ts +0 -456
- package/index.ts +0 -767
- package/init.ts +0 -613
- package/legacy-migration.ts +0 -47
- package/lifecycle.ts +0 -165
- package/logger.ts +0 -127
- package/mcp-auth-flow.ts +0 -975
- package/mcp-auth.ts +0 -793
- package/mcp-callback-server.ts +0 -450
- package/mcp-code.ts +0 -412
- package/mcp-keyring-helper.cjs +0 -89
- package/mcp-oauth-provider.ts +0 -774
- package/mcp-output-guard.ts +0 -530
- package/mcp-panel.ts +0 -907
- package/mcp-script-worker.mjs +0 -131
- package/mcp-setup-panel.ts +0 -666
- package/mcp-status.ts +0 -110
- package/mcp-trace.ts +0 -295
- package/metadata-cache.ts +0 -271
- package/npx-resolver.ts +0 -488
- package/onboarding-state.ts +0 -58
- package/panel-keys.ts +0 -37
- package/prompts.ts +0 -337
- package/proxy-modes.ts +0 -1109
- package/resource-tools.ts +0 -52
- package/runtime-owner.ts +0 -123
- package/sampling-handler.ts +0 -276
- package/scripts/build-app-bridge.mjs +0 -15
- package/scripts/build.mjs +0 -132
- package/scripts/prepare.mjs +0 -103
- package/search-ranking.ts +0 -132
- package/server-manager.ts +0 -1320
- package/session-recovery.ts +0 -316
- package/state.ts +0 -64
- package/tool-approval.ts +0 -78
- package/tool-loader.ts +0 -153
- package/tool-metadata.ts +0 -240
- package/tool-registrar.ts +0 -74
- package/tool-result-renderer.ts +0 -294
- package/tsconfig.build.json +0 -13
- package/tsconfig.json +0 -16
- package/types.ts +0 -785
- package/ui-resource-handler.ts +0 -249
- package/ui-server.ts +0 -808
- package/ui-session.ts +0 -568
- package/ui-stream-types.ts +0 -89
- package/ui-tool-visibility.ts +0 -25
- package/unix-socket-transport.ts +0 -85
- package/utils.ts +0 -307
package/index.ts
DELETED
|
@@ -1,767 +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, directToolSelection, getMissingConfiguredDirectToolServers } from "./direct-tools.ts";
|
|
9
|
-
import { flushMetadataCache, initializeMcp, lazyConnect, updateStatusBar } from "./init.ts";
|
|
10
|
-
import { getMetadataCachePath, isServerCacheValid, loadMetadataCache, reconstructToolMetadata, 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 } from "./utils.ts";
|
|
15
|
-
import { createOAuthRuntime, shutdownOAuth } from "./mcp-auth-flow.ts";
|
|
16
|
-
import { 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, paginate, rankToolMatches } from "./search-ranking.ts";
|
|
22
|
-
import { createToolLoader, supportsNativeAsync, type SearchAPI } from "./tool-loader.ts";
|
|
23
|
-
import { gatewayParameters, prepareGatewayArguments, type GatewayArguments } from "./gateway-arguments.ts";
|
|
24
|
-
import { executeResourceList, executeResourceRead } from "./resource-tools.ts";
|
|
25
|
-
import { guardMcpOutput, guardedMcpDetails, readMcpResult, resolveMcpOutputGuardOptions } from "./mcp-output-guard.ts";
|
|
26
|
-
import { abortable } from "./abort.ts";
|
|
27
|
-
import { onSessionCheckpoint, prepareMcpCheckpoint } from "./checkpoint.ts";
|
|
28
|
-
|
|
29
|
-
export type { McpAdapterOptions, McpOperationContext, McpToolCallEvent, McpToolCallIdentity } from "./types.ts";
|
|
30
|
-
export {
|
|
31
|
-
MCP_STATUS_EVENT,
|
|
32
|
-
MCP_STATUS_SNAPSHOT_VERSION,
|
|
33
|
-
type McpServerRuntimeStatus,
|
|
34
|
-
type McpServerStatusSnapshot,
|
|
35
|
-
type McpStatusSnapshot,
|
|
36
|
-
} from "./types.ts";
|
|
37
|
-
|
|
38
|
-
const INIT_WAIT_TIMEOUT_MS = 30_000;
|
|
39
|
-
const INIT_WAIT_TIMED_OUT: unique symbol = Symbol("init-wait-timed-out");
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
async function awaitWithTimeout<T>(promise: Promise<T>, timeoutMs: number): Promise<T | typeof INIT_WAIT_TIMED_OUT> {
|
|
43
|
-
let timer: ReturnType<typeof setTimeout> | undefined;
|
|
44
|
-
try {
|
|
45
|
-
return await Promise.race([
|
|
46
|
-
promise,
|
|
47
|
-
new Promise<typeof INIT_WAIT_TIMED_OUT>((resolve) => {
|
|
48
|
-
timer = setTimeout(() => resolve(INIT_WAIT_TIMED_OUT), timeoutMs);
|
|
49
|
-
timer.unref?.();
|
|
50
|
-
}),
|
|
51
|
-
]);
|
|
52
|
-
} finally {
|
|
53
|
-
clearTimeout(timer);
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
function installMcpAdapter(pi: ExtensionAPI, options: McpAdapterOptions) {
|
|
58
|
-
const beforeExecute = options.beforeExecute;
|
|
59
|
-
const sessionConfig = options.config !== undefined ? cloneMcpConfig(options.config) : undefined;
|
|
60
|
-
const programmaticConfig = sessionConfig !== undefined;
|
|
61
|
-
let state: McpExtensionState | null = null;
|
|
62
|
-
let initPromise: Promise<McpExtensionState> | null = null;
|
|
63
|
-
let currentOwner: McpRuntimeOwner | null = null;
|
|
64
|
-
let currentOAuthRuntime: McpOAuthRuntime | null = null;
|
|
65
|
-
let lifecycleGeneration = 0;
|
|
66
|
-
let currentConfigPath: string | undefined;
|
|
67
|
-
|
|
68
|
-
async function shutdownState(currentState: McpExtensionState | null, reason: string): Promise<void> {
|
|
69
|
-
if (!currentState) {
|
|
70
|
-
publishMcpStatusShutdown(pi.events);
|
|
71
|
-
return;
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
const failures: unknown[] = [];
|
|
75
|
-
try { publishMcpStatusShutdown(currentState.statusEvents); } catch (error) { failures.push(error); }
|
|
76
|
-
try { currentState.uiServer?.close(reason); } catch (error) { failures.push(error); }
|
|
77
|
-
currentState.uiServer = null;
|
|
78
|
-
try { flushMetadataCache(currentState); } catch (error) { failures.push(error); }
|
|
79
|
-
try {
|
|
80
|
-
if (currentState.owner) await currentState.owner.stop(reason);
|
|
81
|
-
else await currentState.lifecycle.gracefulShutdown();
|
|
82
|
-
} catch (error) { failures.push(error); }
|
|
83
|
-
if (failures.length) throw new AggregateError(failures, "MCP state persistence/cleanup failed");
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
const earlyConfig = programmaticConfig
|
|
87
|
-
? cloneMcpConfig(sessionConfig)
|
|
88
|
-
: { mcpServers: {} };
|
|
89
|
-
const earlyCache: MetadataCache | null = null;
|
|
90
|
-
const envRaw = process.env.MCP_DIRECT_TOOLS;
|
|
91
|
-
const envDirectToolOverride = envRaw?.split(",").map(s => s.trim()).filter(Boolean);
|
|
92
|
-
const toolLoader = createToolLoader(pi, () => state, () => initPromise, beforeExecute);
|
|
93
|
-
const deactivatedTools = new Set<string>();
|
|
94
|
-
let runtimeCwd: string | undefined;
|
|
95
|
-
let nativeAsyncSupported = false;
|
|
96
|
-
let proxyToolRegistered = false;
|
|
97
|
-
let proxyToolDescription: string | null = null;
|
|
98
|
-
let directToolsFrozen = false;
|
|
99
|
-
|
|
100
|
-
function deactivateTools(toolNames: string[]): void {
|
|
101
|
-
if (toolNames.length === 0) return;
|
|
102
|
-
const remove = new Set(toolNames);
|
|
103
|
-
const activeTools = pi.getActiveTools();
|
|
104
|
-
for (const toolName of toolNames) {
|
|
105
|
-
if (activeTools.includes(toolName)) deactivatedTools.add(toolName);
|
|
106
|
-
}
|
|
107
|
-
const nextActiveTools = activeTools.filter((name) => !remove.has(name));
|
|
108
|
-
if (nextActiveTools.length !== activeTools.length) {
|
|
109
|
-
pi.setActiveTools(nextActiveTools);
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
function applyDirectToolConfigChanges(changes: Map<string, true | string[] | false>): void {
|
|
114
|
-
if (!state) return;
|
|
115
|
-
for (const [serverName, value] of changes) {
|
|
116
|
-
const definition = state.config.mcpServers[serverName];
|
|
117
|
-
if (!definition) continue;
|
|
118
|
-
state.config.mcpServers[serverName] = { ...definition, directTools: value };
|
|
119
|
-
}
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
function syncToolSurface(ctx?: ExtensionContext): void {
|
|
123
|
-
const config = state?.config ?? earlyConfig;
|
|
124
|
-
const cache = loadMetadataCache(state?.metadataCacheEnabled ?? false);
|
|
125
|
-
toolLoader.sync(config, cache, envRaw === "__none__" ? null : envDirectToolOverride, nativeAsyncSupported, specs => {
|
|
126
|
-
syncProxyTool(config, cache, specs);
|
|
127
|
-
syncScriptTool(config);
|
|
128
|
-
});
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
const registeredPromptCommands = new Set<string>();
|
|
132
|
-
|
|
133
|
-
function registerPromptCommands(specs: Iterable<PromptMetadata>): void {
|
|
134
|
-
for (const spec of specs) {
|
|
135
|
-
if (registeredPromptCommands.has(spec.commandName)) {
|
|
136
|
-
logger.debug(`MCP: prompt "${spec.originalName}" on ${spec.serverName} skipped; /${spec.commandName} is already registered`);
|
|
137
|
-
continue;
|
|
138
|
-
}
|
|
139
|
-
registeredPromptCommands.add(spec.commandName);
|
|
140
|
-
pi.registerCommand(spec.commandName, createPromptCommand(pi, () => state, spec));
|
|
141
|
-
}
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
function syncPromptCommands(): void {
|
|
145
|
-
registerPromptCommands([...(state?.promptMetadata?.values() ?? [])].flat());
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
if (!options.transformConfig) registerPromptCommands(resolveCachedPrompts(earlyConfig, false));
|
|
149
|
-
|
|
150
|
-
const getPiTools = (): ToolInfo[] => pi.getAllTools();
|
|
151
|
-
|
|
152
|
-
pi.registerFlag("mcp-config", {
|
|
153
|
-
description: "Path to MCP config file",
|
|
154
|
-
type: "string",
|
|
155
|
-
});
|
|
156
|
-
|
|
157
|
-
function startInitialization(
|
|
158
|
-
ctx: ExtensionContext,
|
|
159
|
-
owner: McpRuntimeOwner,
|
|
160
|
-
oauthRuntime: McpOAuthRuntime,
|
|
161
|
-
runtimeConfig: McpConfig,
|
|
162
|
-
generation: number,
|
|
163
|
-
): Promise<void> {
|
|
164
|
-
const promise = initializeMcp(pi, ctx, owner, {
|
|
165
|
-
...(programmaticConfig ? { config: runtimeConfig } : { resolvedConfig: runtimeConfig }),
|
|
166
|
-
...(options.outputDirectory !== undefined ? { outputDirectory: options.outputDirectory } : {}),
|
|
167
|
-
oauthRuntime,
|
|
168
|
-
statusEvents: pi.events,
|
|
169
|
-
...(options.onToolCall ? { onToolCall: options.onToolCall } : {}),
|
|
170
|
-
});
|
|
171
|
-
initPromise = promise;
|
|
172
|
-
|
|
173
|
-
return promise.then(async (nextState) => {
|
|
174
|
-
if (!owner.isActive() || generation !== lifecycleGeneration || initPromise !== promise) {
|
|
175
|
-
try {
|
|
176
|
-
await shutdownState(nextState, "stale_session_start");
|
|
177
|
-
} catch (error) {
|
|
178
|
-
console.error(`MCP: failed to clean stale initialization state: ${formatTerminalError(error)}`);
|
|
179
|
-
}
|
|
180
|
-
return;
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
state = nextState;
|
|
184
|
-
nextState.onToolMetadataUpdated = (_serverName, _reason) => {
|
|
185
|
-
if (state !== nextState || !owner.isActive()) return;
|
|
186
|
-
syncPromptCommands();
|
|
187
|
-
if (directToolsFrozen) {
|
|
188
|
-
logger.debug(`MCP: metadata update for ${_serverName} (${_reason}) skipped — directTools frozen`);
|
|
189
|
-
return;
|
|
190
|
-
}
|
|
191
|
-
syncToolSurface(ctx);
|
|
192
|
-
};
|
|
193
|
-
for (const server of toolLoader.selectedServers()) {
|
|
194
|
-
if (!nextState.toolMetadata.has(server) && nextState.config.mcpServers[server]) await lazyConnect(nextState, server, owner.signal);
|
|
195
|
-
}
|
|
196
|
-
owner.throwIfInactive();
|
|
197
|
-
syncPromptCommands();
|
|
198
|
-
syncToolSurface(ctx);
|
|
199
|
-
updateStatusBar(nextState);
|
|
200
|
-
initPromise = null;
|
|
201
|
-
if (nextState.config.settings?.freezeDirectTools === true) {
|
|
202
|
-
directToolsFrozen = true;
|
|
203
|
-
logger.info("MCP: direct tools frozen after initial sync — reconnects won't rebuild the system prompt; use mcp({ action: \"connect\", server: \"server\" }) to rediscover");
|
|
204
|
-
}
|
|
205
|
-
}).catch(async err => {
|
|
206
|
-
if (!owner.isActive() || generation !== lifecycleGeneration) {
|
|
207
|
-
return;
|
|
208
|
-
}
|
|
209
|
-
if (initPromise !== promise && initPromise !== null) {
|
|
210
|
-
return;
|
|
211
|
-
}
|
|
212
|
-
console.error(`MCP initialization failed: ${formatTerminalError(err)}`);
|
|
213
|
-
initPromise = null;
|
|
214
|
-
if (state) return;
|
|
215
|
-
|
|
216
|
-
try {
|
|
217
|
-
await Promise.all([
|
|
218
|
-
owner.stop("MCP initialization failed"),
|
|
219
|
-
shutdownOAuth(oauthRuntime),
|
|
220
|
-
]);
|
|
221
|
-
} catch (error) {
|
|
222
|
-
console.error(`MCP: failed to clean rejected initialization: ${formatTerminalError(error)}`);
|
|
223
|
-
}
|
|
224
|
-
});
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
async function startSession(ctx: ExtensionContext) {
|
|
228
|
-
runtimeCwd = ctx.cwd;
|
|
229
|
-
nativeAsyncSupported = supportsNativeAsync(ctx);
|
|
230
|
-
directToolsFrozen = false;
|
|
231
|
-
const generation = ++lifecycleGeneration;
|
|
232
|
-
const previousState = state;
|
|
233
|
-
const previousOwner = currentOwner;
|
|
234
|
-
const previousOAuthRuntime = currentOAuthRuntime;
|
|
235
|
-
const owner = createMcpRuntimeOwner();
|
|
236
|
-
const oauthRuntime = createOAuthRuntime(owner.signal);
|
|
237
|
-
currentOwner = owner;
|
|
238
|
-
currentOAuthRuntime = oauthRuntime;
|
|
239
|
-
state = null;
|
|
240
|
-
initPromise = null;
|
|
241
|
-
|
|
242
|
-
// Abort synchronously before awaiting cleanup so old callbacks and startup
|
|
243
|
-
// work cannot resume into a stale ExtensionContext.
|
|
244
|
-
const stopPrevious = previousOwner?.stop("MCP extension session restarted") ?? Promise.resolve();
|
|
245
|
-
try {
|
|
246
|
-
await Promise.all([
|
|
247
|
-
stopPrevious,
|
|
248
|
-
shutdownState(previousState, "session_restart"),
|
|
249
|
-
previousOAuthRuntime ? shutdownOAuth(previousOAuthRuntime) : Promise.resolve(),
|
|
250
|
-
]);
|
|
251
|
-
} catch (error) {
|
|
252
|
-
console.error(`MCP: failed to shut down previous session state: ${formatTerminalError(error)}`);
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
if (generation !== lifecycleGeneration || !owner.isActive()) return;
|
|
256
|
-
|
|
257
|
-
let runtimeConfig: McpConfig;
|
|
258
|
-
if (programmaticConfig) {
|
|
259
|
-
currentConfigPath = undefined;
|
|
260
|
-
runtimeConfig = cloneMcpConfig(sessionConfig);
|
|
261
|
-
} else {
|
|
262
|
-
const registeredConfigPath = pi.getFlag("mcp-config");
|
|
263
|
-
currentConfigPath = options.configPath
|
|
264
|
-
?? (typeof registeredConfigPath === "string" ? registeredConfigPath : undefined);
|
|
265
|
-
runtimeConfig = loadMcpConfig(
|
|
266
|
-
currentConfigPath,
|
|
267
|
-
ctx.cwd,
|
|
268
|
-
{ includeProject: ctx.isProjectTrusted() },
|
|
269
|
-
);
|
|
270
|
-
}
|
|
271
|
-
if (options.transformConfig) {
|
|
272
|
-
runtimeConfig = cloneMcpConfig(options.transformConfig(cloneMcpConfig(runtimeConfig), ctx));
|
|
273
|
-
}
|
|
274
|
-
const metadataCacheEnabled = ctx.isProjectTrusted()
|
|
275
|
-
|| !isPathInsideProject(getMetadataCachePath(), ctx.cwd);
|
|
276
|
-
toolLoader.restore(ctx);
|
|
277
|
-
registerPromptCommands(resolveCachedPrompts(runtimeConfig, metadataCacheEnabled));
|
|
278
|
-
const runtimeCache = loadMetadataCache(metadataCacheEnabled);
|
|
279
|
-
toolLoader.sync(runtimeConfig, runtimeCache, envRaw === "__none__" ? null : envDirectToolOverride, nativeAsyncSupported, specs => {
|
|
280
|
-
syncProxyTool(runtimeConfig, runtimeCache, specs);
|
|
281
|
-
syncScriptTool(runtimeConfig);
|
|
282
|
-
});
|
|
283
|
-
|
|
284
|
-
const initialization = startInitialization(ctx, owner, oauthRuntime, runtimeConfig, generation);
|
|
285
|
-
if (envRaw !== undefined && envRaw !== "__none__") {
|
|
286
|
-
const missingEnvDirectTools = getMissingConfiguredDirectToolServers(
|
|
287
|
-
runtimeConfig,
|
|
288
|
-
runtimeCache,
|
|
289
|
-
envDirectToolOverride,
|
|
290
|
-
);
|
|
291
|
-
if (missingEnvDirectTools.length > 0) {
|
|
292
|
-
await initialization;
|
|
293
|
-
}
|
|
294
|
-
}
|
|
295
|
-
}
|
|
296
|
-
|
|
297
|
-
pi.on("session_start", (_event, ctx) => startSession(ctx));
|
|
298
|
-
pi.on("session_tree", (_event, ctx) => {
|
|
299
|
-
toolLoader.restore(ctx);
|
|
300
|
-
syncToolSurface(ctx);
|
|
301
|
-
});
|
|
302
|
-
pi.on("model_select", (_event, ctx) => {
|
|
303
|
-
nativeAsyncSupported = supportsNativeAsync(ctx);
|
|
304
|
-
if (state) syncToolSurface(ctx);
|
|
305
|
-
});
|
|
306
|
-
pi.on("before_agent_start", async (_event, ctx) => {
|
|
307
|
-
if (ctx.cwd !== runtimeCwd) await startSession(ctx);
|
|
308
|
-
});
|
|
309
|
-
|
|
310
|
-
onSessionCheckpoint(pi, async event => {
|
|
311
|
-
if (initPromise) return { sleepReady: false, reason: "MCP initialization is active" };
|
|
312
|
-
if (!state || !currentOwner?.isActive()) return { sleepReady: false, reason: "MCP runtime is not initialized" };
|
|
313
|
-
return prepareMcpCheckpoint(state, event);
|
|
314
|
-
});
|
|
315
|
-
|
|
316
|
-
pi.on("session_shutdown", async () => {
|
|
317
|
-
// Cached tool selections exist before background server initialization finishes.
|
|
318
|
-
toolLoader.persist();
|
|
319
|
-
++lifecycleGeneration;
|
|
320
|
-
const currentState = state;
|
|
321
|
-
const owner = currentOwner;
|
|
322
|
-
const oauthRuntime = currentOAuthRuntime;
|
|
323
|
-
currentOwner = null;
|
|
324
|
-
currentOAuthRuntime = null;
|
|
325
|
-
state = null;
|
|
326
|
-
initPromise = null;
|
|
327
|
-
|
|
328
|
-
// Abort before awaiting cleanup so delayed initialization cannot touch stale
|
|
329
|
-
// Pi context after session shutdown.
|
|
330
|
-
const stopOwner = owner?.stop("MCP extension session shutdown") ?? Promise.resolve();
|
|
331
|
-
const results = await Promise.allSettled([
|
|
332
|
-
stopOwner,
|
|
333
|
-
shutdownState(currentState, "session_shutdown"),
|
|
334
|
-
oauthRuntime ? shutdownOAuth(oauthRuntime) : Promise.resolve(),
|
|
335
|
-
]);
|
|
336
|
-
const failures = results.flatMap(result => result.status === "rejected" ? [result.reason] : []);
|
|
337
|
-
// Ordinary extension error handling owns reporting. Strict clean-exit hosts must see failure.
|
|
338
|
-
if (failures.length) throw new AggregateError(failures, "MCP session shutdown persistence/cleanup failed");
|
|
339
|
-
});
|
|
340
|
-
|
|
341
|
-
// Re-flag returned MCP tool failures so pi registers them as errors (see toolErrorOverride).
|
|
342
|
-
pi.on("tool_result", (event) => toolErrorOverride(event.details));
|
|
343
|
-
|
|
344
|
-
function createCommandContext(ctx: ExtensionContext): {
|
|
345
|
-
owner: McpRuntimeOwner | null;
|
|
346
|
-
context: ExtensionContext;
|
|
347
|
-
} {
|
|
348
|
-
const owner = currentOwner;
|
|
349
|
-
const hasUI = ctx.hasUI;
|
|
350
|
-
const projectTrusted = ctx.isProjectTrusted();
|
|
351
|
-
return {
|
|
352
|
-
owner,
|
|
353
|
-
context: {
|
|
354
|
-
hasUI,
|
|
355
|
-
ui: hasUI ? (owner ? createOwnedUi(ctx.ui, owner) : ctx.ui) : undefined,
|
|
356
|
-
cwd: ctx.cwd,
|
|
357
|
-
mode: ctx.mode,
|
|
358
|
-
signal: owner?.signal ?? ctx.signal,
|
|
359
|
-
isProjectTrusted: () => projectTrusted,
|
|
360
|
-
} as unknown as ExtensionContext,
|
|
361
|
-
};
|
|
362
|
-
}
|
|
363
|
-
|
|
364
|
-
async function ensureCommandState(owner: McpRuntimeOwner | null, ctx: ExtensionContext): Promise<McpExtensionState | null> {
|
|
365
|
-
if (!state && initPromise) {
|
|
366
|
-
try {
|
|
367
|
-
const initialized = await initPromise;
|
|
368
|
-
owner?.throwIfInactive();
|
|
369
|
-
state = initialized;
|
|
370
|
-
} catch (error) {
|
|
371
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
372
|
-
ctx.ui?.notify(`MCP initialization failed: ${message}`, "error");
|
|
373
|
-
return null;
|
|
374
|
-
}
|
|
375
|
-
}
|
|
376
|
-
if (!state) ctx.ui?.notify("MCP not initialized", "error");
|
|
377
|
-
return state;
|
|
378
|
-
}
|
|
379
|
-
|
|
380
|
-
pi.registerCommand("mcp", {
|
|
381
|
-
description: "Show MCP server status",
|
|
382
|
-
getArgumentCompletions: (prefix: string) => {
|
|
383
|
-
const normalized = prefix.trimStart();
|
|
384
|
-
const argumentMatch = normalized.match(/^(\S+)\s+(.*)$/);
|
|
385
|
-
if (!argumentMatch) {
|
|
386
|
-
const subcommands = [
|
|
387
|
-
{ value: "reconnect", label: "reconnect — Reconnect servers" },
|
|
388
|
-
{ value: "tools", label: "tools — List all tools" },
|
|
389
|
-
{ value: "prompts", label: "prompts — List all MCP prompts" },
|
|
390
|
-
{ value: "setup", label: "setup — Configure MCP servers" },
|
|
391
|
-
{ value: "logout", label: "logout — Clear server credentials" },
|
|
392
|
-
{ value: "disable", label: "disable — Disable a server" },
|
|
393
|
-
{ value: "enable", label: "enable — Enable a server" },
|
|
394
|
-
{ value: "status", label: "status — Show server status" },
|
|
395
|
-
].filter(({ value }) => value.startsWith(normalized));
|
|
396
|
-
return subcommands.length > 0 ? subcommands : null;
|
|
397
|
-
}
|
|
398
|
-
|
|
399
|
-
const [, subcommand, argumentPrefix] = argumentMatch;
|
|
400
|
-
if (
|
|
401
|
-
(subcommand !== "reconnect" && subcommand !== "logout" && subcommand !== "disable" && subcommand !== "enable")
|
|
402
|
-
|| argumentPrefix === undefined
|
|
403
|
-
|| !state
|
|
404
|
-
) return null;
|
|
405
|
-
|
|
406
|
-
const servers = Object.keys(state.config.mcpServers)
|
|
407
|
-
.filter((serverName) => serverName.startsWith(argumentPrefix.trimStart()))
|
|
408
|
-
.map((serverName) => ({ value: `${subcommand} ${serverName}`, label: serverName }));
|
|
409
|
-
return servers.length > 0 ? servers : null;
|
|
410
|
-
},
|
|
411
|
-
handler: async (args, ctx) => {
|
|
412
|
-
const { owner: commandOwner, context: commandCtx } = createCommandContext(ctx);
|
|
413
|
-
const commandProjectTrusted = commandCtx.isProjectTrusted();
|
|
414
|
-
const commandState = await ensureCommandState(commandOwner, commandCtx);
|
|
415
|
-
if (!commandState) return;
|
|
416
|
-
|
|
417
|
-
const parts = args?.trim()?.split(/\s+/) ?? [];
|
|
418
|
-
const subcommand = parts[0] ?? "";
|
|
419
|
-
const targetServer = parts[1];
|
|
420
|
-
const rest = parts.slice(1).join(" ");
|
|
421
|
-
|
|
422
|
-
switch (subcommand) {
|
|
423
|
-
case "reconnect":
|
|
424
|
-
commandOwner?.throwIfInactive();
|
|
425
|
-
await reconnectServers(commandState, commandCtx, targetServer);
|
|
426
|
-
if (directToolsFrozen) syncToolSurface(commandCtx);
|
|
427
|
-
break;
|
|
428
|
-
case "tools":
|
|
429
|
-
await showTools(commandState, commandCtx);
|
|
430
|
-
break;
|
|
431
|
-
case "prompts":
|
|
432
|
-
await showPrompts(commandState, commandCtx);
|
|
433
|
-
break;
|
|
434
|
-
case "setup": {
|
|
435
|
-
commandOwner?.throwIfInactive();
|
|
436
|
-
if (programmaticConfig) {
|
|
437
|
-
commandCtx.ui?.notify("MCP setup is unavailable when config is supplied by createMcpAdapter().", "info");
|
|
438
|
-
break;
|
|
439
|
-
}
|
|
440
|
-
const result = await openMcpSetup(commandState, pi, commandCtx, currentConfigPath, "setup");
|
|
441
|
-
if (result?.configChanged) {
|
|
442
|
-
commandOwner?.throwIfInactive();
|
|
443
|
-
await ctx.reload();
|
|
444
|
-
return;
|
|
445
|
-
}
|
|
446
|
-
break;
|
|
447
|
-
}
|
|
448
|
-
case "logout": {
|
|
449
|
-
const serverName = rest;
|
|
450
|
-
if (!serverName) {
|
|
451
|
-
if (commandCtx.hasUI) commandCtx.ui?.notify("Usage: /mcp logout <server>", "error");
|
|
452
|
-
return;
|
|
453
|
-
}
|
|
454
|
-
commandOwner?.throwIfInactive();
|
|
455
|
-
await logoutServer(serverName, commandState, commandCtx);
|
|
456
|
-
break;
|
|
457
|
-
}
|
|
458
|
-
case "disable":
|
|
459
|
-
case "enable": {
|
|
460
|
-
const serverName = rest;
|
|
461
|
-
if (!commandProjectTrusted) {
|
|
462
|
-
commandCtx.ui?.notify("Project MCP changes are unavailable until this project is trusted.", "warning");
|
|
463
|
-
break;
|
|
464
|
-
}
|
|
465
|
-
if (programmaticConfig) {
|
|
466
|
-
commandCtx.ui?.notify(`/mcp ${subcommand} is unavailable when config is supplied by createMcpAdapter().`, "info");
|
|
467
|
-
break;
|
|
468
|
-
}
|
|
469
|
-
if (!serverName) {
|
|
470
|
-
commandCtx.ui?.notify(`Usage: /mcp ${subcommand} <server>`, "error");
|
|
471
|
-
break;
|
|
472
|
-
}
|
|
473
|
-
if (!commandState.config.mcpServers[serverName]) {
|
|
474
|
-
commandCtx.ui?.notify(`Server "${serverName}" not found in effective config`, "error");
|
|
475
|
-
break;
|
|
476
|
-
}
|
|
477
|
-
commandOwner?.throwIfInactive();
|
|
478
|
-
const result = writeProjectServerDisabledOverride(currentConfigPath, commandCtx.cwd, serverName, subcommand === "disable");
|
|
479
|
-
if (result.changed) {
|
|
480
|
-
commandCtx.ui?.notify(`${subcommand === "disable" ? "Disabled" : "Enabled"} server "${serverName}" in ${result.path} — run /reload to apply`, "info");
|
|
481
|
-
} else {
|
|
482
|
-
commandCtx.ui?.notify(`Server "${serverName}" is already ${subcommand === "disable" ? "disabled" : "enabled"}`, "info");
|
|
483
|
-
}
|
|
484
|
-
break;
|
|
485
|
-
}
|
|
486
|
-
case "status":
|
|
487
|
-
case "":
|
|
488
|
-
default:
|
|
489
|
-
if (commandCtx.hasUI) {
|
|
490
|
-
commandOwner?.throwIfInactive();
|
|
491
|
-
if (programmaticConfig) {
|
|
492
|
-
commandCtx.ui?.notify("MCP status is shown from the in-memory SDK config; configuration discovery is unavailable.", "info");
|
|
493
|
-
await showStatus(commandState, commandCtx);
|
|
494
|
-
break;
|
|
495
|
-
}
|
|
496
|
-
const result = await openMcpPanel(commandState, pi, commandCtx, currentConfigPath, (changes) => {
|
|
497
|
-
applyDirectToolConfigChanges(changes);
|
|
498
|
-
syncToolSurface(commandCtx);
|
|
499
|
-
});
|
|
500
|
-
if (result?.configChanged) {
|
|
501
|
-
commandOwner?.throwIfInactive();
|
|
502
|
-
await ctx.reload();
|
|
503
|
-
return;
|
|
504
|
-
}
|
|
505
|
-
} else {
|
|
506
|
-
await showStatus(commandState, commandCtx);
|
|
507
|
-
}
|
|
508
|
-
break;
|
|
509
|
-
}
|
|
510
|
-
},
|
|
511
|
-
});
|
|
512
|
-
|
|
513
|
-
pi.registerCommand("mcp-auth", {
|
|
514
|
-
description: "Authenticate with an MCP server (OAuth)",
|
|
515
|
-
handler: async (args, ctx) => {
|
|
516
|
-
const { owner: commandOwner, context: commandCtx } = createCommandContext(ctx);
|
|
517
|
-
const serverName = args?.trim();
|
|
518
|
-
if (!serverName && !commandCtx.hasUI) {
|
|
519
|
-
return;
|
|
520
|
-
}
|
|
521
|
-
|
|
522
|
-
const commandState = await ensureCommandState(commandOwner, commandCtx);
|
|
523
|
-
if (!commandState) return;
|
|
524
|
-
|
|
525
|
-
if (!serverName) {
|
|
526
|
-
if (programmaticConfig) {
|
|
527
|
-
commandCtx.ui?.notify("Use /mcp-auth <server> to authenticate a server from the in-memory SDK config.", "info");
|
|
528
|
-
return;
|
|
529
|
-
}
|
|
530
|
-
await openMcpAuthPanel(commandState, commandCtx, currentConfigPath);
|
|
531
|
-
return;
|
|
532
|
-
}
|
|
533
|
-
|
|
534
|
-
const result = await authenticateServer(serverName, commandState.config, commandCtx, commandCtx.signal, commandState.oauthRuntime);
|
|
535
|
-
if (result.ok) {
|
|
536
|
-
commandOwner?.throwIfInactive();
|
|
537
|
-
await reconnectServer(commandState, commandCtx, serverName, true);
|
|
538
|
-
}
|
|
539
|
-
},
|
|
540
|
-
});
|
|
541
|
-
|
|
542
|
-
async function getToolState(signal?: AbortSignal) {
|
|
543
|
-
const owner = currentOwner;
|
|
544
|
-
if (!state && initPromise) {
|
|
545
|
-
try {
|
|
546
|
-
const initialized = await awaitWithTimeout(abortable(initPromise, signal), INIT_WAIT_TIMEOUT_MS);
|
|
547
|
-
if (initialized === INIT_WAIT_TIMED_OUT) return {
|
|
548
|
-
content: [{ type: "text" as const, text: "MCP initialization is still in progress. Try again shortly." }],
|
|
549
|
-
details: { error: "init_timeout", timeoutMs: INIT_WAIT_TIMEOUT_MS },
|
|
550
|
-
};
|
|
551
|
-
owner?.throwIfInactive();
|
|
552
|
-
state = initialized;
|
|
553
|
-
} catch (error) {
|
|
554
|
-
if (signal?.aborted || (owner && isAbortError(error, owner.signal))) throw error;
|
|
555
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
556
|
-
return { content: [{ type: "text" as const, text: `MCP initialization failed: ${message}` }], details: { error: "init_failed", message } };
|
|
557
|
-
}
|
|
558
|
-
}
|
|
559
|
-
owner?.throwIfInactive();
|
|
560
|
-
return state ?? { content: [{ type: "text" as const, text: "MCP not initialized" }], details: { error: "not_initialized" } };
|
|
561
|
-
}
|
|
562
|
-
|
|
563
|
-
async function searchAndLoad(params: { query: string; server?: string; limit?: number; offset?: number }, signal: AbortSignal | undefined, ctx: ExtensionContext) {
|
|
564
|
-
const ready = await getToolState(signal);
|
|
565
|
-
if ("content" in ready) return { ...ready, tools: [] };
|
|
566
|
-
if (params.server && !ready.toolMetadata.has(params.server)) {
|
|
567
|
-
const discovery = await executeConnect(ready, params.server, signal);
|
|
568
|
-
if (discovery.details.error) return { ...discovery, tools: [] };
|
|
569
|
-
}
|
|
570
|
-
syncToolSurface(ctx);
|
|
571
|
-
const matches = params.query.trim() ? rankToolMatches(ready, params.query, params.server)
|
|
572
|
-
: params.server ? (ready.toolMetadata.get(params.server) ?? []).filter(tool => !tool.resourceUri).map(tool => ({ server: params.server!, tool })).sort((a, b) => a.tool.name.localeCompare(b.tool.name)) : [];
|
|
573
|
-
const page = paginate(matches, params.offset ?? 0, params.limit ?? 5);
|
|
574
|
-
const tools = toolLoader.activate(page.items);
|
|
575
|
-
const result = executeSearch(ready, params.query, params.server, tools.length < page.items.length, params.limit ?? 5, params.offset);
|
|
576
|
-
const guarded = await guardMcpOutput([
|
|
577
|
-
...result.content,
|
|
578
|
-
{ type: "text", text: tools.length ? `Loaded for the next request: ${JSON.stringify(tools)}` : "No direct tools loaded. Use mcp action call or tools.call in mcp_script with the returned exact names." },
|
|
579
|
-
], resolveMcpOutputGuardOptions(ready.config.settings, ready.outputDirectory));
|
|
580
|
-
return { content: guarded.content, details: { ...result.details, ...guardedMcpDetails(guarded), loaded: tools }, tools };
|
|
581
|
-
}
|
|
582
|
-
|
|
583
|
-
const searchDefinition = {
|
|
584
|
-
name: "mcp_search", label: "MCP Search",
|
|
585
|
-
description: "Discover MCP tools by name, description, and parameter guidance, and load matching tools with their complete schemas for the next request. Start with a specific task query. Specify server to discover an uncached server; an empty query with server browses it. Results report incomplete catalog coverage. Resources use mcp action resources/read-resource.",
|
|
586
|
-
parameters: Type.Object({
|
|
587
|
-
query: Type.String(),
|
|
588
|
-
server: Type.Optional(Type.String()),
|
|
589
|
-
limit: Type.Optional(Type.Integer({ minimum: 1, maximum: MAX_PAGE_SIZE, description: "Tools to load (default 5)." })),
|
|
590
|
-
offset: Type.Optional(Type.Integer({ minimum: 0 })),
|
|
591
|
-
}),
|
|
592
|
-
renderResult: renderMcpToolResult,
|
|
593
|
-
async execute(toolCallId: string, params: { query: string; server?: string; limit?: number; offset?: number }, signal: AbortSignal | undefined, _onUpdate: AgentToolUpdateCallback<Record<string, unknown>> | undefined, ctx: ExtensionContext) {
|
|
594
|
-
if (beforeExecute) {
|
|
595
|
-
if (currentOwner) await currentOwner.runCallback("beforeExecute", () => beforeExecute(toolCallId, ctx));
|
|
596
|
-
else await beforeExecute(toolCallId, ctx);
|
|
597
|
-
}
|
|
598
|
-
return searchAndLoad(params, signal, ctx);
|
|
599
|
-
},
|
|
600
|
-
};
|
|
601
|
-
if (toolLoader.native) (pi as SearchAPI).registerToolSearch!(searchDefinition);
|
|
602
|
-
else pi.registerTool(searchDefinition);
|
|
603
|
-
|
|
604
|
-
let scriptToolRegistered = false;
|
|
605
|
-
|
|
606
|
-
function registerScriptTool(): void {
|
|
607
|
-
if (scriptToolRegistered) return;
|
|
608
|
-
pi.registerTool({
|
|
609
|
-
...(beforeExecute ? { executionMode: "sequential" as const } : {}),
|
|
610
|
-
name: "mcp_script",
|
|
611
|
-
label: "MCP Script",
|
|
612
|
-
description: "Compose MCP calls with trusted JavaScript: await tools.search({query, server?}), tools.describe({path}), tools.call(path,args). Describe returns complete JSON schemas. Calls return {ok,data} with raw MCP results, or {ok:false,error,data?}. Resources: tools.resources({server}), tools.readResource({server,uri}). Saved results: tools.readResult({ref,path?,fields?,offset?,limit?}); never repeats a remote call. Use emit(value) to return selected data or content blocks. No Node, filesystem, or network globals. Prefer a direct tool for one call; use scripts for loops, filtering and chains.",
|
|
613
|
-
promptSnippet: "Batch multiple MCP tool calls in one JavaScript request (loop, filter, chain)",
|
|
614
|
-
parameters: Type.Object({
|
|
615
|
-
code: Type.String({ description: "Trusted JavaScript MCP script. Use tools.<prefixedToolName>(args) and emit(value)." }),
|
|
616
|
-
timeoutMs: Type.Optional(Type.Number({ minimum: 1, description: options.defaultScriptTimeoutMs === null
|
|
617
|
-
? "Execution timeout in milliseconds (no default deadline)"
|
|
618
|
-
: `Execution timeout in milliseconds (default: ${options.defaultScriptTimeoutMs ?? DEFAULT_MCP_SCRIPT_TIMEOUT_MS})` })),
|
|
619
|
-
}),
|
|
620
|
-
renderResult: renderMcpToolResult,
|
|
621
|
-
async execute(toolCallId: string, params: { code: string; timeoutMs?: number }, signal: AbortSignal | undefined, _onUpdate: AgentToolUpdateCallback<Record<string, unknown>> | undefined, ctx: ExtensionContext) {
|
|
622
|
-
const ready = await getToolState(signal);
|
|
623
|
-
if ("content" in ready) return ready;
|
|
624
|
-
return runMcpScript(ready, params.code, params.timeoutMs ?? options.defaultScriptTimeoutMs, getPiTools, signal, toolCallId,
|
|
625
|
-
beforeExecute ? (callSignal, operation) => beforeExecute(toolCallId, { ...ctx, signal: callSignal }, operation) : undefined);
|
|
626
|
-
},
|
|
627
|
-
});
|
|
628
|
-
scriptToolRegistered = true;
|
|
629
|
-
}
|
|
630
|
-
|
|
631
|
-
function syncScriptTool(config: McpConfig): void {
|
|
632
|
-
if (config.settings?.scriptMode === false) {
|
|
633
|
-
if (scriptToolRegistered) deactivateTools(["mcp_script"]);
|
|
634
|
-
return;
|
|
635
|
-
}
|
|
636
|
-
|
|
637
|
-
registerScriptTool();
|
|
638
|
-
if (deactivatedTools.delete("mcp_script")) {
|
|
639
|
-
const activeTools = pi.getActiveTools();
|
|
640
|
-
if (!activeTools.includes("mcp_script")) pi.setActiveTools([...activeTools, "mcp_script"]);
|
|
641
|
-
}
|
|
642
|
-
}
|
|
643
|
-
|
|
644
|
-
function registerProxyTool(description: string): void {
|
|
645
|
-
pi.registerTool({
|
|
646
|
-
...(beforeExecute ? { executionMode: "sequential" as const } : {}),
|
|
647
|
-
name: "mcp", label: "MCP", description,
|
|
648
|
-
promptSnippet: "MCP server operations, resources, saved result readback, and fallback tool calls",
|
|
649
|
-
renderCall: renderMcpProxyToolCall,
|
|
650
|
-
parameters: gatewayParameters,
|
|
651
|
-
prepareArguments: prepareGatewayArguments,
|
|
652
|
-
renderResult: renderMcpToolResult,
|
|
653
|
-
async execute(toolCallId: string, rawParams: GatewayArguments, signal: AbortSignal | undefined, _onUpdate: AgentToolUpdateCallback<Record<string, unknown>> | undefined, ctx: ExtensionContext) {
|
|
654
|
-
const params = prepareGatewayArguments(rawParams);
|
|
655
|
-
if (beforeExecute && params.action !== "call" && params.action !== "read-resource") {
|
|
656
|
-
if (currentOwner) await currentOwner.runCallback("beforeExecute", () => beforeExecute(toolCallId, ctx));
|
|
657
|
-
else await beforeExecute(toolCallId, ctx);
|
|
658
|
-
}
|
|
659
|
-
const ready = await getToolState(signal);
|
|
660
|
-
if ("content" in ready) return ready;
|
|
661
|
-
const present = async (result: Awaited<ReturnType<typeof executeCall>>) => {
|
|
662
|
-
const guarded = await guardMcpOutput(result.content, resolveMcpOutputGuardOptions(ready.config.settings, ready.outputDirectory));
|
|
663
|
-
return { content: guarded.content, details: { ...result.details, ...guardedMcpDetails(guarded) } };
|
|
664
|
-
};
|
|
665
|
-
const beforeDispatch = beforeExecute
|
|
666
|
-
? (callSignal: AbortSignal | undefined, operation: import("./types.ts").McpOperationContext) => beforeExecute(toolCallId, { ...ctx, signal: callSignal }, operation)
|
|
667
|
-
: undefined;
|
|
668
|
-
if (params.server && ["list", "search", "describe", "instructions"].includes(params.action) && !ready.toolMetadata.has(params.server)) {
|
|
669
|
-
const discovery = await executeConnect(ready, params.server, signal);
|
|
670
|
-
if (discovery.details.error) return discovery;
|
|
671
|
-
syncToolSurface(ctx);
|
|
672
|
-
}
|
|
673
|
-
switch (params.action) {
|
|
674
|
-
case "ui-messages": return executeUiMessages(ready);
|
|
675
|
-
case "auth-start": return executeAuthStart(ready, params.server!, signal);
|
|
676
|
-
case "auth-complete": {
|
|
677
|
-
const input = params.args?.redirectUrl ?? params.args?.code ?? params.args?.input;
|
|
678
|
-
if (input !== undefined && (typeof input !== "string" || input.trim().length === 0)) throw new Error("auth-complete requires a non-empty redirectUrl, code, or input");
|
|
679
|
-
return executeAuthComplete(ready, params.server!, input, signal);
|
|
680
|
-
}
|
|
681
|
-
case "call": return executeCall(ready, params.tool!, params.args, params.server, getPiTools, signal, { toolCallId }, beforeDispatch);
|
|
682
|
-
case "connect": {
|
|
683
|
-
const result = await executeConnect(ready, params.server!, signal, params.limit, params.offset);
|
|
684
|
-
syncToolSurface(ctx);
|
|
685
|
-
return present(result);
|
|
686
|
-
}
|
|
687
|
-
case "describe": return present(executeDescribe(ready, params.tool!, params.server));
|
|
688
|
-
case "instructions": return present(executeInstructions(ready, params.server!));
|
|
689
|
-
case "search": return present(executeSearch(ready, params.query!, params.server, params.includeSchemas, params.limit, params.offset));
|
|
690
|
-
case "list": return present(executeList(ready, params.server!, params.limit, params.offset));
|
|
691
|
-
case "resources": return present(await executeResourceList(ready, params.server!, params.limit, params.offset, signal));
|
|
692
|
-
case "read-resource": return executeResourceRead(ready, params.server!, params.uri!, signal, { toolCallId }, beforeDispatch);
|
|
693
|
-
case "read-result": return readMcpResult({ ref: params.ref!, ...(params.path !== undefined ? { path: params.path } : {}), ...(params.fields ? { fields: params.fields } : {}), ...(params.offset !== undefined ? { offset: params.offset } : {}), ...(params.limit !== undefined ? { limit: params.limit } : {}) }, resolveMcpOutputGuardOptions(ready.config.settings, ready.outputDirectory));
|
|
694
|
-
case "status": return present(executeStatus(ready));
|
|
695
|
-
}
|
|
696
|
-
},
|
|
697
|
-
});
|
|
698
|
-
proxyToolRegistered = true;
|
|
699
|
-
proxyToolDescription = description;
|
|
700
|
-
}
|
|
701
|
-
|
|
702
|
-
function syncProxyTool(config: McpConfig, cache: MetadataCache | null, directSpecs: DirectToolSpec[]): void {
|
|
703
|
-
const missingConfiguredDirectToolServers = getMissingConfiguredDirectToolServers(
|
|
704
|
-
config,
|
|
705
|
-
cache,
|
|
706
|
-
envRaw === undefined || envRaw === "__none__" ? undefined : envDirectToolOverride,
|
|
707
|
-
);
|
|
708
|
-
const hasPinnedResources = envRaw !== "__none__" && Object.entries(config.mcpServers).some(([server, definition]) => {
|
|
709
|
-
if (definition.disabled === true) return false;
|
|
710
|
-
const selection = directToolSelection(config, server, envDirectToolOverride);
|
|
711
|
-
if (!selection) return false;
|
|
712
|
-
const entry = cache?.servers[server];
|
|
713
|
-
const metadata = state?.toolMetadata.get(server) ?? (entry && isServerCacheValid(entry, definition)
|
|
714
|
-
? reconstructToolMetadata(server, entry, config.settings?.toolPrefix ?? "server", definition) : []);
|
|
715
|
-
return metadata.some(tool => tool.resourceUri && (selection === true || selection.includes(tool.originalName)));
|
|
716
|
-
});
|
|
717
|
-
const shouldRegisterProxyTool =
|
|
718
|
-
config.settings?.disableProxyTool !== true
|
|
719
|
-
|| hasPinnedResources
|
|
720
|
-
|| directSpecs.length === 0
|
|
721
|
-
|| missingConfiguredDirectToolServers.length > 0;
|
|
722
|
-
|
|
723
|
-
if (shouldRegisterProxyTool) {
|
|
724
|
-
const description = buildProxyDescription(config);
|
|
725
|
-
if (!proxyToolRegistered || proxyToolDescription !== description) {
|
|
726
|
-
registerProxyTool(description);
|
|
727
|
-
}
|
|
728
|
-
if (deactivatedTools.delete("mcp")) {
|
|
729
|
-
const activeTools = pi.getActiveTools();
|
|
730
|
-
if (!activeTools.includes("mcp")) {
|
|
731
|
-
pi.setActiveTools([...activeTools, "mcp"]);
|
|
732
|
-
}
|
|
733
|
-
}
|
|
734
|
-
return;
|
|
735
|
-
}
|
|
736
|
-
|
|
737
|
-
if (proxyToolRegistered) deactivateTools(["mcp"]);
|
|
738
|
-
}
|
|
739
|
-
|
|
740
|
-
// A transform needs the session context before any configured tool surface is registered.
|
|
741
|
-
if (!options.transformConfig) {
|
|
742
|
-
syncProxyTool(earlyConfig, earlyCache, []);
|
|
743
|
-
syncScriptTool(earlyConfig);
|
|
744
|
-
}
|
|
745
|
-
}
|
|
746
|
-
|
|
747
|
-
export function createMcpAdapter(options: McpAdapterOptions = {}) {
|
|
748
|
-
const { defaultScriptTimeoutMs } = options;
|
|
749
|
-
if (defaultScriptTimeoutMs !== undefined && defaultScriptTimeoutMs !== null
|
|
750
|
-
&& (!Number.isInteger(defaultScriptTimeoutMs) || defaultScriptTimeoutMs < 1 || defaultScriptTimeoutMs > 2_147_483_647)) {
|
|
751
|
-
throw new RangeError("defaultScriptTimeoutMs must be an integer from 1 to 2147483647, or null");
|
|
752
|
-
}
|
|
753
|
-
const factoryConfig = options.config !== undefined ? cloneMcpConfig(options.config) : undefined;
|
|
754
|
-
return function mcpAdapter(pi: ExtensionAPI) {
|
|
755
|
-
installMcpAdapter(pi, {
|
|
756
|
-
...(options.configPath !== undefined ? { configPath: options.configPath } : {}),
|
|
757
|
-
...(defaultScriptTimeoutMs !== undefined ? { defaultScriptTimeoutMs } : {}),
|
|
758
|
-
...(options.outputDirectory !== undefined ? { outputDirectory: options.outputDirectory } : {}),
|
|
759
|
-
...(factoryConfig !== undefined ? { config: cloneMcpConfig(factoryConfig) } : {}),
|
|
760
|
-
...(options.beforeExecute ? { beforeExecute: options.beforeExecute } : {}),
|
|
761
|
-
...(options.transformConfig ? { transformConfig: options.transformConfig } : {}),
|
|
762
|
-
...(options.onToolCall ? { onToolCall: options.onToolCall } : {}),
|
|
763
|
-
});
|
|
764
|
-
};
|
|
765
|
-
}
|
|
766
|
-
|
|
767
|
-
export default createMcpAdapter();
|