@fitchmultz/pi-mcp-adapter 7.1.0 → 8.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 +27 -0
- package/OAUTH.md +3 -3
- package/README.md +26 -17
- package/dist/app-bridge.bundle.js +13 -13
- package/dist/direct-tools.js +6 -5
- package/dist/error-signal.d.ts +3 -5
- package/dist/error-signal.js +3 -5
- package/dist/index.js +6 -23
- package/dist/init.js +0 -1
- package/dist/lifecycle.d.ts +0 -4
- package/dist/lifecycle.js +1 -14
- package/dist/mcp-auth-flow.d.ts +3 -9
- package/dist/mcp-auth-flow.js +4 -43
- package/dist/mcp-code.js +0 -1
- package/dist/mcp-output-guard.d.ts +5 -0
- package/dist/mcp-output-guard.js +55 -1
- package/dist/proxy-modes.d.ts +1 -1
- package/dist/proxy-modes.js +89 -9
- package/dist/runtime-owner.d.ts +0 -4
- package/dist/runtime-owner.js +5 -18
- package/dist/server-manager.d.ts +2 -9
- package/dist/server-manager.js +9 -100
- package/dist/tool-loader.d.ts +6 -21
- package/dist/tool-loader.js +76 -61
- package/dist/tool-registrar.d.ts +11 -0
- package/dist/tool-registrar.js +23 -0
- package/dist/types.d.ts +5 -0
- package/dist/ui-message-history.d.ts +11 -0
- package/dist/ui-message-history.js +81 -0
- package/dist/ui-server.js +17 -10
- package/dist/ui-session.js +3 -2
- package/package.json +12 -12
- package/dist/checkpoint.d.ts +0 -18
- package/dist/checkpoint.js +0 -53
package/dist/direct-tools.js
CHANGED
|
@@ -3,6 +3,7 @@ import { parseDirectToolSelectors } from "./metadata-cache.js";
|
|
|
3
3
|
export { getMissingConfiguredDirectToolServers } from "./metadata-cache.js";
|
|
4
4
|
import { executeCall } from "./proxy-modes.js";
|
|
5
5
|
import { isServerDisabled } from "./types.js";
|
|
6
|
+
import { nativeMcpResult } from "./tool-registrar.js";
|
|
6
7
|
export function directToolSelection(config, serverName, envOverride) {
|
|
7
8
|
if (envOverride) {
|
|
8
9
|
const selection = parseDirectToolSelectors(envOverride);
|
|
@@ -33,20 +34,20 @@ export function createDirectToolExecutor(getState, getInitPromise, spec, beforeE
|
|
|
33
34
|
catch (error) {
|
|
34
35
|
throwIfAborted(signal);
|
|
35
36
|
const message = error instanceof Error ? error.message : String(error);
|
|
36
|
-
return {
|
|
37
|
+
return nativeMcpResult({
|
|
37
38
|
content: [{ type: "text", text: `MCP initialization failed: ${message}` }],
|
|
38
39
|
details: { error: "init_failed", message },
|
|
39
|
-
};
|
|
40
|
+
}, !!spec.outputSchema);
|
|
40
41
|
}
|
|
41
42
|
}
|
|
42
43
|
if (!state) {
|
|
43
|
-
return {
|
|
44
|
+
return nativeMcpResult({
|
|
44
45
|
content: [{ type: "text", text: "MCP not initialized" }],
|
|
45
46
|
details: { error: "not_initialized" },
|
|
46
|
-
};
|
|
47
|
+
}, !!spec.outputSchema);
|
|
47
48
|
}
|
|
48
49
|
const result = await executeCall(state, spec.originalName, params, spec.serverName, undefined, signal, { toolCallId }, beforeExecute ? (callSignal, operation) => beforeExecute(toolCallId, { ...ctx, signal: callSignal }, operation) : undefined, { exactOriginalName: true, ...(spec.resourceUri ? { resourceUri: spec.resourceUri } : {}) });
|
|
49
50
|
const { mode: _mode, ...details } = result.details;
|
|
50
|
-
return { ...result, details };
|
|
51
|
+
return nativeMcpResult({ ...result, details }, !!spec.outputSchema);
|
|
51
52
|
};
|
|
52
53
|
}
|
package/dist/error-signal.d.ts
CHANGED
|
@@ -1,11 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* Classify returned MCP execution failures for Pi's native result-level isError.
|
|
3
3
|
*
|
|
4
4
|
* A failed MCP tool call is *returned* (not thrown), tagged `details.error: "tool_error"` (the server
|
|
5
|
-
* returned an error result) or `"call_failed"` (the call itself threw and was caught).
|
|
6
|
-
*
|
|
7
|
-
* `{ isError: true }` (and nothing else) flips the flag; pi's field-by-field merge keeps the original
|
|
8
|
-
* `content` and `details` intact.
|
|
5
|
+
* returned an error result) or `"call_failed"` (the call itself threw and was caught).
|
|
6
|
+
* Spread this onto the returned result without replacing human or structured content.
|
|
9
7
|
*
|
|
10
8
|
* Limited to those two codes: the adapter's other `details.error` values (`auth_required`, connection
|
|
11
9
|
* states, search/validation feedback, ...) are not failed tool calls, so they get no override.
|
package/dist/error-signal.js
CHANGED
|
@@ -1,11 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* Classify returned MCP execution failures for Pi's native result-level isError.
|
|
3
3
|
*
|
|
4
4
|
* A failed MCP tool call is *returned* (not thrown), tagged `details.error: "tool_error"` (the server
|
|
5
|
-
* returned an error result) or `"call_failed"` (the call itself threw and was caught).
|
|
6
|
-
*
|
|
7
|
-
* `{ isError: true }` (and nothing else) flips the flag; pi's field-by-field merge keeps the original
|
|
8
|
-
* `content` and `details` intact.
|
|
5
|
+
* returned an error result) or `"call_failed"` (the call itself threw and was caught).
|
|
6
|
+
* Spread this onto the returned result without replacing human or structured content.
|
|
9
7
|
*
|
|
10
8
|
* Limited to those two codes: the adapter's other `details.error` values (`auth_required`, connection
|
|
11
9
|
* states, search/validation feedback, ...) are not failed tool calls, so they get no override.
|
package/dist/index.js
CHANGED
|
@@ -10,17 +10,15 @@ import { executeAuthComplete, executeAuthStart, executeCall, executeConnect, exe
|
|
|
10
10
|
import { formatTerminalError } from "./utils.js";
|
|
11
11
|
import { createOAuthRuntime, shutdownOAuth } from "./mcp-auth-flow.js";
|
|
12
12
|
import { renderMcpProxyToolCall, renderMcpToolResult } from "./tool-result-renderer.js";
|
|
13
|
-
import { toolErrorOverride } from "./error-signal.js";
|
|
14
13
|
import { createMcpRuntimeOwner, createOwnedUi, isAbortError } from "./runtime-owner.js";
|
|
15
14
|
import { publishMcpStatusShutdown } from "./mcp-status.js";
|
|
16
15
|
import { DEFAULT_MCP_SCRIPT_TIMEOUT_MS, runMcpScript } from "./mcp-code.js";
|
|
17
16
|
import { MAX_PAGE_SIZE, paginate, rankToolMatches } from "./search-ranking.js";
|
|
18
|
-
import { createToolLoader
|
|
17
|
+
import { createToolLoader } from "./tool-loader.js";
|
|
19
18
|
import { gatewayParameters, prepareGatewayArguments } from "./gateway-arguments.js";
|
|
20
19
|
import { executeResourceList, executeResourceRead } from "./resource-tools.js";
|
|
21
20
|
import { guardMcpOutput, guardedMcpDetails, readMcpResult, resolveMcpOutputGuardOptions } from "./mcp-output-guard.js";
|
|
22
21
|
import { abortable, throwIfAborted } from "./abort.js";
|
|
23
|
-
import { onSessionCheckpoint, prepareMcpCheckpoint } from "./checkpoint.js";
|
|
24
22
|
export { MCP_STATUS_EVENT, MCP_STATUS_SNAPSHOT_VERSION, } from "./types.js";
|
|
25
23
|
const INIT_WAIT_TIMEOUT_MS = 30_000;
|
|
26
24
|
const INIT_WAIT_TIMED_OUT = Symbol("init-wait-timed-out");
|
|
@@ -95,7 +93,6 @@ function installMcpAdapter(pi, options) {
|
|
|
95
93
|
const envDirectToolOverride = envRaw?.split(",").map(s => s.trim()).filter(Boolean);
|
|
96
94
|
const toolLoader = createToolLoader(pi, () => state, () => initPromise, beforeExecute);
|
|
97
95
|
let runtimeCwd;
|
|
98
|
-
let nativeAsyncSupported = false;
|
|
99
96
|
let proxyToolRegistered = false;
|
|
100
97
|
let proxyToolDescription = null;
|
|
101
98
|
let directToolsFrozen = false;
|
|
@@ -112,7 +109,7 @@ function installMcpAdapter(pi, options) {
|
|
|
112
109
|
function syncToolSurface(ctx) {
|
|
113
110
|
const config = state?.config ?? currentConfig;
|
|
114
111
|
const cache = loadMetadataCache(state?.metadataCacheEnabled ?? false);
|
|
115
|
-
toolLoader.sync(config, cache, envRaw === "__none__" ? null : envDirectToolOverride,
|
|
112
|
+
toolLoader.sync(config, cache, envRaw === "__none__" ? null : envDirectToolOverride, specs => syncGateways(config, cache, specs));
|
|
116
113
|
}
|
|
117
114
|
const registeredPromptCommands = new Set();
|
|
118
115
|
function registerPromptCommands(specs) {
|
|
@@ -202,7 +199,6 @@ function installMcpAdapter(pi, options) {
|
|
|
202
199
|
}
|
|
203
200
|
async function startSession(ctx) {
|
|
204
201
|
runtimeCwd = ctx.cwd;
|
|
205
|
-
nativeAsyncSupported = supportsNativeAsync(ctx);
|
|
206
202
|
directToolsFrozen = false;
|
|
207
203
|
const generation = ++lifecycleGeneration;
|
|
208
204
|
const previousState = state;
|
|
@@ -249,7 +245,7 @@ function installMcpAdapter(pi, options) {
|
|
|
249
245
|
toolLoader.restore(ctx);
|
|
250
246
|
registerPromptCommands(resolveCachedPrompts(runtimeConfig, metadataCacheEnabled));
|
|
251
247
|
const runtimeCache = loadMetadataCache(metadataCacheEnabled);
|
|
252
|
-
toolLoader.sync(runtimeConfig, runtimeCache, envRaw === "__none__" ? null : envDirectToolOverride,
|
|
248
|
+
toolLoader.sync(runtimeConfig, runtimeCache, envRaw === "__none__" ? null : envDirectToolOverride, specs => syncGateways(runtimeConfig, runtimeCache, specs));
|
|
253
249
|
const initialization = startInitialization(ctx, owner, oauthRuntime, runtimeConfig, generation);
|
|
254
250
|
if (envRaw !== undefined && envRaw !== "__none__") {
|
|
255
251
|
const missingEnvDirectTools = getMissingConfiguredDirectToolServers(runtimeConfig, runtimeCache, envDirectToolOverride);
|
|
@@ -264,7 +260,6 @@ function installMcpAdapter(pi, options) {
|
|
|
264
260
|
syncToolSurface(ctx);
|
|
265
261
|
});
|
|
266
262
|
pi.on("model_select", (_event, ctx) => {
|
|
267
|
-
nativeAsyncSupported = supportsNativeAsync(ctx);
|
|
268
263
|
if (state)
|
|
269
264
|
syncToolSurface(ctx);
|
|
270
265
|
});
|
|
@@ -272,13 +267,6 @@ function installMcpAdapter(pi, options) {
|
|
|
272
267
|
if (ctx.cwd !== runtimeCwd)
|
|
273
268
|
await startSession(ctx);
|
|
274
269
|
});
|
|
275
|
-
onSessionCheckpoint(pi, async (event) => {
|
|
276
|
-
if (initPromise)
|
|
277
|
-
return { sleepReady: false, reason: "MCP initialization is active" };
|
|
278
|
-
if (!state || !currentOwner?.isActive())
|
|
279
|
-
return { sleepReady: false, reason: "MCP runtime is not initialized" };
|
|
280
|
-
return prepareMcpCheckpoint(state, event);
|
|
281
|
-
});
|
|
282
270
|
pi.on("session_shutdown", async () => {
|
|
283
271
|
// Cached tool selections exist before background server initialization finishes.
|
|
284
272
|
toolLoader.persist();
|
|
@@ -303,8 +291,6 @@ function installMcpAdapter(pi, options) {
|
|
|
303
291
|
if (failures.length)
|
|
304
292
|
throw new AggregateError(failures, "MCP session shutdown persistence/cleanup failed");
|
|
305
293
|
});
|
|
306
|
-
// Re-flag returned MCP tool failures so pi registers them as errors (see toolErrorOverride).
|
|
307
|
-
pi.on("tool_result", (event) => toolErrorOverride(event.details));
|
|
308
294
|
function createCommandContext(ctx) {
|
|
309
295
|
const owner = currentOwner;
|
|
310
296
|
const hasUI = ctx.hasUI;
|
|
@@ -519,7 +505,7 @@ function installMcpAdapter(pi, options) {
|
|
|
519
505
|
owner?.throwIfInactive();
|
|
520
506
|
return state ?? { content: [{ type: "text", text: "MCP not initialized" }], details: { error: "not_initialized" } };
|
|
521
507
|
}
|
|
522
|
-
const unavailableFeatures = (enable = [], tools) => enable.filter(feature => !tools.some(tool => tool.name === (feature === "gateway" ? "mcp" : "mcp_script")
|
|
508
|
+
const unavailableFeatures = (enable = [], tools) => enable.filter(feature => !tools.some(tool => tool.name === (feature === "gateway" ? "mcp" : "mcp_script")));
|
|
523
509
|
async function searchAndLoad(params, signal, ctx) {
|
|
524
510
|
throwIfAborted(signal);
|
|
525
511
|
if (params.query === undefined && !params.enable?.length)
|
|
@@ -544,7 +530,7 @@ function installMcpAdapter(pi, options) {
|
|
|
544
530
|
const page = paginate(matches, params.offset ?? 0, params.limit ?? 5);
|
|
545
531
|
const tools = toolLoader.activate(page.items, params.enable);
|
|
546
532
|
const unavailable = unavailableFeatures(params.enable, tools);
|
|
547
|
-
const typedCount = tools.filter(tool => tool.
|
|
533
|
+
const typedCount = tools.filter(tool => tool.name !== "mcp" && tool.name !== "mcp_script").length;
|
|
548
534
|
const result = executeSearch(ready, params.query, params.server, typedCount < page.items.length, params.limit ?? 5, params.offset, "mcp_search");
|
|
549
535
|
const guarded = await guardMcpOutput([
|
|
550
536
|
...result.content,
|
|
@@ -574,10 +560,7 @@ function installMcpAdapter(pi, options) {
|
|
|
574
560
|
return searchAndLoad(params, signal, ctx);
|
|
575
561
|
},
|
|
576
562
|
};
|
|
577
|
-
|
|
578
|
-
pi.registerToolSearch(searchDefinition);
|
|
579
|
-
else
|
|
580
|
-
pi.registerTool(searchDefinition);
|
|
563
|
+
pi.registerTool(searchDefinition);
|
|
581
564
|
let scriptToolRegistered = false;
|
|
582
565
|
function registerScriptTool() {
|
|
583
566
|
if (scriptToolRegistered)
|
package/dist/init.js
CHANGED
|
@@ -43,7 +43,6 @@ export function recordFailure(state, serverName, message) {
|
|
|
43
43
|
getFailureExpiryTimers(state).delete(serverName);
|
|
44
44
|
return;
|
|
45
45
|
}
|
|
46
|
-
state.owner.beforeActivity();
|
|
47
46
|
if (state.failureTracker.get(serverName) === failedAt) {
|
|
48
47
|
state.failureTracker.delete(serverName);
|
|
49
48
|
state.failureMessages?.delete(serverName);
|
package/dist/lifecycle.d.ts
CHANGED
|
@@ -15,10 +15,6 @@ export declare class McpLifecycleManager {
|
|
|
15
15
|
private activeHealthCheck;
|
|
16
16
|
private shutdownPromise;
|
|
17
17
|
private stopped;
|
|
18
|
-
private checkpointPauses;
|
|
19
|
-
hasActiveHealthCheck(): boolean;
|
|
20
|
-
/** Skip ticks, without cancelling accepted work or permanently closing the manager. */
|
|
21
|
-
pauseForCheckpoint(): () => void;
|
|
22
18
|
private healthSignal;
|
|
23
19
|
private removeHealthAbortListener;
|
|
24
20
|
private readonly manager;
|
package/dist/lifecycle.js
CHANGED
|
@@ -14,19 +14,6 @@ export class McpLifecycleManager {
|
|
|
14
14
|
activeHealthCheck;
|
|
15
15
|
shutdownPromise;
|
|
16
16
|
stopped = false;
|
|
17
|
-
checkpointPauses = 0;
|
|
18
|
-
hasActiveHealthCheck() { return this.activeHealthCheck !== undefined; }
|
|
19
|
-
/** Skip ticks, without cancelling accepted work or permanently closing the manager. */
|
|
20
|
-
pauseForCheckpoint() {
|
|
21
|
-
this.checkpointPauses++;
|
|
22
|
-
let released = false;
|
|
23
|
-
return () => {
|
|
24
|
-
if (!released) {
|
|
25
|
-
released = true;
|
|
26
|
-
this.checkpointPauses--;
|
|
27
|
-
}
|
|
28
|
-
};
|
|
29
|
-
}
|
|
30
17
|
healthSignal;
|
|
31
18
|
removeHealthAbortListener;
|
|
32
19
|
manager;
|
|
@@ -81,7 +68,7 @@ export class McpLifecycleManager {
|
|
|
81
68
|
signal?.addEventListener("abort", stop, { once: true });
|
|
82
69
|
this.removeHealthAbortListener = () => signal?.removeEventListener("abort", stop);
|
|
83
70
|
this.healthCheckInterval = setInterval(() => {
|
|
84
|
-
if (this.stopped || signal?.aborted || this.activeHealthCheck
|
|
71
|
+
if (this.stopped || signal?.aborted || this.activeHealthCheck)
|
|
85
72
|
return;
|
|
86
73
|
const check = this.checkConnections(signal)
|
|
87
74
|
.catch(error => {
|
package/dist/mcp-auth-flow.d.ts
CHANGED
|
@@ -9,7 +9,6 @@ import { type AuthStorageOptions } from "./mcp-auth.ts";
|
|
|
9
9
|
import { type ServerEntry } from "./types.ts";
|
|
10
10
|
/** Auth status for a server */
|
|
11
11
|
export type AuthStatus = "authenticated" | "expired" | "not_authenticated";
|
|
12
|
-
import type { McpCheckpointEvent } from "./checkpoint.ts";
|
|
13
12
|
export interface McpOAuthRuntime {
|
|
14
13
|
readonly signal: AbortSignal;
|
|
15
14
|
}
|
|
@@ -20,8 +19,6 @@ export interface AuthenticateOptions {
|
|
|
20
19
|
runtime?: McpOAuthRuntime;
|
|
21
20
|
}
|
|
22
21
|
export declare function createOAuthRuntime(signal?: AbortSignal): McpOAuthRuntime;
|
|
23
|
-
export declare function holdOAuthCheckpoint(runtime: McpOAuthRuntime, event: McpCheckpointEvent): () => void;
|
|
24
|
-
export declare function getOAuthCheckpointBlocker(runtime: McpOAuthRuntime): string | undefined;
|
|
25
22
|
type OAuthRequest = {
|
|
26
23
|
serverName: string;
|
|
27
24
|
serverUrl: string;
|
|
@@ -46,8 +43,7 @@ export declare function extractOAuthConfig(definition: ServerEntry): McpOAuthCon
|
|
|
46
43
|
* Start OAuth authentication flow for a server.
|
|
47
44
|
* Returns the authorization URL when browser authorization is required.
|
|
48
45
|
*/
|
|
49
|
-
export declare
|
|
50
|
-
declare function startAuthOperation(serverName: string, serverUrl: string, definition?: ServerEntry, options?: AuthenticateOptions): Promise<{
|
|
46
|
+
export declare function startAuth(serverName: string, serverUrl: string, definition?: ServerEntry, options?: AuthenticateOptions): Promise<{
|
|
51
47
|
authorizationUrl: string;
|
|
52
48
|
}>;
|
|
53
49
|
/** Authorization code plus the optional RFC 9207 `iss` callback parameter. */
|
|
@@ -74,8 +70,7 @@ export declare function completeAuthFromInput(serverName: string, input?: string
|
|
|
74
70
|
/**
|
|
75
71
|
* Complete OAuth authentication with an explicit code or a captured callback.
|
|
76
72
|
*/
|
|
77
|
-
export declare
|
|
78
|
-
declare function completeAuthOperation(serverName: string, authorizationCode?: string | AuthorizationResponseInput, options?: AuthenticateOptions): Promise<AuthStatus>;
|
|
73
|
+
export declare function completeAuth(serverName: string, authorizationCode?: string | AuthorizationResponseInput, options?: AuthenticateOptions): Promise<AuthStatus>;
|
|
79
74
|
/**
|
|
80
75
|
* Perform the complete OAuth authentication flow for a server.
|
|
81
76
|
*
|
|
@@ -90,8 +85,7 @@ export declare function authenticate(serverName: string, serverUrl: string, defi
|
|
|
90
85
|
*
|
|
91
86
|
* @param serverName - The name of the MCP server
|
|
92
87
|
*/
|
|
93
|
-
export declare
|
|
94
|
-
declare function removeAuthOperation(serverName: string, options?: AuthenticateOptions): Promise<void>;
|
|
88
|
+
export declare function removeAuth(serverName: string, options?: AuthenticateOptions): Promise<void>;
|
|
95
89
|
/**
|
|
96
90
|
* Check if OAuth is supported for a server configuration.
|
|
97
91
|
* OAuth is supported for HTTP servers unless explicitly disabled.
|
package/dist/mcp-auth-flow.js
CHANGED
|
@@ -42,7 +42,6 @@ export function createOAuthRuntime(signal) {
|
|
|
42
42
|
runtimeStates.set(runtime, {
|
|
43
43
|
controller,
|
|
44
44
|
generation: 0,
|
|
45
|
-
activeOperations: 0,
|
|
46
45
|
pendingAuths: new Map(),
|
|
47
46
|
pendingAuthStates: new Map(),
|
|
48
47
|
pendingAuthCleanupTimers: new Map(),
|
|
@@ -71,36 +70,6 @@ function getRuntimeState(runtime) {
|
|
|
71
70
|
throw new Error("Unknown OAuth runtime");
|
|
72
71
|
return state;
|
|
73
72
|
}
|
|
74
|
-
export function holdOAuthCheckpoint(runtime, event) {
|
|
75
|
-
const state = getRuntimeState(runtime);
|
|
76
|
-
if (state.checkpoint)
|
|
77
|
-
throw new Error("MCP OAuth checkpoint is already held");
|
|
78
|
-
state.checkpoint = event;
|
|
79
|
-
return () => { if (state.checkpoint === event)
|
|
80
|
-
state.checkpoint = undefined; };
|
|
81
|
-
}
|
|
82
|
-
export function getOAuthCheckpointBlocker(runtime) {
|
|
83
|
-
const state = getRuntimeState(runtime);
|
|
84
|
-
if (state.activeOperations || state.pendingAuthentications.size)
|
|
85
|
-
return "MCP OAuth operation is active";
|
|
86
|
-
if (state.pendingAuths.size || state.pendingAuthStates.size || state.pendingAuthCleanupTimers.size)
|
|
87
|
-
return "MCP browser OAuth callback/flow is pending";
|
|
88
|
-
// requests caches server challenges/discovery, not a pending user callback.
|
|
89
|
-
// Cold connect re-challenges; granted scopes and issuer bindings live in the
|
|
90
|
-
// native credential store. Actual interactive/auth operations veto above.
|
|
91
|
-
return undefined;
|
|
92
|
-
}
|
|
93
|
-
async function withOAuthOperation(runtime, run) {
|
|
94
|
-
const state = getRuntimeState(runtime);
|
|
95
|
-
state.checkpoint?.invalidate();
|
|
96
|
-
state.activeOperations++;
|
|
97
|
-
try {
|
|
98
|
-
return await run();
|
|
99
|
-
}
|
|
100
|
-
finally {
|
|
101
|
-
state.activeOperations--;
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
73
|
function getPendingAuthKey(serverName, options) {
|
|
105
74
|
return JSON.stringify([serverName, getAuthBaseDir(options)]);
|
|
106
75
|
}
|
|
@@ -118,7 +87,6 @@ export function getOAuthRequest(serverName, serverUrl, storageBase, runtime = le
|
|
|
118
87
|
export function recordOAuthChallenge(serverName, serverUrl, storageBase, error, runtime = legacyRuntime) {
|
|
119
88
|
if (runtime.signal.aborted || !isOAuthChallenge(error))
|
|
120
89
|
return;
|
|
121
|
-
getRuntimeState(runtime).checkpoint?.invalidate();
|
|
122
90
|
const previous = getOAuthRequest(serverName, serverUrl, storageBase, runtime);
|
|
123
91
|
const request = { ...previous, serverName, serverUrl };
|
|
124
92
|
if (error instanceof InsufficientScopeError) {
|
|
@@ -315,8 +283,7 @@ function parseOAuthRedirectUri(redirectUri) {
|
|
|
315
283
|
* Start OAuth authentication flow for a server.
|
|
316
284
|
* Returns the authorization URL when browser authorization is required.
|
|
317
285
|
*/
|
|
318
|
-
export
|
|
319
|
-
async function startAuthOperation(serverName, serverUrl, definition, options = {}) {
|
|
286
|
+
export async function startAuth(serverName, serverUrl, definition, options = {}) {
|
|
320
287
|
if (isServerDisabled(definition))
|
|
321
288
|
throw new Error(`MCP server "${serverName}" is disabled`);
|
|
322
289
|
const runtime = getRuntime(options);
|
|
@@ -479,10 +446,7 @@ async function setPendingAuth(runtime, serverName, pendingAuth, oauthState, sign
|
|
|
479
446
|
cleanupTimer.unref?.();
|
|
480
447
|
state.pendingAuthCleanupTimers.set(key, cleanupTimer);
|
|
481
448
|
}
|
|
482
|
-
function clearPendingAuth(
|
|
483
|
-
return withOAuthOperation(args[0], () => clearPendingAuthOperation(...args));
|
|
484
|
-
}
|
|
485
|
-
async function clearPendingAuthOperation(runtime, serverName, oauthState, fallbackStorageOptions = {}) {
|
|
449
|
+
async function clearPendingAuth(runtime, serverName, oauthState, fallbackStorageOptions = {}) {
|
|
486
450
|
const state = getRuntimeState(runtime);
|
|
487
451
|
const key = getPendingAuthKey(serverName, fallbackStorageOptions);
|
|
488
452
|
const pendingAuth = state.pendingAuths.get(key);
|
|
@@ -576,8 +540,7 @@ export async function completeAuthFromInput(serverName, input, options = {}) {
|
|
|
576
540
|
/**
|
|
577
541
|
* Complete OAuth authentication with an explicit code or a captured callback.
|
|
578
542
|
*/
|
|
579
|
-
export
|
|
580
|
-
async function completeAuthOperation(serverName, authorizationCode, options = {}) {
|
|
543
|
+
export async function completeAuth(serverName, authorizationCode, options = {}) {
|
|
581
544
|
const runtime = getRuntime(options);
|
|
582
545
|
const runtimeState = getRuntimeState(runtime);
|
|
583
546
|
const fallbackAuthStorageOptions = options.authStorageOptions ?? {};
|
|
@@ -743,8 +706,7 @@ export async function authenticate(serverName, serverUrl, definition, options =
|
|
|
743
706
|
*
|
|
744
707
|
* @param serverName - The name of the MCP server
|
|
745
708
|
*/
|
|
746
|
-
export
|
|
747
|
-
async function removeAuthOperation(serverName, options = {}) {
|
|
709
|
+
export async function removeAuth(serverName, options = {}) {
|
|
748
710
|
const runtime = getRuntime(options);
|
|
749
711
|
const signal = combineAbortSignals(runtime.signal, options.signal);
|
|
750
712
|
throwIfAborted(signal);
|
|
@@ -811,7 +773,6 @@ export async function shutdownOAuth(runtime = legacyRuntime) {
|
|
|
811
773
|
const state = getRuntimeState(runtime);
|
|
812
774
|
if (state.controller.signal.aborted)
|
|
813
775
|
return;
|
|
814
|
-
state.checkpoint?.invalidate();
|
|
815
776
|
state.generation += 1;
|
|
816
777
|
state.controller.abort(new Error("OAuth runtime stopped"));
|
|
817
778
|
for (const callbackState of Array.from(state.pendingAuthStates.values()))
|
package/dist/mcp-code.js
CHANGED
|
@@ -98,7 +98,6 @@ class McpScriptCaptureError extends Error {
|
|
|
98
98
|
}
|
|
99
99
|
export const runMcpScript = async (...args) => {
|
|
100
100
|
const state = args[0];
|
|
101
|
-
state.owner?.beforeActivity();
|
|
102
101
|
state.activeScripts = (state.activeScripts ?? 0) + 1;
|
|
103
102
|
try {
|
|
104
103
|
return await runMcpScriptOperation(...args);
|
|
@@ -78,6 +78,11 @@ export declare function guardMcpOutput(content: ContentBlock[], options?: McpOut
|
|
|
78
78
|
export declare function retainMcpResult(result: unknown, options?: McpOutputGuardOptions, rawResult?: boolean): Promise<RetainedMcpResult>;
|
|
79
79
|
export declare function formatMcpPayloadFile(file: McpPayloadFile): string;
|
|
80
80
|
export declare function formatMcpResultReference(ref: string): string;
|
|
81
|
+
/** Stream histories through the same private output-artifact and paged-reader boundary. */
|
|
82
|
+
export declare function saveMcpOutputStream(chunks: AsyncIterable<string>, outputDirectory?: string): Promise<{
|
|
83
|
+
path?: string;
|
|
84
|
+
error?: string;
|
|
85
|
+
}>;
|
|
81
86
|
export interface ReadMcpResultInput {
|
|
82
87
|
ref: string;
|
|
83
88
|
/** RFC 6901 JSON Pointer. Omit to read the entire retained result. */
|
package/dist/mcp-output-guard.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { randomBytes } from "node:crypto";
|
|
2
|
-
import { mkdir, mkdtemp, readFile, realpath, writeFile } from "node:fs/promises";
|
|
2
|
+
import { mkdir, mkdtemp, open, readFile, realpath, writeFile } from "node:fs/promises";
|
|
3
|
+
import { createReadStream } from "node:fs";
|
|
3
4
|
import { tmpdir } from "node:os";
|
|
4
5
|
import { basename, dirname, join, resolve } from "node:path";
|
|
5
6
|
export const DEFAULT_MCP_OUTPUT_MAX_BYTES = 50 * 1024;
|
|
@@ -320,6 +321,26 @@ async function saveArtifact(kind, text, outputDirectory) {
|
|
|
320
321
|
return { error: error instanceof Error ? error.message : String(error) };
|
|
321
322
|
}
|
|
322
323
|
}
|
|
324
|
+
/** Stream histories through the same private output-artifact and paged-reader boundary. */
|
|
325
|
+
export async function saveMcpOutputStream(chunks, outputDirectory) {
|
|
326
|
+
const artifact = await saveArtifact("output", "", outputDirectory);
|
|
327
|
+
if (!artifact.path)
|
|
328
|
+
return artifact;
|
|
329
|
+
try {
|
|
330
|
+
const file = await open(artifact.path, "a");
|
|
331
|
+
try {
|
|
332
|
+
for await (const chunk of chunks)
|
|
333
|
+
await file.writeFile(chunk);
|
|
334
|
+
}
|
|
335
|
+
finally {
|
|
336
|
+
await file.close();
|
|
337
|
+
}
|
|
338
|
+
return artifact;
|
|
339
|
+
}
|
|
340
|
+
catch (error) {
|
|
341
|
+
return { error: error instanceof Error ? error.message : String(error) };
|
|
342
|
+
}
|
|
343
|
+
}
|
|
323
344
|
export async function readMcpResult(input, options = {}) {
|
|
324
345
|
try {
|
|
325
346
|
if (!input || typeof input.ref !== "string")
|
|
@@ -330,6 +351,39 @@ export async function readMcpResult(input, options = {}) {
|
|
|
330
351
|
if (dirname(dirname(ref)) !== root || !/^pi-mcp-output-/.test(basename(dirname(ref)))
|
|
331
352
|
|| !/^(mcp-result|output)-[a-f0-9]+\.txt$/.test(basename(ref)))
|
|
332
353
|
throw new Error("ref is not an MCP output artifact in this host's output directory");
|
|
354
|
+
// Plain output pages without flattening a complete UI history into memory.
|
|
355
|
+
if (basename(ref).startsWith("output-") && input.path === undefined && input.fields === undefined) {
|
|
356
|
+
const offset = input.offset ?? 0;
|
|
357
|
+
const limit = input.limit ?? 12_000;
|
|
358
|
+
if (!Number.isSafeInteger(offset) || offset < 0 || !Number.isSafeInteger(limit) || limit < 1)
|
|
359
|
+
throw new Error("offset must be a non-negative integer and limit a positive integer");
|
|
360
|
+
const maxBytes = Math.max(1, (options.maxBytes ?? DEFAULT_MCP_OUTPUT_MAX_BYTES) - 256);
|
|
361
|
+
const maxLines = Math.max(1, (options.maxLines ?? DEFAULT_MCP_OUTPUT_MAX_LINES) - 1);
|
|
362
|
+
let total = 0, bytes = 0, lines = 1;
|
|
363
|
+
let page = "";
|
|
364
|
+
let finished = false;
|
|
365
|
+
for await (const chunk of createReadStream(ref, { encoding: "utf8" })) {
|
|
366
|
+
for (const char of chunk) {
|
|
367
|
+
if (total < offset && total + char.length > offset)
|
|
368
|
+
throw new Error("offset must not split a Unicode surrogate pair");
|
|
369
|
+
if (total >= offset && !finished) {
|
|
370
|
+
const nextBytes = bytes + byteLength(char);
|
|
371
|
+
const nextLines = lines + (char === "\n" ? 1 : 0);
|
|
372
|
+
if (page && (nextBytes > maxBytes || nextLines > maxLines || page.length + char.length > limit))
|
|
373
|
+
finished = true;
|
|
374
|
+
else {
|
|
375
|
+
page += char;
|
|
376
|
+
bytes = nextBytes;
|
|
377
|
+
lines = nextLines;
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
total += char.length;
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
const end = offset + page.length;
|
|
384
|
+
const nextOffset = end < total ? end : null;
|
|
385
|
+
return { content: [{ type: "text", text: page }, ...(nextOffset !== null ? [{ type: "text", text: `[MCP result page: ${offset}–${end} of ${total} characters. Continue with offset: ${end}.]` }] : [])], details: { ref, offset, nextOffset, totalCharacters: total } };
|
|
386
|
+
}
|
|
333
387
|
let rendered = await readFile(ref, "utf8");
|
|
334
388
|
if (input.path !== undefined || input.fields !== undefined || basename(ref).startsWith("mcp-result-")) {
|
|
335
389
|
let value = JSON.parse(rendered);
|
package/dist/proxy-modes.d.ts
CHANGED
|
@@ -4,7 +4,7 @@ import type { ServerConnection } from "./server-manager.ts";
|
|
|
4
4
|
import type { ToolMetadata, McpOperationContext, McpToolCallIdentity } from "./types.ts";
|
|
5
5
|
import { type SessionRecoveryDeps } from "./session-recovery.ts";
|
|
6
6
|
type ProxyToolResult = AgentToolResult<Record<string, unknown>>;
|
|
7
|
-
export declare function executeUiMessages(state: McpExtensionState): ProxyToolResult
|
|
7
|
+
export declare function executeUiMessages(state: McpExtensionState): Promise<ProxyToolResult>;
|
|
8
8
|
export declare function executeStatus(state: McpExtensionState): ProxyToolResult;
|
|
9
9
|
export declare function executeAuthStart(state: McpExtensionState, serverName: string, signal?: AbortSignal): Promise<ProxyToolResult>;
|
|
10
10
|
export declare function executeAuthComplete(state: McpExtensionState, serverName: string, input?: string, signal?: AbortSignal): Promise<ProxyToolResult>;
|