@fitchmultz/pi-mcp-adapter 7.1.1 → 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 +12 -0
- package/OAUTH.md +1 -1
- package/README.md +11 -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 +0 -11
- 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 +7 -99
- package/dist/tool-loader.js +35 -6
- 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 +4 -4
- package/dist/checkpoint.d.ts +0 -18
- package/dist/checkpoint.js +0 -53
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,18 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [8.0.0] - 2026-10-01
|
|
11
|
+
|
|
12
|
+
### Changed
|
|
13
|
+
|
|
14
|
+
- Require Pi 1.0.0 and qualify the exact official SDK/CLI cohort; live installations are not changed.
|
|
15
|
+
- Typed MCP tools expose native schemas, complete namespace instructions, annotations, bounded structured output and result-level failures. Human content, private raw capture, output/readback artifacts, cancellation and no-replay ownership remain separate.
|
|
16
|
+
- Restore the selection journal backward to the first valid snapshot, preserving legacy/incomplete recovery, pins, restrictions, lazy startup and enable-first permissions. Keep this journal for the official initial SDK-resume gap.
|
|
17
|
+
- Save MCP App messages privately on disk. Small retrievals stay inline; large histories stream to existing paged output references. Failed persistence/delivery remains recoverable, with no new retention caps.
|
|
18
|
+
- Reject App messages/context updates whose authenticated upload finishes after completion, and refuse incomplete history delivery before draining accepted sessions.
|
|
19
|
+
- Report and document the owner-accepted failed-storage boundary: unsaved in-memory App history is lost on reload/process exit; no second recovery store is added. Normal private disk history and failed-delivery recovery remain protected.
|
|
20
|
+
- Remove retired fork checkpoint/metadata APIs. Preserve actual transport/OAuth/sampling/elicitation/capture shutdown ownership and move their independent lifetime tests out of the retired checkpoint suite.
|
|
21
|
+
|
|
10
22
|
## [7.1.1] - 2026-10-01
|
|
11
23
|
|
|
12
24
|
### Changed
|
package/OAUTH.md
CHANGED
|
@@ -327,7 +327,7 @@ Persistent OAuth entries are stored in service `fitch-mcp-adapter.oauth`, with t
|
|
|
327
327
|
|
|
328
328
|
The adapter fails closed when the OS credential store is unavailable. On headless Linux, configure an unlocked Secret Service-compatible keyring before using persistent OAuth; the adapter does not silently fall back to plaintext token files.
|
|
329
329
|
|
|
330
|
-
|
|
330
|
+
Final runtime shutdown joins owned SDK operations, including background GET/reconnect provider work and cancellation tails. Per-server cancellation stays prompt; closing the runtime waits for actual provider promises, not just the cancelled public request. Granted scopes and issuer bindings remain in the native OS credential store. Active browser flows and callback memory are not serialized, and no operation is replayed to recover them. Hosts must preserve and unlock the actual private credential store on replacement compute; a Linux kernel keyring alone does not prove persistent Secret Service storage.
|
|
331
331
|
|
|
332
332
|
On Linux, if credential access fails because Pi inherited a revoked session keyring, the adapter makes one best-effort retry through `keyctl session - node <packaged helper>`. This lets explicit re-authentication write fresh credentials from a new session keyring without restarting a long-lived tmux or server process. The recovery path requires `keyctl` and `node` on `PATH`; missing, locked, or otherwise unavailable credential stores still fail closed.
|
|
333
333
|
|
package/README.md
CHANGED
|
@@ -20,7 +20,7 @@ The MCP ecosystem has useful databases, browsers, and APIs. This adapter keeps t
|
|
|
20
20
|
|
|
21
21
|
## Pi release qualification
|
|
22
22
|
|
|
23
|
-
The
|
|
23
|
+
The supported Pi baseline is official **1.0.0**; host peers remain wildcard. Older Pi releases are not supported. `npm run check:compat` verifies the installed SDK and CLI identity, builds and typechecks, runs Vitest and memory-only OAuth tests, then loads a packed consumer through the native SDK and CLI.
|
|
24
24
|
|
|
25
25
|
GitHub CI runs that contract on Node 24 against both official Pi and the maintained fork through the shared Pi compatibility automation, including fresh Git and npm installations loaded by the real Pi CLI. A second job checks lockfile registry hosts, published type declarations, the built interactive visualizer, and MCP protocol conformance. These checks use local MCP fixtures and disposable agent directories, never live credentials or paid providers.
|
|
26
26
|
|
|
@@ -28,7 +28,7 @@ Keep the development TypeBox pin aligned with the official Pi baseline's depende
|
|
|
28
28
|
|
|
29
29
|
## Install
|
|
30
30
|
|
|
31
|
-
Requires Pi 0.
|
|
31
|
+
Requires Pi 1.0.0 or later and Node.js 24 or later.
|
|
32
32
|
|
|
33
33
|
```bash
|
|
34
34
|
pi install npm:@fitchmultz/pi-mcp-adapter
|
|
@@ -235,17 +235,11 @@ Supplying `beforeExecute` marks the adapter's tools `executionMode: "sequential"
|
|
|
235
235
|
|
|
236
236
|
The two callbacks serve different boundaries: `beforeExecute` saves completed workspace writes; `onToolCall` captures the resolved operation and its raw outcome. The host owns `pi.appendEntry()` and the awaited checkpoint of that same native JSONL. Exclude `signal`, serialize thrown errors explicitly, redact sensitive checkpoint bytes, and honor the signal and session/attempt fence. `pi.appendEntry()` alone only persists locally; `pi.events.emit()` is not an awaited barrier. Custom entries do not enter model context automatically: the host must provide truthful recovered results or readback instructions when resuming. The adapter stores no separate transcript or receipts, does not replay scripts, and does not invent provider operation keys.
|
|
237
237
|
|
|
238
|
-
###
|
|
238
|
+
### Runtime shutdown and replacement
|
|
239
239
|
|
|
240
|
-
|
|
240
|
+
Pi 1.0 owns session replacement and emits `session_shutdown`. The adapter aborts outgoing activity before awaiting cleanup, closes owned connections, health watchers, OAuth callbacks and UI servers, and reports persistence/cleanup failures. Actual sampling/elicitation and native send/auth tails remain owned through settlement, even when a public request has already been cancelled. Unfinished callbacks prevent idle connection closure; final shutdown joins transport work. Pending `beforeExecute`/`onToolCall` promises prevent a clean-shutdown success claim. No detached work is declared finished merely because the agent settled.
|
|
241
241
|
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
Ordinary logging, argument completion, empty experimental metadata, and the SDK's auto-opened catalog notification subscription are reconstructible: requests are owned and notification ingress is fenced. Explicit consumer resource/listen subscriptions, negotiated remote tasks, opaque protocol capabilities, HTTP session IDs, legacy SSE, stdio/Unix servers, pending initialization/requests/refresh/health checks, browser OAuth flows, UI sessions/messages, active scripts, and unpersisted session approvals return a named `sleepReady: false`. Session grants are not silently reset to obtain readiness. Advertising subscription support alone is not an active subscription. A resource-subscribe attempt with a cancelled/lost response remains unresolved until successful unsubscribe or connection disposal; it is not silently replayed. Completed URL elicitation clears only its accepted ID; other pending IDs still veto. Failed cleanup remains owned; an explicit later close may retry only if the native client still owns its transport. Unsupported work remains usable while compute stays running; the hook does not cancel accepted operations to manufacture readiness.
|
|
245
|
-
|
|
246
|
-
Before any asynchronous flush, the adapter fences owned activity and pauses its existing health-check timer. New requests, background HTTP GET/reconnect/auth work, or incoming callbacks invalidate the host's hold **before** dispatch or mutation. Native provider promises and HTTP headers/body reads remain owned through settlement, including cancellation tails; final shutdown joins them while per-server cancellation stays prompt. An idle inbound SSE stream is not a perpetual active request. Completed refreshes use the existing native OS credential store; no token file or second auth store is introduced. Server OAuth challenges reconstruct through normal re-challenge, with granted scopes and issuer bindings in native credentials. Required metadata persistence errors reject capture; optional diagnostic tracing stays best-effort and never prevents recovery or clean exit. Release/cancellation resumes the same clients and timer, without replaying tools, reauthenticating, or running shutdown. Cold startup uses normal discovery and native credentials; only a new explicit tool invocation sends a new tool request.
|
|
247
|
-
|
|
248
|
-
`session_shutdown` separately performs best-effort cleanup and propagates persistence/cleanup failures through the host's ordinary extension error contract. A host callback still running after cleanup also prevents clean-shutdown success. A clean-exit host must not infer success after a failed handler. No callback/process memory recovery is promised.
|
|
242
|
+
The retired fork `session_checkpoint`/`acquireCheckpoint` APIs are no longer used. Host-supplied `beforeExecute` and raw `onToolCall` capture remain supported; the host still owns durable effect receipts and coherent workspace/session capture. No callback/process-memory recovery or automatic operation replay is promised.
|
|
249
243
|
|
|
250
244
|
### Runtime status snapshots
|
|
251
245
|
|
|
@@ -783,7 +777,11 @@ MCP servers can ship interactive UIs via [MCP Apps](https://github.com/modelcont
|
|
|
783
777
|
mcp({ action: "ui-messages" })
|
|
784
778
|
```
|
|
785
779
|
|
|
786
|
-
Returns
|
|
780
|
+
Returns completed UI sessions (the last ten), grouped in the existing prompt → intent → context → notification order. Prompt handoffs are also normalized into intents. Prompts, intents and notifications are saved in private disk history as they arrive; context updates and SSE replay retain their existing bounds. Small retrievals keep the original inline content/details. Large retrievals return `resultRef` for the complete grouped output and `historyRefs` for original JSONL events; use `read-result` with `offset`/`limit` to page them without rerunning UI actions. Plain output pages are streamed without loading the entire artifact into memory.
|
|
781
|
+
|
|
782
|
+
Retrieval drains only the sessions included in a usable inline/file receipt; read/write failure leaves them available for retry. History-write failure is explicitly reported, and unsaved events remain recoverable in the current instance's memory. **If normal storage fails and you then reload or exit, that unsaved tail is lost.** There is deliberately no session-journal rescue or alternate recovery file. Retrieve the pending messages before reloading or exiting when possible; do not treat the warning as a durable receipt. This rare failed-storage boundary does not change normal disk-history persistence or failed-delivery recovery. Files use private directory/file permissions and the existing output-directory lifetime: receipts remain readable after draining or UI closure, and are not deleted automatically. Remove artifacts only when you no longer need recovery. New sessions completed during retrieval are not cleared.
|
|
783
|
+
|
|
784
|
+
Messages and context updates are accepted only while the UI is open. An upload finishing after completion is rejected before it can invoke callbacks or alter history. Retrieval verifies the accepted history prefix's byte and event counts; truncated history remains pending recovery instead of being delivered and cleared.
|
|
787
785
|
|
|
788
786
|
**Browser controls:**
|
|
789
787
|
|
|
@@ -887,7 +885,7 @@ In interactive sessions, you can also authenticate from `/mcp` with `ctrl+a` or
|
|
|
887
885
|
|
|
888
886
|
### MCP output schemas
|
|
889
887
|
|
|
890
|
-
Advertised tool `outputSchema` values support JSON Schema draft-07 and 2020-12. Unstamped schemas use the SDK's 2020-12 default. Returned `structuredContent` is validated against the advertised schema for both proxy and direct-tool calls. Any JSON structured value—including `null`, `false`, and `0`—is rendered as text, including alongside non-empty content or images. The raw result remains available for script reduction and saved-result readback.
|
|
888
|
+
Advertised tool `outputSchema` values support JSON Schema draft-07 and 2020-12. Unstamped schemas use the SDK's 2020-12 default. Returned `structuredContent` is validated against the advertised schema for both proxy and direct-tool calls. Typed direct tools also expose that schema and bounded `structuredContent` to native Pi composition. Human text/images remain complementary. Oversized, absent, or not-run structured results return an `mcpAdapter` receipt (`omitted`, optional `resultRef`/`error`) instead of fabricating a server-schema value. Only the protocol's structured field is exposed: private details and `_meta` are not copied. Native content-only result-hook redaction drops structured output, as intended. Any JSON structured value—including `null`, `false`, and `0`—is rendered as text, including alongside non-empty content or images. The raw result remains available for script reduction and saved-result readback.
|
|
891
889
|
|
|
892
890
|
## How It Works
|
|
893
891
|
|
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,7 +10,6 @@ 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";
|
|
@@ -20,7 +19,6 @@ import { gatewayParameters, prepareGatewayArguments } from "./gateway-arguments.
|
|
|
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");
|
|
@@ -269,13 +267,6 @@ function installMcpAdapter(pi, options) {
|
|
|
269
267
|
if (ctx.cwd !== runtimeCwd)
|
|
270
268
|
await startSession(ctx);
|
|
271
269
|
});
|
|
272
|
-
onSessionCheckpoint(pi, async (event) => {
|
|
273
|
-
if (initPromise)
|
|
274
|
-
return { sleepReady: false, reason: "MCP initialization is active" };
|
|
275
|
-
if (!state || !currentOwner?.isActive())
|
|
276
|
-
return { sleepReady: false, reason: "MCP runtime is not initialized" };
|
|
277
|
-
return prepareMcpCheckpoint(state, event);
|
|
278
|
-
});
|
|
279
270
|
pi.on("session_shutdown", async () => {
|
|
280
271
|
// Cached tool selections exist before background server initialization finishes.
|
|
281
272
|
toolLoader.persist();
|
|
@@ -300,8 +291,6 @@ function installMcpAdapter(pi, options) {
|
|
|
300
291
|
if (failures.length)
|
|
301
292
|
throw new AggregateError(failures, "MCP session shutdown persistence/cleanup failed");
|
|
302
293
|
});
|
|
303
|
-
// Re-flag returned MCP tool failures so pi registers them as errors (see toolErrorOverride).
|
|
304
|
-
pi.on("tool_result", (event) => toolErrorOverride(event.details));
|
|
305
294
|
function createCommandContext(ctx) {
|
|
306
295
|
const owner = currentOwner;
|
|
307
296
|
const hasUI = ctx.hasUI;
|
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>;
|