@fitchmultz/pi-mcp-adapter 5.0.0 → 7.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +50 -0
- package/OAUTH.md +3 -3
- package/README.md +160 -88
- package/dist/app-bridge.bundle.js +75 -33
- package/dist/commands.js +9 -6
- package/dist/config.js +41 -16
- package/dist/direct-tools.d.ts +2 -4
- package/dist/direct-tools.js +21 -319
- package/dist/gateway-arguments.d.ts +33 -0
- package/dist/gateway-arguments.js +63 -0
- package/dist/index.js +181 -276
- package/dist/init.js +6 -20
- 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 +7 -6
- package/dist/mcp-code.js +98 -51
- package/dist/mcp-oauth-provider.d.ts +5 -5
- package/dist/mcp-oauth-provider.js +5 -5
- package/dist/mcp-output-guard.d.ts +57 -2
- package/dist/mcp-output-guard.js +129 -24
- package/dist/mcp-panel.d.ts +3 -3
- package/dist/mcp-panel.js +36 -99
- package/dist/mcp-script-worker.mjs +5 -2
- package/dist/mcp-setup-panel.d.ts +5 -4
- package/dist/mcp-setup-panel.js +15 -7
- package/dist/mcp-status.js +2 -1
- package/dist/mcp-trace.d.ts +1 -1
- package/dist/mcp-trace.js +1 -1
- package/dist/metadata-cache.d.ts +1 -1
- package/dist/metadata-cache.js +25 -88
- package/dist/npx-resolver.js +8 -4
- package/dist/prompts.d.ts +1 -1
- package/dist/prompts.js +1 -1
- package/dist/proxy-modes.d.ts +41 -7
- package/dist/proxy-modes.js +275 -490
- package/dist/resource-tools.d.ts +15 -0
- package/dist/resource-tools.js +26 -1
- package/dist/sampling-handler.js +5 -1
- package/dist/search-ranking.d.ts +0 -2
- package/dist/search-ranking.js +70 -137
- 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/state.d.ts +1 -1
- package/dist/tool-loader.d.ts +32 -0
- package/dist/tool-loader.js +152 -0
- package/dist/tool-metadata.d.ts +67 -0
- package/dist/tool-metadata.js +23 -17
- package/dist/tool-registrar.d.ts +4 -4
- package/dist/tool-registrar.js +18 -13
- package/dist/tool-result-renderer.d.ts +6 -3
- package/dist/tool-result-renderer.js +11 -6
- package/dist/types.d.ts +11 -36
- 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 +16 -4
- package/package.json +37 -96
- package/skills/mcp-scripting/SKILL.md +67 -20
- package/abort.ts +0 -34
- package/agent-dir.ts +0 -24
- package/app-bridge.bundle.js +0 -67
- package/banner.png +0 -0
- package/checkpoint.ts +0 -55
- package/commands.ts +0 -619
- package/config.ts +0 -1204
- package/consent-manager.ts +0 -25
- package/direct-tools.ts +0 -408
- package/dist/ts-shape.d.ts +0 -2
- package/dist/ts-shape.js +0 -147
- package/elicitation-handler.ts +0 -349
- package/error-signal.ts +0 -21
- package/glimpse-ui.ts +0 -80
- package/host-html-template.ts +0 -456
- package/index.ts +0 -900
- package/init.ts +0 -627
- 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 -386
- package/mcp-keyring-helper.cjs +0 -89
- package/mcp-oauth-provider.ts +0 -774
- package/mcp-output-guard.ts +0 -412
- package/mcp-panel.ts +0 -978
- package/mcp-script-worker.mjs +0 -128
- package/mcp-setup-panel.ts +0 -666
- package/mcp-status.ts +0 -109
- package/mcp-trace.ts +0 -295
- package/metadata-cache.ts +0 -333
- 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 -1361
- package/resource-tools.ts +0 -17
- package/runtime-owner.ts +0 -123
- package/sampling-handler.ts +0 -273
- package/scripts/build.mjs +0 -131
- package/scripts/prepare.mjs +0 -102
- package/search-ranking.ts +0 -196
- 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-metadata.ts +0 -235
- package/tool-registrar.ts +0 -69
- package/tool-result-renderer.ts +0 -286
- package/ts-shape.ts +0 -145
- package/tsconfig.build.json +0 -13
- package/tsconfig.json +0 -16
- package/types.ts +0 -810
- 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 -309
package/dist/lifecycle.d.ts
CHANGED
|
@@ -4,8 +4,6 @@ import { hasPendingAuth } from "./mcp-auth-flow.ts";
|
|
|
4
4
|
export type ReconnectCallback = (serverName: string) => void;
|
|
5
5
|
export type ReconnectFailureCallback = (serverName: string, error: unknown) => void;
|
|
6
6
|
export declare class McpLifecycleManager {
|
|
7
|
-
private readonly manager;
|
|
8
|
-
private readonly hasPendingAuthForServer;
|
|
9
7
|
private keepAliveServers;
|
|
10
8
|
private allServers;
|
|
11
9
|
private serverSettings;
|
|
@@ -23,6 +21,8 @@ export declare class McpLifecycleManager {
|
|
|
23
21
|
pauseForCheckpoint(): () => void;
|
|
24
22
|
private healthSignal;
|
|
25
23
|
private removeHealthAbortListener;
|
|
24
|
+
private readonly manager;
|
|
25
|
+
private readonly hasPendingAuthForServer;
|
|
26
26
|
constructor(manager: McpServerManager, hasPendingAuthForServer?: typeof hasPendingAuth);
|
|
27
27
|
setReconnectCallback(callback: ReconnectCallback): void;
|
|
28
28
|
setReconnectFailureCallback(callback: ReconnectFailureCallback): void;
|
package/dist/lifecycle.js
CHANGED
|
@@ -3,8 +3,6 @@ import { hasPendingAuth } from "./mcp-auth-flow.js";
|
|
|
3
3
|
import { logger } from "./logger.js";
|
|
4
4
|
import { formatTerminalError, sanitizeTerminalText } from "./utils.js";
|
|
5
5
|
export class McpLifecycleManager {
|
|
6
|
-
manager;
|
|
7
|
-
hasPendingAuthForServer;
|
|
8
6
|
keepAliveServers = new Map();
|
|
9
7
|
allServers = new Map();
|
|
10
8
|
serverSettings = new Map();
|
|
@@ -31,6 +29,8 @@ export class McpLifecycleManager {
|
|
|
31
29
|
}
|
|
32
30
|
healthSignal;
|
|
33
31
|
removeHealthAbortListener;
|
|
32
|
+
manager;
|
|
33
|
+
hasPendingAuthForServer;
|
|
34
34
|
constructor(manager, hasPendingAuthForServer = hasPendingAuth) {
|
|
35
35
|
this.manager = manager;
|
|
36
36
|
this.hasPendingAuthForServer = hasPendingAuthForServer;
|
package/dist/mcp-auth-flow.js
CHANGED
|
@@ -9,7 +9,7 @@ import { McpOAuthProvider, issuersMatch, loopbackRedirectsMatch, validateOAuthCl
|
|
|
9
9
|
import { ensureCallbackServer, waitForCallback, cancelPendingCallback, stopCallbackServer, releaseCallbackServer, takeCallbackResult, } from "./mcp-callback-server.js";
|
|
10
10
|
import { getAuthForUrl, clearAllCredentials, clearClientInfo, clearTokens, clearCodeVerifier, getOAuthState, clearOAuthState, getAuthBaseDir, } from "./mcp-auth.js";
|
|
11
11
|
import { isServerDisabled, isNonInteractiveOAuth, validateServerProtocolConfig } from "./types.js";
|
|
12
|
-
import { formatTerminalError, interpolateEnvRecord, interpolateEnvVars, normalizeRequestTimeoutMs } from "./utils.js";
|
|
12
|
+
import { ADAPTER_VERSION, formatTerminalError, interpolateEnvRecord, interpolateEnvVars, normalizeRequestTimeoutMs } from "./utils.js";
|
|
13
13
|
import { abortable, throwIfAborted } from "./abort.js";
|
|
14
14
|
import { combineAbortSignals, isAbortError } from "./runtime-owner.js";
|
|
15
15
|
function applyConfiguredScope(discovery, config) {
|
|
@@ -255,7 +255,7 @@ async function probeAuthDiscovery(serverUrl, definition, signal) {
|
|
|
255
255
|
const timeout = normalizeRequestTimeoutMs(definition?.requestTimeoutMs) ?? 5000;
|
|
256
256
|
const discoverySignal = combineAbortSignals(signal, AbortSignal.timeout(Math.ceil(timeout)));
|
|
257
257
|
let discovery = {};
|
|
258
|
-
const client = new Client({ name: "pi-mcp-auth-discovery", version:
|
|
258
|
+
const client = new Client({ name: "pi-mcp-auth-discovery", version: ADAPTER_VERSION }, {
|
|
259
259
|
versionNegotiation: { mode: definition?.protocolVersion ?? "auto" },
|
|
260
260
|
...(isNonInteractiveOAuth(definition?.oauth)
|
|
261
261
|
? { capabilities: { extensions: { [definition?.oauth && definition.oauth.crossAppAccess
|
package/dist/mcp-auth.d.ts
CHANGED
|
@@ -51,8 +51,8 @@ export interface AuthStorageOptions {
|
|
|
51
51
|
baseDir?: string;
|
|
52
52
|
}
|
|
53
53
|
export declare class OAuthCredentialStoreError extends Error {
|
|
54
|
-
readonly operation: 'read' | 'write' | 'remove';
|
|
55
54
|
readonly code = "OAUTH_CREDENTIAL_STORE_UNAVAILABLE";
|
|
55
|
+
readonly operation: 'read' | 'write' | 'remove';
|
|
56
56
|
constructor(message: string, operation: 'read' | 'write' | 'remove', cause: unknown);
|
|
57
57
|
}
|
|
58
58
|
export type OAuthCredentialStatus = {
|
package/dist/mcp-auth.js
CHANGED
|
@@ -15,8 +15,8 @@ import { createRequire } from 'module';
|
|
|
15
15
|
import { readFileSync, existsSync, rmSync } from 'fs';
|
|
16
16
|
import { dirname, join } from 'path';
|
|
17
17
|
import { fileURLToPath } from 'url';
|
|
18
|
-
import { getAdapterPath, getAgentPath } from
|
|
19
|
-
import { resolveConfiguredOAuthDir } from
|
|
18
|
+
import { getAdapterPath, getAgentPath } from './agent-dir.js';
|
|
19
|
+
import { resolveConfiguredOAuthDir } from './config.js';
|
|
20
20
|
const require = createRequire(import.meta.url);
|
|
21
21
|
const AUTH_SECRET_SERVICE = 'fitch-mcp-adapter.oauth';
|
|
22
22
|
const LEGACY_AUTH_SECRET_SERVICE = 'pi-mcp-adapter.oauth';
|
|
@@ -30,8 +30,8 @@ const TEST_LINUX_KEYRING_RECOVERY_ENV = 'PI_MCP_ADAPTER_TEST_LINUX_KEYRING_RECOV
|
|
|
30
30
|
const KEYRING_RECOVERY_TIMEOUT_MS = 10_000;
|
|
31
31
|
const AUTH_CHUNK_MANIFEST_KEY = '__piMcpAdapterOAuthChunked';
|
|
32
32
|
export class OAuthCredentialStoreError extends Error {
|
|
33
|
-
operation;
|
|
34
33
|
code = 'OAUTH_CREDENTIAL_STORE_UNAVAILABLE';
|
|
34
|
+
operation;
|
|
35
35
|
constructor(message, operation, cause) {
|
|
36
36
|
super(isAndroidKeyringLoadFailure(cause)
|
|
37
37
|
? `${message}. Android/Termux: @napi-rs/keyring could not load a native binding. Persistent OAuth requires a supported platform with an OS credential store; no plaintext fallback is used.`
|
package/dist/mcp-code.d.ts
CHANGED
|
@@ -1,11 +1,17 @@
|
|
|
1
1
|
import type { ToolInfo } from "@earendil-works/pi-coding-agent";
|
|
2
2
|
import type { McpExtensionState } from "./state.ts";
|
|
3
|
-
import type
|
|
3
|
+
import { type ContentBlock, type McpOperationContext } from "./types.ts";
|
|
4
4
|
export declare const DEFAULT_MCP_SCRIPT_TIMEOUT_MS = 30000;
|
|
5
5
|
export declare const runMcpScript: typeof runMcpScriptOperation;
|
|
6
6
|
declare function runMcpScriptOperation(state: McpExtensionState, code: string, timeoutMs?: number | null, getPiTools?: () => ToolInfo[], signal?: AbortSignal, toolCallId?: string, beforeDispatch?: (signal: AbortSignal | undefined, operation: McpOperationContext) => Promise<void>): Promise<{
|
|
7
7
|
content: ContentBlock[];
|
|
8
8
|
details: {
|
|
9
|
+
mode: string;
|
|
10
|
+
resultRefs?: string[];
|
|
11
|
+
error?: "aborted" | "call_capture_failed" | "script_error" | "timeout";
|
|
12
|
+
message?: string | undefined;
|
|
13
|
+
timeoutMs: number | null;
|
|
14
|
+
recovery?: {} | null;
|
|
9
15
|
calls?: ({
|
|
10
16
|
operation: "call";
|
|
11
17
|
path: string;
|
|
@@ -41,11 +47,6 @@ declare function runMcpScriptOperation(state: McpExtensionState, code: string, t
|
|
|
41
47
|
error: string;
|
|
42
48
|
durationMs: number;
|
|
43
49
|
})[];
|
|
44
|
-
recovery?: {} | null;
|
|
45
|
-
timeoutMs: number | null;
|
|
46
|
-
error?: "aborted" | "timeout" | "call_capture_failed" | "script_error";
|
|
47
|
-
message?: string | undefined;
|
|
48
|
-
mode: string;
|
|
49
50
|
};
|
|
50
51
|
}>;
|
|
51
52
|
export {};
|
package/dist/mcp-code.js
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import { formatWithOptions } from "node:util";
|
|
2
2
|
import { Worker } from "node:worker_threads";
|
|
3
|
-
import { guardMcpOutput, guardedMcpDetails, resolveMcpOutputGuardOptions } from "./mcp-output-guard.js";
|
|
4
|
-
import { executeCall } from "./proxy-modes.js";
|
|
3
|
+
import { guardMcpOutput, guardedMcpDetails, resolveMcpOutputGuardOptions, readMcpResult, formatMcpResultReference, formatMcpPayloadFile } from "./mcp-output-guard.js";
|
|
4
|
+
import { executeCall, executeConnect } from "./proxy-modes.js";
|
|
5
|
+
import { executeResourceList, executeResourceRead } from "./resource-tools.js";
|
|
5
6
|
import { combineAbortSignals } from "./runtime-owner.js";
|
|
6
7
|
import { paginate, rankSuggestions, rankToolMatches } from "./search-ranking.js";
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
8
|
+
import { catalogCoverage, toToolDescriptor } from "./tool-metadata.js";
|
|
9
|
+
import { isServerDisabled } from "./types.js";
|
|
9
10
|
export const DEFAULT_MCP_SCRIPT_TIMEOUT_MS = 30_000;
|
|
10
11
|
class McpScriptTimeoutError extends Error {
|
|
11
12
|
constructor(timeoutMs) {
|
|
@@ -73,11 +74,9 @@ function parseWorkerMessage(value) {
|
|
|
73
74
|
if (message.type === "emit" && "block" in message)
|
|
74
75
|
return { type: "emit", block: message.block };
|
|
75
76
|
if (message.type === "call" && typeof message.id === "number" && typeof message.path === "string") {
|
|
76
|
-
return "args"
|
|
77
|
-
? { type: "call", id: message.id, path: message.path, args: message.args }
|
|
78
|
-
: { type: "call", id: message.id, path: message.path };
|
|
77
|
+
return { type: "call", id: message.id, path: message.path, ...(message.args !== undefined ? { args: message.args } : {}), ...(typeof message.server === "string" ? { server: message.server } : {}) };
|
|
79
78
|
}
|
|
80
|
-
if ((message.type === "search" || message.type === "describe") && typeof message.id === "number") {
|
|
79
|
+
if ((message.type === "search" || message.type === "describe" || message.type === "resources" || message.type === "readResource" || message.type === "readResult") && typeof message.id === "number") {
|
|
81
80
|
return "input" in message
|
|
82
81
|
? { type: message.type, id: message.id, input: message.input }
|
|
83
82
|
: { type: message.type, id: message.id };
|
|
@@ -123,18 +122,21 @@ async function runMcpScriptOperation(state, code, timeoutMs = DEFAULT_MCP_SCRIPT
|
|
|
123
122
|
? Math.max(0, Date.now() - startedAt)
|
|
124
123
|
: operation.durationMs,
|
|
125
124
|
}));
|
|
126
|
-
|
|
127
|
-
const
|
|
125
|
+
const pendingOperations = new Set();
|
|
126
|
+
const resultRefs = new Set();
|
|
127
|
+
const payloadNotices = new Set();
|
|
128
|
+
const recordCall = async (path, dispatch) => {
|
|
128
129
|
// Record before dispatch so calls still in flight at timeout/abort appear in the trace.
|
|
129
130
|
const startedAt = Date.now();
|
|
130
131
|
const index = calls.push({ operation: "call", path, ok: false, error: "incomplete", durationMs: 0, startedAt }) - 1;
|
|
131
|
-
|
|
132
|
-
let hasRawResult = false;
|
|
133
|
-
const result = await executeCall(state, path, args, undefined, getPiTools, callSignal, (raw) => {
|
|
134
|
-
rawResult = raw;
|
|
135
|
-
hasRawResult = true;
|
|
136
|
-
}, { ...(toolCallId !== undefined ? { toolCallId } : {}), innerCallId }, beforeDispatch);
|
|
132
|
+
const result = await dispatch();
|
|
137
133
|
const details = result.details;
|
|
134
|
+
if (typeof details.resultRef === "string")
|
|
135
|
+
resultRefs.add(details.resultRef);
|
|
136
|
+
if (typeof details.resultWriteError === "string")
|
|
137
|
+
payloadNotices.add(`[MCP result could not be saved: ${details.resultWriteError}]`);
|
|
138
|
+
for (const file of (details.payloadFiles ?? []))
|
|
139
|
+
payloadNotices.add(formatMcpPayloadFile(file));
|
|
138
140
|
if (details.error !== undefined) {
|
|
139
141
|
const errorCode = String(details.error);
|
|
140
142
|
const suggestions = Array.isArray(details.suggestions)
|
|
@@ -147,37 +149,57 @@ async function runMcpScriptOperation(state, code, timeoutMs = DEFAULT_MCP_SCRIPT
|
|
|
147
149
|
: textFromContent(result.content);
|
|
148
150
|
calls[index] = {
|
|
149
151
|
operation: "call", path, ok: false, error: errorCode, durationMs: Date.now() - startedAt, startedAt,
|
|
150
|
-
...(
|
|
152
|
+
...(details.recovery !== undefined ? { recovery: details.recovery } : {}),
|
|
151
153
|
};
|
|
152
154
|
if (errorCode === "call_capture_failed")
|
|
153
155
|
throw new McpScriptCaptureError(details.recovery, message);
|
|
154
|
-
if (
|
|
156
|
+
if (details.recovery !== undefined)
|
|
155
157
|
output.push({ type: "text", text: message });
|
|
156
158
|
return {
|
|
157
159
|
ok: false,
|
|
160
|
+
...(details.mcpResult !== undefined ? { data: details.mcpResult } : {}),
|
|
161
|
+
...(details.resultRef ? { resultRef: details.resultRef } : {}),
|
|
158
162
|
error: { code: errorCode, message, ...(details.recovery ? { recovery: details.recovery } : {}) },
|
|
159
163
|
};
|
|
160
164
|
}
|
|
161
165
|
calls[index] = { operation: "call", path, ok: true, durationMs: Date.now() - startedAt, startedAt };
|
|
162
166
|
return {
|
|
163
167
|
ok: true,
|
|
164
|
-
data:
|
|
168
|
+
data: details.mcpResult !== undefined ? details.mcpResult : textFromContent(result.content),
|
|
169
|
+
...(details.resultRef ? { resultRef: details.resultRef } : {}),
|
|
165
170
|
};
|
|
166
171
|
};
|
|
167
|
-
const
|
|
172
|
+
const callIdentity = (innerCallId) => ({ ...(toolCallId !== undefined ? { toolCallId } : {}), innerCallId });
|
|
173
|
+
const callTool = (innerCallId, path, args, server) => recordCall(path, () => executeCall(state, path, args, server, getPiTools, callSignal, callIdentity(innerCallId), beforeDispatch, { raw: true }));
|
|
174
|
+
const discoverServer = async (server) => {
|
|
175
|
+
if (!server)
|
|
176
|
+
return undefined;
|
|
177
|
+
if (isServerDisabled(state.config.mcpServers[server]))
|
|
178
|
+
return { code: "server_disabled", message: `Server "${server}" is disabled.` };
|
|
179
|
+
if (state.config.mcpServers[server] && state.toolMetadata.has(server))
|
|
180
|
+
return undefined;
|
|
181
|
+
const result = await executeConnect(state, server, callSignal);
|
|
182
|
+
return result.details.error ? { code: result.details.error, message: textFromContent(result.content) } : undefined;
|
|
183
|
+
};
|
|
184
|
+
const searchTools = async (input) => {
|
|
168
185
|
const startedAt = Date.now();
|
|
169
186
|
const query = typeof input?.query === "string" ? input.query : "";
|
|
170
187
|
let error;
|
|
171
188
|
try {
|
|
172
|
-
if (query.trim() === "") {
|
|
173
|
-
return { items: [], total: 0, hasMore: false, nextOffset: null };
|
|
174
|
-
}
|
|
175
189
|
const server = typeof input?.server === "string" ? input.server : undefined;
|
|
190
|
+
const discoveryError = await discoverServer(server);
|
|
191
|
+
const coverage = catalogCoverage(state, server);
|
|
192
|
+
if (discoveryError) {
|
|
193
|
+
error = discoveryError.code;
|
|
194
|
+
return { error: discoveryError, coverage };
|
|
195
|
+
}
|
|
196
|
+
if (query.trim() === "")
|
|
197
|
+
return { items: [], total: 0, hasMore: false, nextOffset: null, coverage };
|
|
176
198
|
const limit = typeof input?.limit === "number" ? input.limit : 12;
|
|
177
199
|
const offset = typeof input?.offset === "number" ? input.offset : 0;
|
|
178
|
-
const page = paginate(rankToolMatches(state, query, server), offset, limit);
|
|
200
|
+
const page = paginate(rankToolMatches(state, query, server).filter(match => !match.tool.resourceUri), offset, limit);
|
|
179
201
|
return {
|
|
180
|
-
...page,
|
|
202
|
+
...page, coverage,
|
|
181
203
|
items: page.items.map(({ server: matchServer, tool, score }) => ({
|
|
182
204
|
path: tool.name,
|
|
183
205
|
name: tool.originalName,
|
|
@@ -197,26 +219,31 @@ async function runMcpScriptOperation(state, code, timeoutMs = DEFAULT_MCP_SCRIPT
|
|
|
197
219
|
: { operation: "search", query, ok: false, error: error instanceof Error ? error.message : String(error), durationMs: Date.now() - startedAt, startedAt });
|
|
198
220
|
}
|
|
199
221
|
};
|
|
200
|
-
const describeTool = (input) => {
|
|
222
|
+
const describeTool = async (input) => {
|
|
201
223
|
const startedAt = Date.now();
|
|
202
224
|
const path = typeof input?.path === "string" ? input.path : "";
|
|
203
225
|
let error;
|
|
204
226
|
try {
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
227
|
+
const selectedServer = typeof input?.server === "string" ? input.server : undefined;
|
|
228
|
+
const discoveryError = await discoverServer(selectedServer);
|
|
229
|
+
if (discoveryError) {
|
|
230
|
+
error = discoveryError.code;
|
|
231
|
+
return { path, error: discoveryError };
|
|
232
|
+
}
|
|
233
|
+
const matches = [...state.toolMetadata].flatMap(([server, metadata]) => {
|
|
234
|
+
if ((selectedServer && server !== selectedServer) || !state.config.mcpServers[server] || isServerDisabled(state.config.mcpServers[server]))
|
|
235
|
+
return [];
|
|
236
|
+
const tools = metadata.filter(tool => !tool.resourceUri);
|
|
237
|
+
const originals = selectedServer ? tools.filter(tool => tool.originalName === path) : [];
|
|
238
|
+
return (originals.length ? originals : tools.filter(tool => tool.name === path)).map(tool => toToolDescriptor(server, tool));
|
|
239
|
+
});
|
|
240
|
+
if (matches.length === 1)
|
|
241
|
+
return matches[0];
|
|
242
|
+
if (matches.length > 1) {
|
|
243
|
+
error = "ambiguous_tool";
|
|
244
|
+
return { path, error: { code: "ambiguous_tool", message: "Specify server and exact tool name." } };
|
|
218
245
|
}
|
|
219
|
-
const suggestions = path ? rankSuggestions(state, path, 5) : [];
|
|
246
|
+
const suggestions = path ? rankSuggestions(state, path, 5, selectedServer) : [];
|
|
220
247
|
error = "tool_not_found";
|
|
221
248
|
return {
|
|
222
249
|
path,
|
|
@@ -274,20 +301,40 @@ async function runMcpScriptOperation(state, code, timeoutMs = DEFAULT_MCP_SCRIPT
|
|
|
274
301
|
reject(new Error(message.message));
|
|
275
302
|
return;
|
|
276
303
|
}
|
|
277
|
-
|
|
304
|
+
const operation = (async () => {
|
|
278
305
|
let envelope;
|
|
279
306
|
if (message.type === "call") {
|
|
280
|
-
envelope = await callTool(message.id, message.path, message.args);
|
|
307
|
+
envelope = await callTool(message.id, message.path, message.args, message.server);
|
|
281
308
|
}
|
|
282
309
|
else if (message.type === "search") {
|
|
283
|
-
envelope = searchTools(message.input);
|
|
310
|
+
envelope = await searchTools(message.input);
|
|
311
|
+
}
|
|
312
|
+
else if (message.type === "describe") {
|
|
313
|
+
envelope = await describeTool(message.input);
|
|
314
|
+
}
|
|
315
|
+
else if (message.type === "readResult") {
|
|
316
|
+
envelope = await readMcpResult(message.input, resolveMcpOutputGuardOptions(state.config.settings, state.outputDirectory));
|
|
284
317
|
}
|
|
285
318
|
else {
|
|
286
|
-
|
|
319
|
+
const input = message.input;
|
|
320
|
+
if (typeof input?.server !== "string" || !input.server || (message.type === "readResource" && (typeof input.uri !== "string" || !input.uri))) {
|
|
321
|
+
envelope = { error: { code: "invalid_arguments", message: "Specify server and, for reads, resource uri." } };
|
|
322
|
+
}
|
|
323
|
+
else if (message.type === "resources") {
|
|
324
|
+
const result = await executeResourceList(state, input.server, typeof input.limit === "number" ? input.limit : 12, typeof input.offset === "number" ? input.offset : 0, callSignal);
|
|
325
|
+
envelope = result.details.error ? { error: { code: result.details.error, message: textFromContent(result.content) } } : result.details;
|
|
326
|
+
}
|
|
327
|
+
else {
|
|
328
|
+
const server = input.server;
|
|
329
|
+
const uri = input.uri;
|
|
330
|
+
envelope = await recordCall(`${server}:${uri}`, () => executeResourceRead(state, server, uri, callSignal, callIdentity(message.id), beforeDispatch, true));
|
|
331
|
+
}
|
|
287
332
|
}
|
|
288
333
|
const response = { type: "result", id: message.id, envelope };
|
|
289
334
|
activeWorker.postMessage(response);
|
|
290
|
-
})()
|
|
335
|
+
})();
|
|
336
|
+
pendingOperations.add(operation);
|
|
337
|
+
void operation.catch(reject).finally(() => pendingOperations.delete(operation));
|
|
291
338
|
});
|
|
292
339
|
activeWorker.once("error", reject);
|
|
293
340
|
activeWorker.once("exit", (code) => {
|
|
@@ -300,7 +347,6 @@ async function runMcpScriptOperation(state, code, timeoutMs = DEFAULT_MCP_SCRIPT
|
|
|
300
347
|
return;
|
|
301
348
|
const timeoutError = new McpScriptTimeoutError(resolvedTimeoutMs);
|
|
302
349
|
timer = setTimeout(() => {
|
|
303
|
-
callsSnapshot = snapshotCalls();
|
|
304
350
|
timeoutController.abort(timeoutError);
|
|
305
351
|
void activeWorker.terminate();
|
|
306
352
|
reject(timeoutError);
|
|
@@ -309,7 +355,6 @@ async function runMcpScriptOperation(state, code, timeoutMs = DEFAULT_MCP_SCRIPT
|
|
|
309
355
|
const aborted = externalSignal
|
|
310
356
|
? new Promise((_resolve, reject) => {
|
|
311
357
|
const onAbort = () => {
|
|
312
|
-
callsSnapshot = snapshotCalls();
|
|
313
358
|
void activeWorker.terminate();
|
|
314
359
|
reject(abortReasonError(externalSignal.reason));
|
|
315
360
|
};
|
|
@@ -342,20 +387,22 @@ async function runMcpScriptOperation(state, code, timeoutMs = DEFAULT_MCP_SCRIPT
|
|
|
342
387
|
finally {
|
|
343
388
|
clearTimeout(timer);
|
|
344
389
|
removeAbortListener();
|
|
345
|
-
|
|
346
|
-
// (deadline, abort, or early return). Snapshot before aborting stragglers.
|
|
347
|
-
callsSnapshot ??= snapshotCalls();
|
|
390
|
+
worker?.removeAllListeners("message");
|
|
348
391
|
// A script may finish without awaiting every call; abort leftovers so
|
|
349
392
|
// parent-side dispatches do not outlive the script.
|
|
350
393
|
timeoutController.abort(new Error("mcp_script finished"));
|
|
394
|
+
// Let cancelled dispatches report whether an outcome is known before taking the trace.
|
|
395
|
+
await Promise.allSettled(pendingOperations);
|
|
351
396
|
await worker?.terminate();
|
|
352
397
|
}
|
|
398
|
+
const callsSnapshot = snapshotCalls();
|
|
353
399
|
// Snapshot before the asynchronous output guard; the terminated worker can no longer emit.
|
|
354
|
-
const guarded = await guardMcpOutput(output.length > 0 ?
|
|
400
|
+
const guarded = await guardMcpOutput([...(output.length > 0 ? output : [{ type: "text", text: "(no output)" }]), ...[...payloadNotices].map(text => ({ type: "text", text })), ...[...resultRefs].map(ref => ({ type: "text", text: formatMcpResultReference(ref) }))], resolveMcpOutputGuardOptions(state.config.settings, state.outputDirectory));
|
|
355
401
|
return {
|
|
356
402
|
content: guarded.content,
|
|
357
403
|
details: {
|
|
358
404
|
mode: "script",
|
|
405
|
+
...(resultRefs.size ? { resultRefs: [...resultRefs] } : {}),
|
|
359
406
|
...(errorCode ? { error: errorCode, message: errorMessage } : {}),
|
|
360
407
|
timeoutMs: resolvedTimeoutMs,
|
|
361
408
|
...(recovery !== undefined ? { recovery } : {}),
|
|
@@ -49,11 +49,6 @@ export interface McpOAuthCallbacks {
|
|
|
49
49
|
* Implements the OAuthClientProvider interface from the MCP SDK.
|
|
50
50
|
*/
|
|
51
51
|
export declare class McpOAuthProvider implements OAuthClientProvider {
|
|
52
|
-
private serverName;
|
|
53
|
-
private serverUrl;
|
|
54
|
-
private config;
|
|
55
|
-
private callbacks;
|
|
56
|
-
private storageOptions;
|
|
57
52
|
private readonly redirectUrlSnapshot;
|
|
58
53
|
readonly clientMetadataUrl?: string;
|
|
59
54
|
private readonly controller;
|
|
@@ -66,6 +61,11 @@ export declare class McpOAuthProvider implements OAuthClientProvider {
|
|
|
66
61
|
private flowDiscoveryState;
|
|
67
62
|
private flowIssuerMismatch;
|
|
68
63
|
private flowState;
|
|
64
|
+
private serverName;
|
|
65
|
+
private serverUrl;
|
|
66
|
+
private config;
|
|
67
|
+
private callbacks;
|
|
68
|
+
private storageOptions;
|
|
69
69
|
constructor(serverName: string, serverUrl: string, config: McpOAuthConfig, callbacks: McpOAuthCallbacks, storageOptions?: AuthStorageOptions, runtimeSignal?: AbortSignal, initialState?: string);
|
|
70
70
|
private get isNonInteractive();
|
|
71
71
|
private get discoveredIssuer();
|
|
@@ -152,11 +152,6 @@ function addAuthorizationParams(authorizationUrl, params) {
|
|
|
152
152
|
* Implements the OAuthClientProvider interface from the MCP SDK.
|
|
153
153
|
*/
|
|
154
154
|
export class McpOAuthProvider {
|
|
155
|
-
serverName;
|
|
156
|
-
serverUrl;
|
|
157
|
-
config;
|
|
158
|
-
callbacks;
|
|
159
|
-
storageOptions;
|
|
160
155
|
redirectUrlSnapshot;
|
|
161
156
|
clientMetadataUrl;
|
|
162
157
|
controller = new AbortController();
|
|
@@ -169,6 +164,11 @@ export class McpOAuthProvider {
|
|
|
169
164
|
flowDiscoveryState;
|
|
170
165
|
flowIssuerMismatch = false;
|
|
171
166
|
flowState;
|
|
167
|
+
serverName;
|
|
168
|
+
serverUrl;
|
|
169
|
+
config;
|
|
170
|
+
callbacks;
|
|
171
|
+
storageOptions;
|
|
172
172
|
constructor(serverName, serverUrl, config, callbacks, storageOptions = {}, runtimeSignal, initialState) {
|
|
173
173
|
this.serverName = serverName;
|
|
174
174
|
this.serverUrl = serverUrl;
|
|
@@ -38,16 +38,29 @@ export interface McpOutputGuardOptions {
|
|
|
38
38
|
/**
|
|
39
39
|
* Raw MCP result to expose as details.mcpResult. Kept raw when its JSON
|
|
40
40
|
* fits detailsMaxBytes (or when the guard is disabled); otherwise replaced
|
|
41
|
-
* with a compact summary and spilled to a
|
|
42
|
-
*
|
|
41
|
+
* with a compact summary and spilled to a file. Payloads that are not fully
|
|
42
|
+
* visible as plain text also get a model-visible reference for local readback.
|
|
43
43
|
*/
|
|
44
44
|
rawMcpResult?: unknown;
|
|
45
|
+
retainedMcpResult?: RetainedMcpResult;
|
|
45
46
|
}
|
|
46
47
|
export interface GuardedMcpOutput {
|
|
47
48
|
content: ContentBlock[];
|
|
48
49
|
outputGuard?: McpOutputGuardDetails;
|
|
49
50
|
mcpResult?: unknown;
|
|
51
|
+
resultRef?: string;
|
|
52
|
+
resultWriteError?: string;
|
|
53
|
+
payloadFiles?: McpPayloadFile[];
|
|
54
|
+
}
|
|
55
|
+
export interface McpPayloadFile {
|
|
56
|
+
index: number;
|
|
57
|
+
kind: "audio" | "resource";
|
|
58
|
+
uri?: string;
|
|
59
|
+
mimeType?: string;
|
|
60
|
+
path?: string;
|
|
61
|
+
error?: string;
|
|
50
62
|
}
|
|
63
|
+
export type RetainedMcpResult = Pick<GuardedMcpOutput, "mcpResult" | "resultRef" | "resultWriteError" | "payloadFiles">;
|
|
51
64
|
export declare function resolveMcpOutputGuardOptions(settings?: McpSettings, outputDirectory?: string): Pick<McpOutputGuardOptions, "enabled" | "maxBytes" | "maxLines" | "detailsMaxBytes" | "outputDirectory">;
|
|
52
65
|
/** Spread helper for tool-result details: includes mcpResult/outputGuard only when present. */
|
|
53
66
|
export declare function guardedMcpDetails(guarded: GuardedMcpOutput): Record<string, unknown>;
|
|
@@ -57,3 +70,45 @@ export declare function guardedMcpDetails(guarded: GuardedMcpOutput): Record<str
|
|
|
57
70
|
* they are delivered to the provider as native image content, not text context.
|
|
58
71
|
*/
|
|
59
72
|
export declare function guardMcpOutput(content: ContentBlock[], options?: McpOutputGuardOptions): Promise<GuardedMcpOutput>;
|
|
73
|
+
/**
|
|
74
|
+
* Bound details.mcpResult: keep the raw result when its JSON fits within
|
|
75
|
+
* detailsMaxBytes; otherwise replace it with a compact summary and spill the
|
|
76
|
+
* raw JSON to a temp file.
|
|
77
|
+
*/
|
|
78
|
+
export declare function retainMcpResult(result: unknown, options?: McpOutputGuardOptions, rawResult?: boolean): Promise<RetainedMcpResult>;
|
|
79
|
+
export declare function formatMcpPayloadFile(file: McpPayloadFile): string;
|
|
80
|
+
export declare function formatMcpResultReference(ref: string): string;
|
|
81
|
+
export interface ReadMcpResultInput {
|
|
82
|
+
ref: string;
|
|
83
|
+
/** RFC 6901 JSON Pointer. Omit to read the entire retained result. */
|
|
84
|
+
path?: string;
|
|
85
|
+
fields?: string[];
|
|
86
|
+
/** Zero-based character offset in the selected, pretty-printed value. */
|
|
87
|
+
offset?: number;
|
|
88
|
+
/** Maximum characters, additionally bounded by the configured output limits. */
|
|
89
|
+
limit?: number;
|
|
90
|
+
}
|
|
91
|
+
export declare function readMcpResult(input: ReadMcpResultInput, options?: McpOutputGuardOptions): Promise<{
|
|
92
|
+
content: {
|
|
93
|
+
type: "text";
|
|
94
|
+
text: string;
|
|
95
|
+
}[];
|
|
96
|
+
details: {
|
|
97
|
+
ref: string;
|
|
98
|
+
offset: number;
|
|
99
|
+
nextOffset: number | null;
|
|
100
|
+
totalCharacters: number;
|
|
101
|
+
path?: string;
|
|
102
|
+
error?: never;
|
|
103
|
+
message?: never;
|
|
104
|
+
};
|
|
105
|
+
} | {
|
|
106
|
+
content: {
|
|
107
|
+
type: "text";
|
|
108
|
+
text: string;
|
|
109
|
+
}[];
|
|
110
|
+
details: {
|
|
111
|
+
error: string;
|
|
112
|
+
message: string;
|
|
113
|
+
};
|
|
114
|
+
}>;
|