@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.
Files changed (129) hide show
  1. package/CHANGELOG.md +50 -0
  2. package/OAUTH.md +3 -3
  3. package/README.md +160 -88
  4. package/dist/app-bridge.bundle.js +75 -33
  5. package/dist/commands.js +9 -6
  6. package/dist/config.js +41 -16
  7. package/dist/direct-tools.d.ts +2 -4
  8. package/dist/direct-tools.js +21 -319
  9. package/dist/gateway-arguments.d.ts +33 -0
  10. package/dist/gateway-arguments.js +63 -0
  11. package/dist/index.js +181 -276
  12. package/dist/init.js +6 -20
  13. package/dist/lifecycle.d.ts +2 -2
  14. package/dist/lifecycle.js +2 -2
  15. package/dist/mcp-auth-flow.js +2 -2
  16. package/dist/mcp-auth.d.ts +1 -1
  17. package/dist/mcp-auth.js +3 -3
  18. package/dist/mcp-code.d.ts +7 -6
  19. package/dist/mcp-code.js +98 -51
  20. package/dist/mcp-oauth-provider.d.ts +5 -5
  21. package/dist/mcp-oauth-provider.js +5 -5
  22. package/dist/mcp-output-guard.d.ts +57 -2
  23. package/dist/mcp-output-guard.js +129 -24
  24. package/dist/mcp-panel.d.ts +3 -3
  25. package/dist/mcp-panel.js +36 -99
  26. package/dist/mcp-script-worker.mjs +5 -2
  27. package/dist/mcp-setup-panel.d.ts +5 -4
  28. package/dist/mcp-setup-panel.js +15 -7
  29. package/dist/mcp-status.js +2 -1
  30. package/dist/mcp-trace.d.ts +1 -1
  31. package/dist/mcp-trace.js +1 -1
  32. package/dist/metadata-cache.d.ts +1 -1
  33. package/dist/metadata-cache.js +25 -88
  34. package/dist/npx-resolver.js +8 -4
  35. package/dist/prompts.d.ts +1 -1
  36. package/dist/prompts.js +1 -1
  37. package/dist/proxy-modes.d.ts +41 -7
  38. package/dist/proxy-modes.js +275 -490
  39. package/dist/resource-tools.d.ts +15 -0
  40. package/dist/resource-tools.js +26 -1
  41. package/dist/sampling-handler.js +5 -1
  42. package/dist/search-ranking.d.ts +0 -2
  43. package/dist/search-ranking.js +70 -137
  44. package/dist/server-manager.d.ts +2 -2
  45. package/dist/server-manager.js +44 -27
  46. package/dist/session-recovery.d.ts +7 -4
  47. package/dist/session-recovery.js +76 -10
  48. package/dist/state.d.ts +1 -1
  49. package/dist/tool-loader.d.ts +32 -0
  50. package/dist/tool-loader.js +152 -0
  51. package/dist/tool-metadata.d.ts +67 -0
  52. package/dist/tool-metadata.js +23 -17
  53. package/dist/tool-registrar.d.ts +4 -4
  54. package/dist/tool-registrar.js +18 -13
  55. package/dist/tool-result-renderer.d.ts +6 -3
  56. package/dist/tool-result-renderer.js +11 -6
  57. package/dist/types.d.ts +11 -36
  58. package/dist/ui-resource-handler.d.ts +2 -2
  59. package/dist/ui-resource-handler.js +2 -2
  60. package/dist/ui-server.js +4 -3
  61. package/dist/ui-stream-types.d.ts +8 -8
  62. package/dist/unix-socket-transport.d.ts +1 -1
  63. package/dist/unix-socket-transport.js +1 -1
  64. package/dist/utils.d.ts +1 -0
  65. package/dist/utils.js +16 -4
  66. package/package.json +37 -96
  67. package/skills/mcp-scripting/SKILL.md +67 -20
  68. package/abort.ts +0 -34
  69. package/agent-dir.ts +0 -24
  70. package/app-bridge.bundle.js +0 -67
  71. package/banner.png +0 -0
  72. package/checkpoint.ts +0 -55
  73. package/commands.ts +0 -619
  74. package/config.ts +0 -1204
  75. package/consent-manager.ts +0 -25
  76. package/direct-tools.ts +0 -408
  77. package/dist/ts-shape.d.ts +0 -2
  78. package/dist/ts-shape.js +0 -147
  79. package/elicitation-handler.ts +0 -349
  80. package/error-signal.ts +0 -21
  81. package/glimpse-ui.ts +0 -80
  82. package/host-html-template.ts +0 -456
  83. package/index.ts +0 -900
  84. package/init.ts +0 -627
  85. package/legacy-migration.ts +0 -47
  86. package/lifecycle.ts +0 -165
  87. package/logger.ts +0 -127
  88. package/mcp-auth-flow.ts +0 -975
  89. package/mcp-auth.ts +0 -793
  90. package/mcp-callback-server.ts +0 -450
  91. package/mcp-code.ts +0 -386
  92. package/mcp-keyring-helper.cjs +0 -89
  93. package/mcp-oauth-provider.ts +0 -774
  94. package/mcp-output-guard.ts +0 -412
  95. package/mcp-panel.ts +0 -978
  96. package/mcp-script-worker.mjs +0 -128
  97. package/mcp-setup-panel.ts +0 -666
  98. package/mcp-status.ts +0 -109
  99. package/mcp-trace.ts +0 -295
  100. package/metadata-cache.ts +0 -333
  101. package/npx-resolver.ts +0 -488
  102. package/onboarding-state.ts +0 -58
  103. package/panel-keys.ts +0 -37
  104. package/prompts.ts +0 -337
  105. package/proxy-modes.ts +0 -1361
  106. package/resource-tools.ts +0 -17
  107. package/runtime-owner.ts +0 -123
  108. package/sampling-handler.ts +0 -273
  109. package/scripts/build.mjs +0 -131
  110. package/scripts/prepare.mjs +0 -102
  111. package/search-ranking.ts +0 -196
  112. package/server-manager.ts +0 -1320
  113. package/session-recovery.ts +0 -316
  114. package/state.ts +0 -64
  115. package/tool-approval.ts +0 -78
  116. package/tool-metadata.ts +0 -235
  117. package/tool-registrar.ts +0 -69
  118. package/tool-result-renderer.ts +0 -286
  119. package/ts-shape.ts +0 -145
  120. package/tsconfig.build.json +0 -13
  121. package/tsconfig.json +0 -16
  122. package/types.ts +0 -810
  123. package/ui-resource-handler.ts +0 -249
  124. package/ui-server.ts +0 -808
  125. package/ui-session.ts +0 -568
  126. package/ui-stream-types.ts +0 -89
  127. package/ui-tool-visibility.ts +0 -25
  128. package/unix-socket-transport.ts +0 -85
  129. package/utils.ts +0 -309
package/dist/types.d.ts CHANGED
@@ -13,6 +13,8 @@ export interface McpServerStatusSnapshot {
13
13
  readonly name: string;
14
14
  readonly status: McpServerRuntimeStatus;
15
15
  readonly toolCount: number;
16
+ /** False means the server has not been discovered, rather than an empty inventory. */
17
+ readonly catalogKnown?: boolean;
16
18
  readonly resourceCount?: number;
17
19
  readonly failedAgoSeconds?: number;
18
20
  readonly disabled: boolean;
@@ -30,21 +32,10 @@ type SdkTool = ListToolsResult["tools"][number];
30
32
  type SdkResource = ListResourcesResult["resources"][number];
31
33
  type SdkPrompt = ListPromptsResult["prompts"][number];
32
34
  type SdkPromptArgument = NonNullable<SdkPrompt["arguments"]>[number];
33
- export interface McpTool {
34
- name: SdkTool["name"];
35
- title?: SdkTool["title"];
36
- description?: SdkTool["description"];
35
+ export interface McpTool extends Omit<SdkTool, "inputSchema"> {
37
36
  inputSchema?: SdkTool["inputSchema"];
38
- annotations?: SdkTool["annotations"];
39
- _meta?: SdkTool["_meta"];
40
- }
41
- export interface McpResource {
42
- uri: SdkResource["uri"];
43
- name: SdkResource["name"];
44
- description?: SdkResource["description"];
45
- mimeType?: SdkResource["mimeType"];
46
- _meta?: SdkResource["_meta"];
47
37
  }
38
+ export type McpResource = SdkResource;
48
39
  export interface McpPromptArgument {
49
40
  name: SdkPromptArgument["name"];
50
41
  description?: SdkPromptArgument["description"];
@@ -327,7 +318,7 @@ export interface McpSettings {
327
318
  /** Freeze direct-tool registration after the initial sync. Automatic metadata updates
328
319
  * (reconnects, lazy-connect, tool-list-changed) won't rebuild the system prompt,
329
320
  * preserving the prompt-cache prefix. The agent rediscovers explicitly via
330
- * mcp({ connect: "server" }). Default: false. */
321
+ * mcp({ action: "connect", server: "server" }). Default: false. */
331
322
  freezeDirectTools?: boolean;
332
323
  autoAuth?: boolean;
333
324
  sampling?: boolean;
@@ -411,15 +402,14 @@ export interface McpAdapterOptions {
411
402
  onToolCall?: (event: McpToolCallEvent) => Promise<void>;
412
403
  }
413
404
  export type ServerDefinition = ServerEntry;
414
- export interface ToolMetadata {
405
+ export interface ToolMetadata extends Omit<McpTool, "name" | "description"> {
415
406
  name: string;
416
407
  originalName: string;
417
408
  description: string;
418
409
  resourceUri?: string;
410
+ resourceDescriptor?: McpResource;
419
411
  uiResourceUri?: string;
420
412
  uiVisibility?: UiToolVisibility[];
421
- inputSchema?: unknown;
422
- annotations?: McpTool["annotations"];
423
413
  uiStreamMode?: UiStreamMode;
424
414
  }
425
415
  export interface PromptMetadata {
@@ -430,16 +420,9 @@ export interface PromptMetadata {
430
420
  description: string;
431
421
  arguments: McpPromptArgument[];
432
422
  }
433
- export interface DirectToolSpec {
423
+ export interface DirectToolSpec extends Omit<ToolMetadata, "name"> {
434
424
  serverName: string;
435
- originalName: string;
436
425
  prefixedName: string;
437
- description: string;
438
- inputSchema?: unknown;
439
- annotations?: McpTool["annotations"];
440
- resourceUri?: string;
441
- uiResourceUri?: string;
442
- uiStreamMode?: UiStreamMode;
443
426
  }
444
427
  export interface ServerProvenance {
445
428
  path: string;
@@ -450,20 +433,12 @@ export interface McpAuthResult {
450
433
  ok: boolean;
451
434
  message?: string;
452
435
  }
453
- export interface CachedTool {
454
- name: string;
455
- description?: string;
456
- inputSchema?: unknown;
457
- annotations?: McpTool["annotations"];
436
+ export interface CachedTool extends McpTool {
458
437
  uiResourceUri?: string;
459
438
  uiVisibility?: UiToolVisibility[];
460
- uiStreamMode?: "eager" | "stream-first";
461
- }
462
- export interface CachedResource {
463
- uri: string;
464
- name: string;
465
- description?: string;
439
+ uiStreamMode?: UiStreamMode;
466
440
  }
441
+ export type CachedResource = McpResource;
467
442
  export interface CachedPrompt {
468
443
  name: string;
469
444
  title?: string;
@@ -7,10 +7,10 @@ interface ReadUiResourceOptions {
7
7
  onNeedsAuth?: SessionRecoveryDeps["onNeedsAuth"];
8
8
  }
9
9
  export declare class UiResourceHandler {
10
+ private log;
10
11
  private manager;
11
12
  private config;
12
- private log;
13
- constructor(manager: McpServerManager, config?: McpConfig | undefined);
13
+ constructor(manager: McpServerManager, config?: McpConfig);
14
14
  readUiResource(serverName: string, uri: string, options?: ReadUiResourceOptions): Promise<UiResourceContent>;
15
15
  private getListResourceMeta;
16
16
  }
@@ -4,10 +4,10 @@ import { logger } from "./logger.js";
4
4
  import { SessionRecoveryAuthRequiredError, withSessionRecovery } from "./session-recovery.js";
5
5
  import { isServerDisabled } from "./types.js";
6
6
  export class UiResourceHandler {
7
+ log = logger.child({ component: "UiResourceHandler" });
7
8
  manager;
8
9
  config;
9
- log = logger.child({ component: "UiResourceHandler" });
10
- constructor(manager, config = undefined) {
10
+ constructor(manager, config) {
11
11
  this.manager = manager;
12
12
  this.config = config;
13
13
  }
package/dist/ui-server.js CHANGED
@@ -1,4 +1,4 @@
1
- import http from "node:http";
1
+ import http, {} from "node:http";
2
2
  import fs from "node:fs/promises";
3
3
  import path from "node:path";
4
4
  import { randomUUID } from "node:crypto";
@@ -10,7 +10,7 @@ import { logger } from "./logger.js";
10
10
  import { SessionRecoveryAuthRequiredError, withSessionRecovery } from "./session-recovery.js";
11
11
  import { ensureToolCallApproved, isToolCallApprovalRequired } from "./tool-approval.js";
12
12
  import { extractUiToolVisibility, isUiToolCallableByApp } from "./ui-tool-visibility.js";
13
- import { createUiModelContextUpdate, extractUiPromptText, getVisualizationStreamEnvelope, isServerDisabled, } from "./types.js";
13
+ import { createUiModelContextUpdate, extractUiPromptText, getVisualizationStreamEnvelope, isServerDisabled, isToolAllowed, resolveToolPrefix, } from "./types.js";
14
14
  const MAX_BODY_SIZE = 2 * 1024 * 1024;
15
15
  const ABANDONED_GRACE_MS = 60_000;
16
16
  const WATCHDOG_INTERVAL_MS = 5_000;
@@ -299,9 +299,10 @@ export async function startUiServer(options) {
299
299
  sendJson(res, 503, { ok: false, error: `Server "${options.serverName}" is disabled` });
300
300
  return;
301
301
  }
302
+ const definition = options.config?.mcpServers[options.serverName] ?? connection.definition;
302
303
  const toolDefinitions = Array.isArray(connection.tools) ? connection.tools : [];
303
304
  const toolDefinition = toolDefinitions.find((tool) => tool.name === callParams.name);
304
- if (!toolDefinition) {
305
+ if (!toolDefinition || !isToolAllowed(callParams.name, options.serverName, resolveToolPrefix(definition, options.config?.settings?.toolPrefix), definition?.includeTools, definition?.excludeTools)) {
305
306
  sendJson(res, 403, { ok: false, error: `MCP tool "${callParams.name}" is not callable by apps` });
306
307
  return;
307
308
  }
@@ -10,17 +10,17 @@ export declare const uiStreamModeSchema: z.ZodEnum<{
10
10
  }>;
11
11
  export type UiStreamMode = z.infer<typeof uiStreamModeSchema>;
12
12
  export declare const visualizationStreamPhaseSchema: z.ZodEnum<{
13
- shell: "shell";
14
- narrative: "narrative";
15
- structure: "structure";
16
13
  detail: "detail";
14
+ narrative: "narrative";
17
15
  settled: "settled";
16
+ shell: "shell";
17
+ structure: "structure";
18
18
  }>;
19
19
  export type VisualizationStreamPhase = z.infer<typeof visualizationStreamPhaseSchema>;
20
20
  export declare const visualizationStreamFrameTypeSchema: z.ZodEnum<{
21
- patch: "patch";
22
21
  checkpoint: "checkpoint";
23
22
  final: "final";
23
+ patch: "patch";
24
24
  }>;
25
25
  export type VisualizationStreamFrameType = z.infer<typeof visualizationStreamFrameTypeSchema>;
26
26
  export declare const visualizationStreamStatusSchema: z.ZodEnum<{
@@ -42,16 +42,16 @@ export declare const visualizationStreamEnvelopeSchema: z.ZodObject<{
42
42
  streamId: z.ZodString;
43
43
  sequence: z.ZodNumber;
44
44
  frameType: z.ZodEnum<{
45
- patch: "patch";
46
45
  checkpoint: "checkpoint";
47
46
  final: "final";
47
+ patch: "patch";
48
48
  }>;
49
49
  phase: z.ZodEnum<{
50
- shell: "shell";
51
- narrative: "narrative";
52
- structure: "structure";
53
50
  detail: "detail";
51
+ narrative: "narrative";
54
52
  settled: "settled";
53
+ shell: "shell";
54
+ structure: "structure";
55
55
  }>;
56
56
  status: z.ZodEnum<{
57
57
  error: "error";
@@ -2,12 +2,12 @@ import type { Transport } from "@modelcontextprotocol/client";
2
2
  import type { JSONRPCMessage } from "@modelcontextprotocol/client";
3
3
  /** MCP JSONL transport for an explicitly configured Unix-domain socket. */
4
4
  export declare class UnixSocketClientTransport implements Transport {
5
- private readonly socketPath;
6
5
  private socket;
7
6
  private readonly readBuffer;
8
7
  onclose?: () => void;
9
8
  onerror?: (error: Error) => void;
10
9
  onmessage?: (message: JSONRPCMessage) => void;
10
+ private readonly socketPath;
11
11
  constructor(socketPath: string);
12
12
  start(): Promise<void>;
13
13
  close(): Promise<void>;
@@ -2,12 +2,12 @@ import { createConnection } from "node:net";
2
2
  import { ReadBuffer, serializeMessage } from "@modelcontextprotocol/client";
3
3
  /** MCP JSONL transport for an explicitly configured Unix-domain socket. */
4
4
  export class UnixSocketClientTransport {
5
- socketPath;
6
5
  socket;
7
6
  readBuffer = new ReadBuffer();
8
7
  onclose;
9
8
  onerror;
10
9
  onmessage;
10
+ socketPath;
11
11
  constructor(socketPath) {
12
12
  this.socketPath = socketPath;
13
13
  }
package/dist/utils.d.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
2
2
  import type { McpConfig, ServerEntry } from "./types.ts";
3
+ export declare const ADAPTER_VERSION: string;
3
4
  export declare function normalizeRequestTimeoutMs(timeoutMs: number | undefined): number | undefined;
4
5
  export declare function openUrl(pi: ExtensionAPI, url: string, browser?: string, signal?: AbortSignal): Promise<void>;
5
6
  export declare function openPath(pi: ExtensionAPI, targetPath: string): Promise<void>;
package/dist/utils.js CHANGED
@@ -1,6 +1,20 @@
1
1
  import { spawnSync } from "node:child_process";
2
+ import { readFileSync } from "node:fs";
2
3
  import { homedir, platform } from "node:os";
3
4
  import { join } from "node:path";
5
+ function readAdapterVersion() {
6
+ // Source modules sit beside package.json; compiled modules sit one level down in dist/.
7
+ for (const path of ["./package.json", "../package.json"]) {
8
+ try {
9
+ const manifest = JSON.parse(readFileSync(new URL(path, import.meta.url), "utf8"));
10
+ if (manifest.name === "@fitchmultz/pi-mcp-adapter" && typeof manifest.version === "string")
11
+ return manifest.version;
12
+ }
13
+ catch { }
14
+ }
15
+ return "0.0.0";
16
+ }
17
+ export const ADAPTER_VERSION = readAdapterVersion();
4
18
  export function normalizeRequestTimeoutMs(timeoutMs) {
5
19
  return typeof timeoutMs === "number" && Number.isFinite(timeoutMs) && timeoutMs > 0
6
20
  ? timeoutMs
@@ -246,11 +260,9 @@ export function truncateAtWord(text, target) {
246
260
  return truncated + "...";
247
261
  }
248
262
  export function normalizeDirectToolInputSchema(schema) {
249
- const inputSchema = schema && typeof schema === "object" && !Array.isArray(schema)
250
- ? schema
263
+ return schema && typeof schema === "object" && !Array.isArray(schema)
264
+ ? structuredClone(schema)
251
265
  : { type: "object", properties: {} };
252
- const { $schema, additionalProperties, ...normalized } = inputSchema;
253
- return normalized;
254
266
  }
255
267
  export function formatAuthRequiredMessage(config, serverName, defaultMessage) {
256
268
  const template = config.settings?.authRequiredMessage;
package/package.json CHANGED
@@ -1,42 +1,44 @@
1
1
  {
2
2
  "name": "@fitchmultz/pi-mcp-adapter",
3
- "version": "5.0.0",
3
+ "version": "7.0.0",
4
4
  "description": "Fitch's independently maintained MCP adapter for Pi coding agent",
5
5
  "type": "module",
6
6
  "types": "./dist/index.d.ts",
7
7
  "exports": {
8
8
  ".": {
9
9
  "types": "./dist/index.d.ts",
10
- "import": "./index.ts",
11
- "default": "./index.ts"
10
+ "default": "./dist/index.js"
12
11
  },
13
12
  "./types": {
14
13
  "types": "./dist/types.d.ts",
15
- "import": "./types.ts",
16
- "default": "./types.ts"
14
+ "default": "./dist/types.js"
17
15
  }
18
16
  },
19
17
  "license": "MIT",
20
18
  "author": "Mitch Fultz",
21
- "contributors": ["Nico Bailon"],
19
+ "contributors": [
20
+ "Nico Bailon"
21
+ ],
22
22
  "homepage": "https://github.com/fitchmultz/pi-mcp-adapter",
23
23
  "bugs": "https://github.com/fitchmultz/pi-mcp-adapter/issues",
24
- "publishConfig": { "access": "public" },
24
+ "publishConfig": {
25
+ "access": "public"
26
+ },
25
27
  "engines": {
26
- "node": ">=22.19.0"
28
+ "node": ">=24.0.0"
27
29
  },
28
30
  "bin": {
29
31
  "fitch-mcp-adapter": "cli.js"
30
32
  },
31
33
  "scripts": {
32
34
  "build": "node ./scripts/build.mjs",
35
+ "build:bridge": "node ./scripts/build-app-bridge.mjs",
33
36
  "prepare": "node ./scripts/prepare.mjs",
37
+ "check:compat": "node scripts/compat-host.mjs && npm run build && npm run typecheck && vitest run --exclude __tests__/interactive-visualizer-server.test.ts && npm run test:oauth && node scripts/compat-package-smoke.mjs",
34
38
  "typecheck": "tsc --noEmit",
35
- "test": "vitest run",
39
+ "test": "vitest run && npm run test:oauth",
36
40
  "test:watch": "vitest",
37
- "test:coverage": "vitest run --coverage",
38
- "test:oauth": "PI_MCP_ADAPTER_TEST_AUTH_STORE=memory node --import tsx --test --test-concurrency=1 mcp-auth.test.ts mcp-auth-flow.test.ts mcp-callback-server.test.ts mcp-oauth-provider.test.ts",
39
- "test:oauth-provider": "PI_MCP_ADAPTER_TEST_AUTH_STORE=memory node --import tsx --test mcp-oauth-provider.test.ts",
41
+ "test:oauth": "PI_MCP_ADAPTER_TEST_AUTH_STORE=memory node --test --test-concurrency=1 mcp-auth.test.ts mcp-auth-flow.test.ts mcp-callback-server.test.ts mcp-oauth-provider.test.ts",
40
42
  "test:conformance": "bash conformance/run.sh"
41
43
  },
42
44
  "repository": {
@@ -65,92 +67,31 @@
65
67
  },
66
68
  "files": [
67
69
  "dist",
68
- "docs/client-metadata.json",
69
- "scripts/build.mjs",
70
- "scripts/prepare.mjs",
71
- "tsconfig.build.json",
72
- "tsconfig.json",
73
70
  "cli.js",
74
- "agent-dir.ts",
75
- "legacy-migration.ts",
76
- "index.ts",
77
- "error-signal.ts",
78
- "state.ts",
79
- "utils.ts",
80
- "abort.ts",
81
- "runtime-owner.ts",
82
- "checkpoint.ts",
83
- "tool-metadata.ts",
84
- "ts-shape.ts",
85
- "search-ranking.ts",
86
- "tool-approval.ts",
87
- "init.ts",
88
- "ui-session.ts",
89
- "proxy-modes.ts",
90
- "direct-tools.ts",
91
- "commands.ts",
92
- "prompts.ts",
93
- "onboarding-state.ts",
94
- "mcp-setup-panel.ts",
95
- "types.ts",
96
- "ui-stream-types.ts",
97
- "ui-tool-visibility.ts",
98
- "config.ts",
99
- "server-manager.ts",
100
- "unix-socket-transport.ts",
101
- "session-recovery.ts",
102
- "sampling-handler.ts",
103
- "elicitation-handler.ts",
104
- "tool-registrar.ts",
105
- "tool-result-renderer.ts",
106
- "mcp-output-guard.ts",
107
- "mcp-code.ts",
108
- "mcp-script-worker.mjs",
109
- "resource-tools.ts",
110
- "lifecycle.ts",
111
- "mcp-status.ts",
112
- "metadata-cache.ts",
113
- "host-html-template.ts",
114
- "ui-resource-handler.ts",
115
- "consent-manager.ts",
116
- "ui-server.ts",
117
- "glimpse-ui.ts",
118
- "npx-resolver.ts",
119
- "mcp-auth.ts",
120
- "mcp-keyring-helper.cjs",
121
- "mcp-oauth-provider.ts",
122
- "mcp-callback-server.ts",
123
- "mcp-auth-flow.ts",
124
- "mcp-panel.ts",
125
- "panel-keys.ts",
126
- "mcp-trace.ts",
127
- "logger.ts",
128
- "app-bridge.bundle.js",
129
- "banner.png",
130
71
  "skills",
72
+ "docs/client-metadata.json",
131
73
  "README.md",
132
74
  "OAUTH.md",
133
75
  "CHANGELOG.md",
134
76
  "LICENSE"
135
77
  ],
136
78
  "dependencies": {
137
- "@modelcontextprotocol/client": "2.0.0",
138
- "@modelcontextprotocol/core": "2.0.0",
139
- "@modelcontextprotocol/ext-apps": "^1.7.5",
140
- "@modelcontextprotocol/sdk": "^1.30.0",
141
- "@napi-rs/keyring": "^1.3.0",
142
- "cross-spawn": "^7.0.6",
143
- "open": "^10.2.0",
144
- "smol-toml": "^1.8.0",
145
- "strip-json-comments": "^5.0.3",
146
- "zod": "^3.25.0 || ^4.0.0"
79
+ "@modelcontextprotocol/client": "2.1.0",
80
+ "@modelcontextprotocol/core": "2.1.0",
81
+ "@modelcontextprotocol/ext-apps": "2.0.0",
82
+ "@napi-rs/keyring": "2.1.0",
83
+ "cross-spawn": "7.0.6",
84
+ "minisearch": "7.2.0",
85
+ "open": "11.0.4",
86
+ "smol-toml": "1.9.0",
87
+ "strip-json-comments": "5.0.3",
88
+ "zod": "4.6.5"
147
89
  },
148
90
  "peerDependencies": {
149
91
  "@earendil-works/pi-ai": "*",
150
92
  "@earendil-works/pi-coding-agent": "*",
151
93
  "@earendil-works/pi-tui": "*",
152
- "typebox": "*",
153
- "zod": "^3.25.0 || ^4.0.0"
94
+ "typebox": "*"
154
95
  },
155
96
  "peerDependenciesMeta": {
156
97
  "@earendil-works/pi-ai": {
@@ -167,16 +108,16 @@
167
108
  }
168
109
  },
169
110
  "devDependencies": {
170
- "@earendil-works/pi-ai": "0.84.0",
171
- "@earendil-works/pi-coding-agent": "0.84.0",
172
- "@earendil-works/pi-tui": "0.84.0",
173
- "@modelcontextprotocol/conformance": "0.1.16",
174
- "@modelcontextprotocol/server": "2.0.0",
111
+ "@earendil-works/pi-ai": "0.87.1",
112
+ "@earendil-works/pi-coding-agent": "0.87.1",
113
+ "@earendil-works/pi-tui": "0.87.1",
114
+ "@modelcontextprotocol/server": "2.1.0",
175
115
  "@types/cross-spawn": "6.0.6",
176
- "@types/node": "20.19.43",
177
- "tsx": "^4.21.0",
178
- "typebox": "1.3.7",
179
- "typescript": "5.9.3",
180
- "vitest": "^3.0.0"
181
- }
116
+ "@types/node": "24.19.0",
117
+ "esbuild": "0.28.2",
118
+ "typebox": "1.3.34",
119
+ "typescript": "7.0.2",
120
+ "vitest": "5.0.2"
121
+ },
122
+ "packageManager": "npm@12.1.0"
182
123
  }
@@ -1,38 +1,85 @@
1
1
  ---
2
2
  name: mcp-scripting
3
- description: Write mcp_script JavaScript for discovering, inspecting, and calling MCP tools.
3
+ description: Write mcp_script JavaScript for discovering, inspecting, and calling MCP tools, resources, and saved results.
4
4
  ---
5
5
 
6
6
  # MCP scripting
7
7
 
8
- For multi-call MCP work, write ordinary JavaScript with loops, filtering, chaining, fan-out, or other logic between calls. Run that source with `mcp_script`; it is the primary MCP orchestration surface. For a single MCP search, describe, status check, auth action, or tool call, use `mcp` instead.
8
+ Use `mcp_script` for MCP calls with loops, filtering, chaining, fan-out, or other logic between calls. Pass ordinary JavaScript as its `code` argument. For a single call, use an already loaded typed tool. Use `mcp_search({ query, server?, limit?, offset? })` to discover and load typed tools for the next request, or explicit `mcp` actions for gateway-only workflows. Discovery never executes a search hit.
9
9
 
10
- Write the source naturally, then pass it as `mcp_script`'s `code` argument:
10
+ ## Discover, inspect, then call
11
11
 
12
12
  ```js
13
- const { items } = await tools.search({ query: "search issues", server: "github" });
14
- const candidate = items[0];
15
- if (!candidate) return { error: "No matching tool" };
13
+ const found = await tools.search({ query: "search issues", server: "github" });
14
+ if (found.error) return found;
15
+ emit(found.coverage);
16
+ for (const item of found.items) {
17
+ emit(await tools.describe({ path: item.path, server: item.server }));
18
+ }
19
+ ```
20
+
21
+ Read the returned descriptors and choose the tool that matches the task. Do not call the first ranked hit without checking its identity and schema. In a subsequent script, use its exact returned path and schema-correct arguments:
22
+
23
+ ```js
24
+ const results = [];
25
+ for (const query of ["is:open label:bug", "is:open label:docs"]) {
26
+ const result = await tools.call("github_search_issues", { query });
27
+ if (!result.ok) return result;
28
+ results.push({ query, data: result.data });
29
+ }
30
+ return results;
31
+ ```
32
+
33
+ ## API and return shapes
34
+
35
+ - `await tools.search({ query, server?, limit?, offset? })` returns `{ items, total, hasMore, nextOffset, coverage }`. Items carry `path`, `name`, `server`, optional `description`, and `score`. Discovery failures return `{ error, coverage }`. Script search defaults to **12** items, maximum **100**, with zero-based item offsets. The separate `mcp_search` loader defaults to **5**.
36
+ - `coverage` is `{ complete, knownServers, unknownServers }`. Global search covers known catalogs without starting every lazy server. Select an uncached server to discover it. Script search does not activate typed tools.
37
+ - `await tools.describe({ path, server? })` returns the complete tool descriptor with `path`, `server`, original `name`, and all server-supplied fields, including `inputSchema`, `outputSchema`, `title`, `annotations`, and `_meta` when present. It never substitutes `inputTypeScript`. Failures return `{ path, error: { code, message, suggestions? } }`. Supply `server` to disambiguate a name.
38
+ - `await tools.call(path, args, server?)` returns `{ ok: true, data, resultRef? }` or `{ ok: false, error: { code, message, ... }, data?, resultRef? }`. `data` is the **raw MCP result**, including raw error-result data when available. Tools usually return `{ content, structuredContent?, isError?, ... }`; resource reads return `{ contents, ... }`.
39
+ - If a flat path is ambiguous, call `tools.call(descriptor.name, args, descriptor.server)` with the exact original name and server returned by describe.
40
+ - `await tools.resources({ server, limit?, offset? })` returns `{ mode: "resources", server, items, total, hasMore, nextOffset }`, or `{ error: { code, message } }`. Items preserve resource descriptors, including the exact `uri`, name, title, MIME type, description, icons, and `_meta` when supplied; pages default to 12, maximum 100.
41
+ - `await tools.readResource({ server, uri })` returns the same call envelope as `tools.call`, with raw resource contents in `data.contents`. Resources are separate from typed functions. Legacy `read_<resource>` call aliases remain compatible, but prefer this URI-based API.
42
+ - `await tools.readResult({ ref, path?, fields?, offset?, limit? })` returns `{ content, details }`, not a call envelope. `content` contains readback text; `details` contains `ref`, `offset`, `nextOffset`, `totalCharacters`, and the selected `path` when supplied. Failures contain `details.error` and `details.message`.
43
+ - `emit(value)` adds model-visible output before the final `return`; `console` output is captured too.
44
+
45
+ Treat `structuredContent` as structured data even alongside text or image blocks. Only parse a text block as JSON when the server documents that format. Handle `ok: false`: ordinary failed calls do not stop a script automatically. A call-capture failure stops dependent work. An ambiguous outcome requires provider readback before considering another write; never blindly repeat a call or rerun its whole script.
16
46
 
17
- const details = await tools.describe({ path: candidate.path });
18
- if (details.error) return details;
47
+ ## Saved-result readback
19
48
 
20
- const result = await tools.call(details.path, { query: "is:open label:bug" });
21
- if (!result.ok) return result;
22
- emit({ tool: details.path, completed: true });
23
- return result.data?.structuredContent ?? result.data;
49
+ Structured, media, resource, oversized, and extra-field results retain a raw artifact with a model-visible result reference. Small plain-text-only results stay inline. To inspect an existing reference without another MCP call:
50
+
51
+ ```js
52
+ const page = await tools.readResult({
53
+ ref: "/returned/result/path",
54
+ path: "/structuredContent/rows",
55
+ fields: ["id", "title"],
56
+ offset: 0,
57
+ limit: 12000,
58
+ });
59
+ return page;
60
+ ```
61
+
62
+ `path` is an RFC 6901 JSON Pointer. `fields` retains immediate keys on the selected object or every object in a selected array. Selection precedes paging. `offset` and `limit` are **zero-based character positions in the selected, pretty-printed JSON**, not rows, lines, or bytes. The default limit is 12,000 characters; output byte/line caps still apply. Continue with `details.nextOffset` until it is `null`. References must identify existing adapter artifacts in the host's output directory. Readback never re-calls the server and is not a general filesystem API.
63
+
64
+ Gateway equivalents require explicit actions:
65
+
66
+ ```js
67
+ mcp({ action: "search", query: "search issues", server: "github" })
68
+ mcp({ action: "describe", tool: "github_search_issues" })
69
+ mcp({ action: "call", tool: "github_search_issues", args: { query: "is:open" } })
70
+ mcp({ action: "resources", server: "docs" })
71
+ mcp({ action: "read-resource", server: "docs", uri: "docs://guide" })
72
+ mcp({ action: "read-result", ref: "/returned/result/path", path: "/structuredContent" })
24
73
  ```
25
74
 
26
- ## Workflow
75
+ Gateway search returns schemas without activating functions; use it when only `mcp` is allowed. New gateway calls use object `args`. Optional v5 mode fields and JSON-string arguments are only a stored-call ingress compatibility path.
27
76
 
28
- 1. Find candidate tools with `await tools.search({ query, server?, limit?, offset? })`. `limit` defaults to 12 and is capped at 100.
29
- 2. Inspect the exact returned path with `await tools.describe({ path })`. Arguments are described by `inputTypeScript` when representable, otherwise by the original JSON Schema in `inputSchema`; only one is included.
30
- 3. Call it with `tools.call(path, args)`.
77
+ ## Execution rules
31
78
 
32
- Calls resolve to `{ ok: true, data }` or `{ ok: false, error }`; handle failed calls instead of expecting them to stop the script. Successful `data` is the raw MCP result: tool calls usually return `{ content, structuredContent? }`, while resource reads return `{ contents }`. Prefer `structuredContent` when present, and only parse a text block when that server documents JSON text. `emit(value)` adds user-visible output before the final `return` value. `console` output is captured too.
79
+ `tools` is a non-enumerable proxy: `Object.keys(tools)` throws. Use `tools.search` for discovery. Known flat paths support `tools.github_search_issues(args)` or `tools["server_tool-name"](args)`. Proxy API and promise/serialization names are reserved (`search`, `describe`, `call`, `resources`, `listResources`, `readResource`, `readResult`, `then`, `catch`, `finally`, `toJSON`, `toString`, `valueOf`); use `tools.call("exact-path", args)` for collisions.
33
80
 
34
- `tools` is a non-enumerable proxy: `Object.keys(tools)` throws. Always use `tools.search` for discovery. When a known flat path is a valid identifier, direct calls such as `tools.github_search_issues(args)` are supported; use bracket syntax for hyphenated names: `tools["server_tool-name"](args)`. `search`, `call`, `describe`, and promise/serialization names (`then`, `catch`, `finally`, `toJSON`, `toString`, `valueOf`) are reserved on the proxy; if a flat path collides with one, call it via `tools.call("exact-path", args)`.
81
+ Await every operation, including all promises started with `Promise.all`, before returning. Calls use the shared authentication, approval, filtering, cancellation, and capture path. Resources honor `exposeResources` and include/exclude policy. Raw results are cloned into the worker before model-facing rendering; reduce them before emitting to avoid unnecessary context. Result details include a concise search/describe/call trace and saved-result references.
35
82
 
36
- `tools.search` and `tools.describe` are asynchronous and must be awaited. The default script timeout is 30 seconds unless the SDK host overrides or disables it (see the registered `timeoutMs` description); explicit per-script timeouts still apply, and the worker is terminated on timeout or cancellation, including for infinite loops. Every invocation still uses normal lazy connection, authentication, output guarding, and approval gates. Raw call results are cloned into the worker so scripts can reduce them before the model-facing guard; request scoped data instead of unnecessarily large payloads. Result details contain a concise `calls` trace with every search, describe, and call operation; each entry includes its query or path, outcome, and duration.
83
+ The default overall script timeout is 30 seconds unless the host overrides or disables it; an explicit per-script `timeoutMs` wins. Individual MCP request deadlines still apply. The local worker terminates on timeout or cancellation, including infinite loops. This is trusted agent-authored local orchestration, not provider-native programmatic tool calling or an isolation boundary; Codex native PTC is not implied.
37
84
 
38
- Use plain JavaScript loops and Promise utilities for composition. The sandbox has no Node, filesystem, or network globals such as `process`, `Buffer`, or `fetch`. Fluent helpers such as `tools.find(...).one()`, `tools.parallel(...)`, and `tools.retry(...)` are not provided.
85
+ The worker exposes no Node, filesystem, or network globals such as `process`, `Buffer`, or `fetch`. Use normal JavaScript loops and Promise utilities; fluent helpers such as `tools.find(...).one()`, `tools.parallel(...)`, and `tools.retry(...)` do not exist. Run Pi in an isolated environment if you need isolation.
package/abort.ts DELETED
@@ -1,34 +0,0 @@
1
- export function throwIfAborted(signal?: AbortSignal): void {
2
- if (!signal?.aborted) return;
3
- throw signal.reason instanceof Error ? signal.reason : new Error(String(signal.reason ?? "MCP request aborted"));
4
- }
5
-
6
- export async function abortable<T>(promise: Promise<T>, signal?: AbortSignal): Promise<T> {
7
- if (!signal) return promise;
8
- throwIfAborted(signal);
9
- return await new Promise<T>((resolve, reject) => {
10
- let settled = false;
11
- const cleanup = () => signal.removeEventListener("abort", onAbort);
12
- const onAbort = () => {
13
- if (settled) return;
14
- settled = true;
15
- cleanup();
16
- reject(signal.reason instanceof Error ? signal.reason : new Error(String(signal.reason ?? "MCP request aborted")));
17
- };
18
- signal.addEventListener("abort", onAbort, { once: true });
19
- promise.then(
20
- value => {
21
- if (settled) return;
22
- settled = true;
23
- cleanup();
24
- resolve(value);
25
- },
26
- error => {
27
- if (settled) return;
28
- settled = true;
29
- cleanup();
30
- reject(error);
31
- },
32
- );
33
- });
34
- }
package/agent-dir.ts DELETED
@@ -1,24 +0,0 @@
1
- import { homedir } from "node:os";
2
- import { join, resolve } from "node:path";
3
-
4
- export function getAgentDir(): string {
5
- const configured = process.env.PI_CODING_AGENT_DIR?.trim();
6
- if (!configured) {
7
- return join(homedir(), ".pi", "agent");
8
- }
9
- if (configured === "~") {
10
- return homedir();
11
- }
12
- if (configured.startsWith("~/")) {
13
- return resolve(homedir(), configured.slice(2));
14
- }
15
- return resolve(configured);
16
- }
17
-
18
- export function getAgentPath(...segments: string[]): string {
19
- return join(getAgentDir(), ...segments);
20
- }
21
-
22
- export function getAdapterPath(...segments: string[]): string {
23
- return getAgentPath("fitch-mcp-adapter", ...segments);
24
- }