@fitchmultz/pi-mcp-adapter 6.0.0 → 7.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (105) hide show
  1. package/CHANGELOG.md +26 -0
  2. package/README.md +12 -8
  3. package/dist/app-bridge.bundle.js +14 -14
  4. package/dist/config.js +41 -16
  5. package/dist/gateway-arguments.d.ts +1 -1
  6. package/dist/index.js +1 -1
  7. package/dist/lifecycle.d.ts +2 -2
  8. package/dist/lifecycle.js +2 -2
  9. package/dist/mcp-auth-flow.js +2 -2
  10. package/dist/mcp-auth.d.ts +1 -1
  11. package/dist/mcp-auth.js +3 -3
  12. package/dist/mcp-code.d.ts +6 -6
  13. package/dist/mcp-code.js +11 -10
  14. package/dist/mcp-oauth-provider.d.ts +5 -5
  15. package/dist/mcp-oauth-provider.js +5 -5
  16. package/dist/mcp-output-guard.d.ts +1 -1
  17. package/dist/mcp-panel.d.ts +2 -2
  18. package/dist/mcp-panel.js +2 -2
  19. package/dist/mcp-setup-panel.d.ts +5 -4
  20. package/dist/mcp-setup-panel.js +15 -7
  21. package/dist/mcp-trace.d.ts +1 -1
  22. package/dist/mcp-trace.js +1 -1
  23. package/dist/npx-resolver.js +8 -4
  24. package/dist/proxy-modes.d.ts +2 -2
  25. package/dist/proxy-modes.js +9 -9
  26. package/dist/sampling-handler.js +1 -0
  27. package/dist/search-ranking.js +6 -2
  28. package/dist/server-manager.d.ts +2 -2
  29. package/dist/server-manager.js +44 -27
  30. package/dist/session-recovery.d.ts +7 -4
  31. package/dist/session-recovery.js +76 -10
  32. package/dist/tool-metadata.d.ts +43 -43
  33. package/dist/tool-result-renderer.d.ts +3 -3
  34. package/dist/tool-result-renderer.js +3 -3
  35. package/dist/ui-resource-handler.d.ts +2 -2
  36. package/dist/ui-resource-handler.js +2 -2
  37. package/dist/ui-server.js +4 -3
  38. package/dist/ui-stream-types.d.ts +8 -8
  39. package/dist/unix-socket-transport.d.ts +1 -1
  40. package/dist/unix-socket-transport.js +1 -1
  41. package/dist/utils.d.ts +1 -0
  42. package/dist/utils.js +14 -0
  43. package/package.json +34 -98
  44. package/abort.ts +0 -34
  45. package/agent-dir.ts +0 -24
  46. package/app-bridge.bundle.js +0 -109
  47. package/banner.png +0 -0
  48. package/checkpoint.ts +0 -55
  49. package/commands.ts +0 -622
  50. package/config.ts +0 -1204
  51. package/consent-manager.ts +0 -25
  52. package/direct-tools.ts +0 -75
  53. package/elicitation-handler.ts +0 -349
  54. package/error-signal.ts +0 -21
  55. package/gateway-arguments.ts +0 -73
  56. package/glimpse-ui.ts +0 -80
  57. package/host-html-template.ts +0 -456
  58. package/index.ts +0 -767
  59. package/init.ts +0 -613
  60. package/legacy-migration.ts +0 -47
  61. package/lifecycle.ts +0 -165
  62. package/logger.ts +0 -127
  63. package/mcp-auth-flow.ts +0 -975
  64. package/mcp-auth.ts +0 -793
  65. package/mcp-callback-server.ts +0 -450
  66. package/mcp-code.ts +0 -412
  67. package/mcp-keyring-helper.cjs +0 -89
  68. package/mcp-oauth-provider.ts +0 -774
  69. package/mcp-output-guard.ts +0 -530
  70. package/mcp-panel.ts +0 -907
  71. package/mcp-script-worker.mjs +0 -131
  72. package/mcp-setup-panel.ts +0 -666
  73. package/mcp-status.ts +0 -110
  74. package/mcp-trace.ts +0 -295
  75. package/metadata-cache.ts +0 -271
  76. package/npx-resolver.ts +0 -488
  77. package/onboarding-state.ts +0 -58
  78. package/panel-keys.ts +0 -37
  79. package/prompts.ts +0 -337
  80. package/proxy-modes.ts +0 -1109
  81. package/resource-tools.ts +0 -52
  82. package/runtime-owner.ts +0 -123
  83. package/sampling-handler.ts +0 -276
  84. package/scripts/build-app-bridge.mjs +0 -15
  85. package/scripts/build.mjs +0 -132
  86. package/scripts/prepare.mjs +0 -103
  87. package/search-ranking.ts +0 -132
  88. package/server-manager.ts +0 -1320
  89. package/session-recovery.ts +0 -316
  90. package/state.ts +0 -64
  91. package/tool-approval.ts +0 -78
  92. package/tool-loader.ts +0 -153
  93. package/tool-metadata.ts +0 -240
  94. package/tool-registrar.ts +0 -74
  95. package/tool-result-renderer.ts +0 -294
  96. package/tsconfig.build.json +0 -13
  97. package/tsconfig.json +0 -16
  98. package/types.ts +0 -785
  99. package/ui-resource-handler.ts +0 -249
  100. package/ui-server.ts +0 -808
  101. package/ui-session.ts +0 -568
  102. package/ui-stream-types.ts +0 -89
  103. package/ui-tool-visibility.ts +0 -25
  104. package/unix-socket-transport.ts +0 -85
  105. package/utils.ts +0 -307
package/dist/config.js CHANGED
@@ -1,7 +1,9 @@
1
1
  // config.ts - Config loading with import support
2
- import { existsSync, readFileSync, writeFileSync, mkdirSync, realpathSync, renameSync } from "node:fs";
2
+ import { existsSync, readFileSync, writeFileSync, mkdirSync, realpathSync, renameSync, statSync, chmodSync } from "node:fs";
3
+ import { randomUUID } from "node:crypto";
3
4
  import { homedir } from "node:os";
4
5
  import { dirname, isAbsolute, join, relative, resolve, sep } from "node:path";
6
+ import { isDeepStrictEqual } from "node:util";
5
7
  import { parse as parseToml } from "smol-toml";
6
8
  import stripJsonComments from "strip-json-comments";
7
9
  import { getAdapterPath } from "./agent-dir.js";
@@ -367,6 +369,14 @@ function mergeServerMaps(base, next) {
367
369
  delete baseEntry.oauth;
368
370
  }
369
371
  }
372
+ // Stdio env belongs to the program that received it, not just the server name.
373
+ if (existing?.command && ((definition.command !== undefined && definition.command !== existing.command)
374
+ || (definition.args !== undefined && !isDeepStrictEqual(definition.args, existing.args ?? []))
375
+ || (definition.cwd !== undefined && definition.cwd !== existing.cwd))) {
376
+ if (baseEntry === existing)
377
+ baseEntry = { ...existing };
378
+ delete baseEntry.env;
379
+ }
370
380
  merged[name] = { ...baseEntry, ...definition };
371
381
  }
372
382
  return merged;
@@ -533,8 +543,10 @@ function mergeOpenCodeConfigs(base, next) {
533
543
  delete safeBase.cwd;
534
544
  }
535
545
  }
546
+ if (Object.hasOwn(override, "cwd") && override.cwd !== safeBase.cwd)
547
+ delete safeBase.environment;
536
548
  const mergedEntry = { ...safeBase, ...override };
537
- for (const field of ["environment", "headers", "oauth"]) {
549
+ for (const field of ["headers", "oauth"]) {
538
550
  const baseField = safeBase[field];
539
551
  const nextField = override[field];
540
552
  if (baseField && typeof baseField === "object" && !Array.isArray(baseField)
@@ -714,24 +726,37 @@ function buildConfigWritePreview(filePath, nextRaw) {
714
726
  function readRawConfigObject(filePath) {
715
727
  if (!existsSync(filePath))
716
728
  return {};
729
+ let raw;
717
730
  try {
718
- const raw = parseJsonConfig(readFileSync(filePath, "utf-8"));
719
- return raw && typeof raw === "object" && !Array.isArray(raw) ? raw : {};
731
+ raw = parseJsonConfig(readFileSync(filePath, "utf-8"));
720
732
  }
721
- catch {
722
- return {};
733
+ catch (error) {
734
+ throw new Error(`Cannot read MCP config at ${filePath}: ${error instanceof Error ? error.message : String(error)}`, { cause: error });
735
+ }
736
+ if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
737
+ throw new Error(`Invalid MCP config at ${filePath}: expected an object`);
723
738
  }
739
+ return raw;
724
740
  }
725
741
  function writeRawConfigObject(filePath, raw) {
726
742
  mkdirSync(dirname(filePath), { recursive: true });
727
- const tmpPath = `${filePath}.${process.pid}.tmp`;
728
- writeFileSync(tmpPath, `${JSON.stringify(raw, null, 2)}\n`, "utf-8");
743
+ const existingMode = existsSync(filePath) ? statSync(filePath).mode & 0o7777 : undefined;
744
+ const tmpPath = `${filePath}.${randomUUID()}.tmp`;
745
+ writeFileSync(tmpPath, `${JSON.stringify(raw, null, 2)}\n`, {
746
+ encoding: "utf-8",
747
+ mode: existingMode ?? 0o666,
748
+ flag: "wx",
749
+ });
750
+ if (existingMode !== undefined)
751
+ chmodSync(tmpPath, existingMode);
729
752
  renameSync(tmpPath, filePath);
730
753
  }
731
- function getServersObject(raw) {
732
- const existing = raw.mcpServers ?? raw["mcp-servers"] ?? {};
754
+ function getServersObject(raw, filePath) {
755
+ const existing = raw.mcpServers !== undefined ? raw.mcpServers
756
+ : raw["mcp-servers"] !== undefined ? raw["mcp-servers"]
757
+ : {};
733
758
  if (!existing || typeof existing !== "object" || Array.isArray(existing)) {
734
- return {};
759
+ throw new Error(`Invalid MCP config at ${filePath}: mcpServers must be an object`);
735
760
  }
736
761
  return existing;
737
762
  }
@@ -886,7 +911,7 @@ export function previewCompatibilityImports(importKinds, overridePath) {
886
911
  const currentImports = Array.isArray(raw.imports) ? raw.imports.filter((value) => typeof value === "string") : [];
887
912
  const merged = [...new Set([...currentImports, ...importKinds])];
888
913
  const nextRaw = { ...raw, imports: merged };
889
- setServersObject(nextRaw, getServersObject(nextRaw));
914
+ setServersObject(nextRaw, getServersObject(nextRaw, targetPath));
890
915
  return buildConfigWritePreview(targetPath, nextRaw);
891
916
  }
892
917
  export function ensureCompatibilityImports(importKinds, overridePath) {
@@ -899,7 +924,7 @@ export function ensureCompatibilityImports(importKinds, overridePath) {
899
924
  return { path: targetPath, added: [] };
900
925
  }
901
926
  raw.imports = merged;
902
- const servers = getServersObject(raw);
927
+ const servers = getServersObject(raw, targetPath);
903
928
  setServersObject(raw, servers);
904
929
  writeRawConfigObject(targetPath, raw);
905
930
  return { path: targetPath, added };
@@ -923,14 +948,14 @@ export function writeStarterProjectConfig(cwd = process.cwd()) {
923
948
  export function previewSharedServerEntry(filePath, serverName, entry) {
924
949
  const raw = readRawConfigObject(filePath);
925
950
  const nextRaw = { ...raw };
926
- const servers = getServersObject(nextRaw);
951
+ const servers = getServersObject(nextRaw, filePath);
927
952
  servers[serverName] = entry;
928
953
  setServersObject(nextRaw, servers);
929
954
  return buildConfigWritePreview(filePath, nextRaw);
930
955
  }
931
956
  export function writeSharedServerEntry(filePath, serverName, entry) {
932
957
  const raw = readRawConfigObject(filePath);
933
- const servers = getServersObject(raw);
958
+ const servers = getServersObject(raw, filePath);
934
959
  servers[serverName] = entry;
935
960
  setServersObject(raw, servers);
936
961
  writeRawConfigObject(filePath, raw);
@@ -991,7 +1016,7 @@ export function writeDirectToolsConfig(changes, provenance) {
991
1016
  }
992
1017
  for (const [filePath, entries] of byPath) {
993
1018
  const raw = readRawConfigObject(filePath);
994
- const servers = getServersObject(raw);
1019
+ const servers = getServersObject(raw, filePath);
995
1020
  for (const { name, value } of entries) {
996
1021
  // Keep inherited connection details in their source file.
997
1022
  servers[name] = { ...servers[name], directTools: value };
@@ -15,7 +15,7 @@ export type GatewayArguments = {
15
15
  fields?: string[];
16
16
  };
17
17
  export declare const gatewayParameters: Type.TObject<{
18
- action: Type.TUnsafe<"status" | "resources" | "connect" | "search" | "list" | "instructions" | "call" | "auth-start" | "auth-complete" | "describe" | "read-resource" | "read-result" | "ui-messages">;
18
+ action: Type.TUnsafe<"auth-complete" | "auth-start" | "call" | "connect" | "describe" | "instructions" | "list" | "read-resource" | "read-result" | "resources" | "search" | "status" | "ui-messages">;
19
19
  server: Type.TOptional<Type.TString>;
20
20
  tool: Type.TOptional<Type.TString>;
21
21
  args: Type.TOptional<Type.TObject<{}>>;
package/dist/index.js CHANGED
@@ -552,7 +552,7 @@ function installMcpAdapter(pi, options) {
552
552
  : params.server ? (ready.toolMetadata.get(params.server) ?? []).filter(tool => !tool.resourceUri).map(tool => ({ server: params.server, tool })).sort((a, b) => a.tool.name.localeCompare(b.tool.name)) : [];
553
553
  const page = paginate(matches, params.offset ?? 0, params.limit ?? 5);
554
554
  const tools = toolLoader.activate(page.items);
555
- const result = executeSearch(ready, params.query, params.server, tools.length < page.items.length, params.limit ?? 5, params.offset);
555
+ const result = executeSearch(ready, params.query, params.server, tools.length < page.items.length, params.limit ?? 5, params.offset, "mcp_search");
556
556
  const guarded = await guardMcpOutput([
557
557
  ...result.content,
558
558
  { type: "text", text: tools.length ? `Loaded for the next request: ${JSON.stringify(tools)}` : "No direct tools loaded. Use mcp action call or tools.call in mcp_script with the returned exact names." },
@@ -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;
@@ -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: "4.3.1" }, {
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
@@ -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 "./agent-dir.js";
19
- import { resolveConfiguredOAuthDir } from "./config.js";
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.`
@@ -6,6 +6,12 @@ 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,12 +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
- resultRefs?: string[];
49
- mode: string;
50
50
  };
51
51
  }>;
52
52
  export {};
package/dist/mcp-code.js CHANGED
@@ -122,7 +122,7 @@ async function runMcpScriptOperation(state, code, timeoutMs = DEFAULT_MCP_SCRIPT
122
122
  ? Math.max(0, Date.now() - startedAt)
123
123
  : operation.durationMs,
124
124
  }));
125
- let callsSnapshot;
125
+ const pendingOperations = new Set();
126
126
  const resultRefs = new Set();
127
127
  const payloadNotices = new Set();
128
128
  const recordCall = async (path, dispatch) => {
@@ -149,11 +149,11 @@ async function runMcpScriptOperation(state, code, timeoutMs = DEFAULT_MCP_SCRIPT
149
149
  : textFromContent(result.content);
150
150
  calls[index] = {
151
151
  operation: "call", path, ok: false, error: errorCode, durationMs: Date.now() - startedAt, startedAt,
152
- ...(errorCode === "ambiguous_outcome" ? { recovery: details.recovery } : {}),
152
+ ...(details.recovery !== undefined ? { recovery: details.recovery } : {}),
153
153
  };
154
154
  if (errorCode === "call_capture_failed")
155
155
  throw new McpScriptCaptureError(details.recovery, message);
156
- if (errorCode === "ambiguous_outcome")
156
+ if (details.recovery !== undefined)
157
157
  output.push({ type: "text", text: message });
158
158
  return {
159
159
  ok: false,
@@ -301,7 +301,7 @@ async function runMcpScriptOperation(state, code, timeoutMs = DEFAULT_MCP_SCRIPT
301
301
  reject(new Error(message.message));
302
302
  return;
303
303
  }
304
- void (async () => {
304
+ const operation = (async () => {
305
305
  let envelope;
306
306
  if (message.type === "call") {
307
307
  envelope = await callTool(message.id, message.path, message.args, message.server);
@@ -332,7 +332,9 @@ async function runMcpScriptOperation(state, code, timeoutMs = DEFAULT_MCP_SCRIPT
332
332
  }
333
333
  const response = { type: "result", id: message.id, envelope };
334
334
  activeWorker.postMessage(response);
335
- })().catch(reject);
335
+ })();
336
+ pendingOperations.add(operation);
337
+ void operation.catch(reject).finally(() => pendingOperations.delete(operation));
336
338
  });
337
339
  activeWorker.once("error", reject);
338
340
  activeWorker.once("exit", (code) => {
@@ -345,7 +347,6 @@ async function runMcpScriptOperation(state, code, timeoutMs = DEFAULT_MCP_SCRIPT
345
347
  return;
346
348
  const timeoutError = new McpScriptTimeoutError(resolvedTimeoutMs);
347
349
  timer = setTimeout(() => {
348
- callsSnapshot = snapshotCalls();
349
350
  timeoutController.abort(timeoutError);
350
351
  void activeWorker.terminate();
351
352
  reject(timeoutError);
@@ -354,7 +355,6 @@ async function runMcpScriptOperation(state, code, timeoutMs = DEFAULT_MCP_SCRIPT
354
355
  const aborted = externalSignal
355
356
  ? new Promise((_resolve, reject) => {
356
357
  const onAbort = () => {
357
- callsSnapshot = snapshotCalls();
358
358
  void activeWorker.terminate();
359
359
  reject(abortReasonError(externalSignal.reason));
360
360
  };
@@ -387,14 +387,15 @@ async function runMcpScriptOperation(state, code, timeoutMs = DEFAULT_MCP_SCRIPT
387
387
  finally {
388
388
  clearTimeout(timer);
389
389
  removeAbortListener();
390
- // "incomplete" means the call had not settled when the script finished
391
- // (deadline, abort, or early return). Snapshot before aborting stragglers.
392
- callsSnapshot ??= snapshotCalls();
390
+ worker?.removeAllListeners("message");
393
391
  // A script may finish without awaiting every call; abort leftovers so
394
392
  // parent-side dispatches do not outlive the script.
395
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);
396
396
  await worker?.terminate();
397
397
  }
398
+ const callsSnapshot = snapshotCalls();
398
399
  // Snapshot before the asynchronous output guard; the terminated worker can no longer emit.
399
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));
400
401
  return {
@@ -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;
@@ -94,11 +94,11 @@ export declare function readMcpResult(input: ReadMcpResultInput, options?: McpOu
94
94
  text: string;
95
95
  }[];
96
96
  details: {
97
- path?: string;
98
97
  ref: string;
99
98
  offset: number;
100
99
  nextOffset: number | null;
101
100
  totalCharacters: number;
101
+ path?: string;
102
102
  error?: never;
103
103
  message?: never;
104
104
  };
@@ -2,8 +2,6 @@ import { type PanelKeybindings } from "./panel-keys.ts";
2
2
  import type { McpConfig, McpPanelCallbacks, McpPanelResult, ServerProvenance } from "./types.ts";
3
3
  import { type MetadataCache } from "./metadata-cache.ts";
4
4
  declare class McpPanel {
5
- private callbacks;
6
- private done;
7
5
  private noticeLines;
8
6
  private prefix;
9
7
  private servers;
@@ -25,6 +23,8 @@ declare class McpPanel {
25
23
  private keys;
26
24
  private static readonly MAX_VISIBLE;
27
25
  private static readonly INACTIVITY_MS;
26
+ private callbacks;
27
+ private done;
28
28
  constructor(config: McpConfig, cache: MetadataCache | null, provenance: Map<string, ServerProvenance>, callbacks: McpPanelCallbacks, tui: {
29
29
  requestRender(): void;
30
30
  }, done: (result: McpPanelResult) => void, options?: {
package/dist/mcp-panel.js CHANGED
@@ -93,8 +93,6 @@ function estimateTokens(tool) {
93
93
  return Math.ceil((tool.name.length + descLen + schemaLen) / 4) + 10;
94
94
  }
95
95
  class McpPanel {
96
- callbacks;
97
- done;
98
96
  noticeLines;
99
97
  prefix;
100
98
  servers = [];
@@ -116,6 +114,8 @@ class McpPanel {
116
114
  keys;
117
115
  static MAX_VISIBLE = 12;
118
116
  static INACTIVITY_MS = 60_000;
117
+ callbacks;
118
+ done;
119
119
  constructor(config, cache, provenance, callbacks, tui, done, options = {}) {
120
120
  this.callbacks = callbacks;
121
121
  this.done = done;
@@ -31,10 +31,6 @@ export interface SetupPanelOptions {
31
31
  keybindings?: PanelKeybindings;
32
32
  }
33
33
  export declare class McpSetupPanel {
34
- private discovery;
35
- private callbacks;
36
- private options;
37
- private done;
38
34
  private screen;
39
35
  private actionCursor;
40
36
  private importCursor;
@@ -47,6 +43,10 @@ export declare class McpSetupPanel {
47
43
  private keys;
48
44
  private inactivityTimeout;
49
45
  private static readonly INACTIVITY_MS;
46
+ private discovery;
47
+ private callbacks;
48
+ private options;
49
+ private done;
50
50
  constructor(discovery: McpDiscoverySummary, callbacks: SetupPanelCallbacks, options: SetupPanelOptions, tui: {
51
51
  requestRender(): void;
52
52
  }, done: () => void);
@@ -72,6 +72,7 @@ export declare class McpSetupPanel {
72
72
  private previewWidth;
73
73
  private getActionPreview;
74
74
  private formatPreview;
75
+ private previewOrError;
75
76
  private formatWritePreview;
76
77
  private padLine;
77
78
  invalidate(): void;
@@ -38,10 +38,6 @@ function wrapText(text, width) {
38
38
  return lines.length > 0 ? lines : [""];
39
39
  }
40
40
  export class McpSetupPanel {
41
- discovery;
42
- callbacks;
43
- options;
44
- done;
45
41
  screen;
46
42
  actionCursor = 0;
47
43
  importCursor = 0;
@@ -54,6 +50,10 @@ export class McpSetupPanel {
54
50
  keys;
55
51
  inactivityTimeout = null;
56
52
  static INACTIVITY_MS = 60_000;
53
+ discovery;
54
+ callbacks;
55
+ options;
56
+ done;
57
57
  constructor(discovery, callbacks, options, tui, done) {
58
58
  this.discovery = discovery;
59
59
  this.callbacks = callbacks;
@@ -361,7 +361,7 @@ export class McpSetupPanel {
361
361
  lines.push(this.padLine(fg(this.t.muted, `… ${actions.length - end} more below`), innerW));
362
362
  }
363
363
  lines.push(this.padLine("", innerW));
364
- const preview = this.getActionPreview(this.getSelectedAction(), this.previewWidth(innerW));
364
+ const preview = this.previewOrError(width => this.getActionPreview(this.getSelectedAction(), width), innerW);
365
365
  for (const line of preview) {
366
366
  lines.push(this.padLine(line, innerW));
367
367
  }
@@ -384,8 +384,7 @@ export class McpSetupPanel {
384
384
  }
385
385
  lines.push(this.padLine("", innerW));
386
386
  const selected = this.discovery.imports.filter((entry) => this.selectedImports.has(entry.kind)).map((entry) => entry.kind);
387
- const preview = this.callbacks.previewImports(selected);
388
- for (const line of this.formatWritePreview("Compatibility import write preview", preview, [], this.previewWidth(innerW))) {
387
+ for (const line of this.previewOrError(width => this.formatWritePreview("Compatibility import write preview", this.callbacks.previewImports(selected), [], width), innerW)) {
389
388
  lines.push(this.padLine(line, innerW));
390
389
  }
391
390
  return lines;
@@ -522,6 +521,15 @@ export class McpSetupPanel {
522
521
  }
523
522
  return preview;
524
523
  }
524
+ previewOrError(preview, innerW) {
525
+ const width = this.previewWidth(innerW);
526
+ try {
527
+ return preview(width);
528
+ }
529
+ catch (error) {
530
+ return this.formatPreview([`Cannot preview write: ${error instanceof Error ? error.message : String(error)}`], width);
531
+ }
532
+ }
525
533
  formatWritePreview(title, preview, intro = [], width = DESKTOP_PREVIEW_WIDTH) {
526
534
  const lines = [];
527
535
  for (const line of intro) {
@@ -52,7 +52,6 @@ export declare function createMcpTraceEvent(direction: McpTraceDirection, server
52
52
  durationMs?: number;
53
53
  }): McpTraceEvent;
54
54
  export declare class McpTraceWriter {
55
- private readonly options;
56
55
  private readonly maxBytes;
57
56
  private readonly maxEvents;
58
57
  private readonly append;
@@ -65,6 +64,7 @@ export declare class McpTraceWriter {
65
64
  private initializationFailed;
66
65
  private persistenceError;
67
66
  private readonly fileReady;
67
+ private readonly options;
68
68
  constructor(options: McpTraceWriterOptions);
69
69
  get filePath(): string;
70
70
  get isDisabled(): boolean;
package/dist/mcp-trace.js CHANGED
@@ -73,7 +73,6 @@ export function createMcpTraceEvent(direction, server, transport, message, statu
73
73
  return event;
74
74
  }
75
75
  export class McpTraceWriter {
76
- options;
77
76
  maxBytes;
78
77
  maxEvents;
79
78
  append;
@@ -86,6 +85,7 @@ export class McpTraceWriter {
86
85
  initializationFailed = false;
87
86
  persistenceError;
88
87
  fileReady;
88
+ options;
89
89
  constructor(options) {
90
90
  this.options = options;
91
91
  this.maxBytes = boundedPositiveInteger(options.maxBytes, DEFAULT_MCP_TRACE_MAX_BYTES);
@@ -17,13 +17,15 @@ export async function resolveNpxBinary(command, args, signal) {
17
17
  if (!parsed)
18
18
  return null;
19
19
  const packageSpec = parsePackageSpec(parsed.packageSpec);
20
+ if (!packageSpec)
21
+ return null;
20
22
  const cacheKey = JSON.stringify([command, ...args]);
21
23
  const cache = loadCache();
22
24
  const cached = cache?.entries?.[cacheKey];
23
25
  if (cached
24
26
  && Date.now() - cached.resolvedAt < CACHE_TTL_MS
25
27
  && existsSync(cached.resolvedBin)
26
- && (!packageSpec?.exactVersion || cached.packageVersion === packageSpec.exactVersion)) {
28
+ && (!packageSpec.exactVersion || cached.packageVersion === packageSpec.exactVersion)) {
27
29
  return { binPath: cached.resolvedBin, extraArgs: parsed.extraArgs, isJs: cached.isJs };
28
30
  }
29
31
  const resolved = resolveFromNpmCache(parsed.packageSpec, parsed.binName);
@@ -289,11 +291,13 @@ function parsePackageSpec(spec) {
289
291
  if (!packageName)
290
292
  return null;
291
293
  const normalizedVersion = requestedVersion?.replace(/^=/, "").replace(/^v/i, "");
294
+ // Let npm resolve ranges and tags rather than choosing an arbitrary cached version.
295
+ if (requestedVersion !== undefined && (!normalizedVersion || !EXACT_PACKAGE_VERSION_RE.test(normalizedVersion))) {
296
+ return null;
297
+ }
292
298
  return {
293
299
  packageName,
294
- ...(normalizedVersion && EXACT_PACKAGE_VERSION_RE.test(normalizedVersion)
295
- ? { exactVersion: normalizedVersion }
296
- : {}),
300
+ ...(normalizedVersion ? { exactVersion: normalizedVersion } : {}),
297
301
  };
298
302
  }
299
303
  function defaultBinName(packageName) {
@@ -9,7 +9,7 @@ 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>;
11
11
  export declare function executeDescribe(state: McpExtensionState, toolName: string, serverFilter?: string): ProxyToolResult;
12
- export declare function executeSearch(state: McpExtensionState, query: string, server?: string, includeSchemas?: boolean, limit?: number, offset?: number): ProxyToolResult;
12
+ export declare function executeSearch(state: McpExtensionState, query: string, server?: string, includeSchemas?: boolean, limit?: number, offset?: number, caller?: "mcp" | "mcp_search"): ProxyToolResult;
13
13
  export declare function executeList(state: McpExtensionState, server: string, limit?: number, offset?: number): ProxyToolResult;
14
14
  export declare function executeInstructions(state: McpExtensionState, server: string): ProxyToolResult;
15
15
  export declare function executeConnect(state: McpExtensionState, serverName: string, signal?: AbortSignal, limit?: number, offset?: number): Promise<ProxyToolResult>;
@@ -63,10 +63,10 @@ export declare function prepareMcpConnection(state: McpExtensionState, serverNam
63
63
  recoverAuthConnection?: never;
64
64
  autoAuthAttempted?: never;
65
65
  } | {
66
+ error?: never;
66
67
  connection: ServerConnection;
67
68
  recoverAuthConnection: (serverName: string, signal?: AbortSignal, challenge?: import("./session-recovery.ts").AuthChallengeContext) => Promise<ServerConnection | undefined>;
68
69
  autoAuthAttempted: () => boolean;
69
- error?: never;
70
70
  }>;
71
71
  export declare function executeCall(state: McpExtensionState, toolName: string, args?: Record<string, unknown>, serverOverride?: string, getPiTools?: () => ToolInfo[], signal?: AbortSignal, identity?: McpToolCallIdentity, beforeDispatch?: (signal: AbortSignal | undefined, operation: McpOperationContext) => Promise<void>, options?: ExecuteCallOptions): Promise<ProxyToolResult>;
72
72
  export {};