@fitchmultz/pi-mcp-adapter 8.0.6 → 8.0.8

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 (87) hide show
  1. package/CHANGELOG.md +142 -8
  2. package/OAUTH.md +13 -16
  3. package/README.md +8 -8
  4. package/cli.js +67 -28
  5. package/dist/abort.js +18 -9
  6. package/dist/client-operations.js +4 -2
  7. package/dist/commands.js +77 -43
  8. package/dist/config.js +194 -105
  9. package/dist/connection-runtime.d.ts +2 -2
  10. package/dist/connection-runtime.js +29 -18
  11. package/dist/consent-manager.d.ts +2 -2
  12. package/dist/consent-manager.js +5 -2
  13. package/dist/direct-tools.d.ts +1 -1
  14. package/dist/direct-tools.js +13 -7
  15. package/dist/elicitation-handler.d.ts +2 -3
  16. package/dist/elicitation-handler.js +113 -58
  17. package/dist/gateway-arguments.d.ts +1 -1
  18. package/dist/gateway-arguments.js +90 -23
  19. package/dist/glimpse-ui.js +10 -5
  20. package/dist/host-html-template.d.ts +3 -1
  21. package/dist/host-html-template.js +9 -12
  22. package/dist/index.d.ts +1 -1
  23. package/dist/index.js +264 -119
  24. package/dist/init.d.ts +1 -3
  25. package/dist/init.js +91 -51
  26. package/dist/legacy-migration.d.ts +1 -1
  27. package/dist/legacy-migration.js +17 -12
  28. package/dist/lifecycle.d.ts +3 -3
  29. package/dist/lifecycle.js +33 -17
  30. package/dist/logger.d.ts +2 -2
  31. package/dist/logger.js +4 -2
  32. package/dist/mcp-auth-flow.js +150 -58
  33. package/dist/mcp-auth.d.ts +7 -7
  34. package/dist/mcp-auth.js +189 -138
  35. package/dist/mcp-callback-server.js +19 -8
  36. package/dist/mcp-code.js +168 -54
  37. package/dist/mcp-keyring-helper.cjs +57 -23
  38. package/dist/mcp-oauth-provider.d.ts +6 -7
  39. package/dist/mcp-oauth-provider.js +146 -72
  40. package/dist/mcp-oauth-runtime.js +29 -16
  41. package/dist/mcp-output-guard.js +193 -65
  42. package/dist/mcp-output-preview.js +41 -20
  43. package/dist/mcp-panel.d.ts +9 -9
  44. package/dist/mcp-panel.js +141 -70
  45. package/dist/mcp-script-worker.mjs +135 -43
  46. package/dist/mcp-setup-panel.d.ts +8 -8
  47. package/dist/mcp-setup-panel.js +130 -44
  48. package/dist/mcp-status.js +14 -7
  49. package/dist/mcp-trace.d.ts +10 -5
  50. package/dist/mcp-trace.js +127 -61
  51. package/dist/metadata-cache.d.ts +3 -4
  52. package/dist/metadata-cache.js +83 -43
  53. package/dist/npx-resolver.js +101 -60
  54. package/dist/onboarding-state.js +4 -2
  55. package/dist/plain-result-reader.js +78 -31
  56. package/dist/prompts.js +36 -20
  57. package/dist/proxy-modes.js +670 -193
  58. package/dist/resource-tools.js +30 -9
  59. package/dist/runtime-owner.js +36 -18
  60. package/dist/sampling-handler.d.ts +1 -2
  61. package/dist/sampling-handler.js +41 -21
  62. package/dist/search-ranking.d.ts +1 -1
  63. package/dist/search-ranking.js +67 -34
  64. package/dist/server-manager.d.ts +11 -11
  65. package/dist/server-manager.js +283 -166
  66. package/dist/session-recovery.js +113 -61
  67. package/dist/tool-approval.js +9 -3
  68. package/dist/tool-loader.js +154 -55
  69. package/dist/tool-metadata.d.ts +1 -1
  70. package/dist/tool-metadata.js +51 -23
  71. package/dist/tool-registrar.js +32 -10
  72. package/dist/tool-result-renderer.js +58 -33
  73. package/dist/types.d.ts +2 -2
  74. package/dist/types.js +39 -18
  75. package/dist/ui-message-history.js +33 -13
  76. package/dist/ui-resource-handler.d.ts +3 -3
  77. package/dist/ui-resource-handler.js +37 -21
  78. package/dist/ui-server.js +101 -43
  79. package/dist/ui-session.js +66 -29
  80. package/dist/ui-stream-types.js +14 -4
  81. package/dist/ui-tool-visibility.js +12 -6
  82. package/dist/unix-socket-transport.d.ts +1 -2
  83. package/dist/unix-socket-transport.js +19 -12
  84. package/dist/utils.d.ts +2 -0
  85. package/dist/utils.js +60 -35
  86. package/package.json +28 -2
  87. package/skills/mcp-scripting/SKILL.md +6 -6
@@ -1,5 +1,4 @@
1
- import type { Transport } from "@modelcontextprotocol/client";
2
- import type { JSONRPCMessage } from "@modelcontextprotocol/client";
1
+ import { type Transport, type JSONRPCMessage } from "@modelcontextprotocol/client";
3
2
  /** MCP JSONL transport for an explicitly configured Unix-domain socket. */
4
3
  export declare class UnixSocketClientTransport implements Transport {
5
4
  private socket;
@@ -1,5 +1,5 @@
1
1
  import { createConnection } from "node:net";
2
- import { ReadBuffer, serializeMessage } from "@modelcontextprotocol/client";
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
5
  socket;
@@ -23,13 +23,14 @@ export class UnixSocketClientTransport {
23
23
  connected = true;
24
24
  resolve();
25
25
  });
26
- socket.on("data", chunk => {
26
+ socket.on("data", (chunk) => {
27
27
  try {
28
28
  this.readBuffer.append(chunk);
29
29
  while (true) {
30
30
  const message = this.readBuffer.readMessage();
31
- if (message === null)
31
+ if (message === null) {
32
32
  break;
33
+ }
33
34
  this.onmessage?.(message);
34
35
  }
35
36
  }
@@ -39,14 +40,16 @@ export class UnixSocketClientTransport {
39
40
  void this.close();
40
41
  }
41
42
  });
42
- socket.on("error", error => {
43
- if (!connected)
43
+ socket.on("error", (error) => {
44
+ if (!connected) {
44
45
  reject(error);
46
+ }
45
47
  this.onerror?.(error);
46
48
  });
47
49
  socket.on("close", () => {
48
- if (this.socket === socket)
50
+ if (this.socket === socket) {
49
51
  this.socket = undefined;
52
+ }
50
53
  this.readBuffer.clear();
51
54
  this.onclose?.();
52
55
  });
@@ -56,9 +59,10 @@ export class UnixSocketClientTransport {
56
59
  const socket = this.socket;
57
60
  this.socket = undefined;
58
61
  this.readBuffer.clear();
59
- if (!socket || socket.destroyed)
62
+ if (!socket || socket.destroyed) {
60
63
  return;
61
- await new Promise(resolve => {
64
+ }
65
+ await new Promise((resolve) => {
62
66
  const timeout = setTimeout(() => socket.destroy(), 2_000);
63
67
  timeout.unref();
64
68
  socket.once("close", () => {
@@ -70,14 +74,17 @@ export class UnixSocketClientTransport {
70
74
  }
71
75
  async send(message) {
72
76
  const socket = this.socket;
73
- if (!socket || socket.destroyed)
77
+ if (!socket || socket.destroyed) {
74
78
  throw new Error("Unix socket is not connected");
79
+ }
75
80
  await new Promise((resolve, reject) => {
76
- socket.write(serializeMessage(message), error => {
77
- if (error)
81
+ socket.write(serializeMessage(message), (error) => {
82
+ if (error) {
78
83
  reject(error);
79
- else
84
+ }
85
+ else {
80
86
  resolve();
87
+ }
81
88
  });
82
89
  });
83
90
  }
package/dist/utils.d.ts CHANGED
@@ -2,6 +2,8 @@ import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
2
2
  import type { McpConfig, ServerEntry } from "./types.ts";
3
3
  export declare const ADAPTER_VERSION: string;
4
4
  export declare const DEFAULT_MCP_SCRIPT_TIMEOUT_MS = 30000;
5
+ /** Most servers one multi-server pass connects at once. */
6
+ export declare const MAX_CONCURRENT_SERVER_CONNECTS = 10;
5
7
  export declare function normalizeRequestTimeoutMs(timeoutMs: number | undefined): number | undefined;
6
8
  export declare function openUrl(pi: ExtensionAPI, url: string, browser?: string, signal?: AbortSignal): Promise<void>;
7
9
  export declare function openPath(pi: ExtensionAPI, targetPath: string): Promise<void>;
package/dist/utils.js CHANGED
@@ -7,8 +7,9 @@ function readAdapterVersion() {
7
7
  for (const path of ["./package.json", "../package.json"]) {
8
8
  try {
9
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")
10
+ if (manifest.name === "@fitchmultz/pi-mcp-adapter" && typeof manifest.version === "string") {
11
11
  return manifest.version;
12
+ }
12
13
  }
13
14
  catch { }
14
15
  }
@@ -16,6 +17,8 @@ function readAdapterVersion() {
16
17
  }
17
18
  export const ADAPTER_VERSION = readAdapterVersion();
18
19
  export const DEFAULT_MCP_SCRIPT_TIMEOUT_MS = 30_000;
20
+ /** Most servers one multi-server pass connects at once. */
21
+ export const MAX_CONCURRENT_SERVER_CONNECTS = 10;
19
22
  export function normalizeRequestTimeoutMs(timeoutMs) {
20
23
  return typeof timeoutMs === "number" && Number.isFinite(timeoutMs) && timeoutMs > 0
21
24
  ? timeoutMs
@@ -55,13 +58,16 @@ export async function parallelLimit(items, limit, fn) {
55
58
  async function worker() {
56
59
  while (true) {
57
60
  const next = iterator.next();
58
- if (next.done)
61
+ if (next.done) {
59
62
  return;
63
+ }
60
64
  const [index, item] = next.value;
61
65
  results[index] = await fn(item);
62
66
  }
63
67
  }
64
- const workers = Array(Math.min(limit, items.length)).fill(null).map(() => worker());
68
+ const workers = Array(Math.min(limit, items.length))
69
+ .fill(null)
70
+ .map(() => worker());
65
71
  await Promise.all(workers);
66
72
  return results;
67
73
  }
@@ -82,37 +88,41 @@ function getMissingEnvVars(value) {
82
88
  return [...missing];
83
89
  }
84
90
  export function toStringRecord(value) {
85
- if (!value || typeof value !== "object" || Array.isArray(value))
91
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
86
92
  return undefined;
93
+ }
87
94
  const result = {};
88
95
  for (const [key, entry] of Object.entries(value)) {
89
- if (typeof entry === "string")
96
+ if (typeof entry === "string") {
90
97
  result[key] = entry;
98
+ }
91
99
  }
92
100
  return Object.keys(result).length > 0 ? result : undefined;
93
101
  }
94
102
  function interpolateSecretExpression(value) {
95
- if (value.startsWith("!!"))
103
+ if (value.startsWith("!!")) {
96
104
  return interpolateEnvVars(value.slice(1));
105
+ }
97
106
  return value.startsWith("!") ? value : interpolateEnvVars(value);
98
107
  }
99
108
  export function interpolateEnvRecord(values) {
100
- if (!values)
109
+ if (!values) {
101
110
  return undefined;
102
- return Object.fromEntries(Object.entries(values).map(([key, value]) => [
103
- key,
104
- interpolateSecretExpression(value),
105
- ]));
111
+ }
112
+ return Object.fromEntries(Object.entries(values).map(([key, value]) => [key, interpolateSecretExpression(value)]));
106
113
  }
107
114
  const COMMAND_SECRET_TIMEOUT_MS = 10_000;
108
115
  const COMMAND_SECRET_MAX_OUTPUT_BYTES = 1024 * 1024;
109
116
  export function resolveCommandSecret(value, context) {
110
- if (value === undefined)
117
+ if (value === undefined) {
111
118
  return undefined;
112
- if (value.startsWith("!!"))
119
+ }
120
+ if (value.startsWith("!!")) {
113
121
  return interpolateEnvVars(value.slice(1));
114
- if (!value.startsWith("!"))
122
+ }
123
+ if (!value.startsWith("!")) {
115
124
  return interpolateEnvVars(value);
125
+ }
116
126
  let result;
117
127
  try {
118
128
  result = spawnSync(value.slice(1), {
@@ -140,22 +150,22 @@ export function resolveCommandSecret(value, context) {
140
150
  throw new Error(`Failed to resolve ${context}: command exited with code ${result.status ?? "unknown"}`);
141
151
  }
142
152
  const resolved = result.stdout.trim();
143
- if (!resolved)
153
+ if (!resolved) {
144
154
  throw new Error(`Failed to resolve ${context}: command returned empty output`);
155
+ }
145
156
  return resolved;
146
157
  }
147
158
  /** Resolve command markers in a configured record without mutating the input. */
148
159
  export function resolveCommandSecretsRecord(values, context) {
149
- if (!values)
160
+ if (!values) {
150
161
  return undefined;
151
- return Object.fromEntries(Object.entries(values).map(([key, value]) => [
152
- key,
153
- resolveCommandSecret(value, context(key)),
154
- ]));
162
+ }
163
+ return Object.fromEntries(Object.entries(values).map(([key, value]) => [key, resolveCommandSecret(value, context(key))]));
155
164
  }
156
165
  export function resolveServerUrl(definition) {
157
- if (definition.url == null)
166
+ if (definition.url == null) {
158
167
  return undefined;
168
+ }
159
169
  if (typeof definition.url !== "string") {
160
170
  throw new Error("MCP server URL must be a string");
161
171
  }
@@ -168,16 +178,20 @@ export function resolveServerUrl(definition) {
168
178
  new URL(resolved);
169
179
  }
170
180
  catch (error) {
171
- throw new Error(`Invalid MCP server URL after environment interpolation: ${resolved}`, { cause: error });
181
+ throw new Error(`Invalid MCP server URL after environment interpolation: ${resolved}`, {
182
+ cause: error,
183
+ });
172
184
  }
173
185
  return resolved;
174
186
  }
175
187
  export function resolveConfigPath(value) {
176
- if (value === undefined)
188
+ if (value === undefined) {
177
189
  return undefined;
190
+ }
178
191
  const resolved = interpolateEnvVars(value);
179
- if (resolved === "~")
192
+ if (resolved === "~") {
180
193
  return homedir();
194
+ }
181
195
  if (resolved.startsWith("~/") || resolved.startsWith("~\\")) {
182
196
  return join(homedir(), resolved.slice(2));
183
197
  }
@@ -203,8 +217,9 @@ export function stripOscSequences(text) {
203
217
  index += isEscOsc ? 2 : 1;
204
218
  while (index < text.length) {
205
219
  const code = text.charCodeAt(index++);
206
- if (code === 0x07 || code === 0x9c)
220
+ if (code === 0x07 || code === 0x9c) {
207
221
  break;
222
+ }
208
223
  if (code === 0x1b && text[index] === "\\") {
209
224
  index++;
210
225
  break;
@@ -224,25 +239,32 @@ export function formatTerminalError(error) {
224
239
  const messages = [];
225
240
  const seen = new Set();
226
241
  const collect = (value) => {
227
- if (seen.has(value))
242
+ if (seen.has(value)) {
228
243
  return;
229
- if ((typeof value === "object" && value !== null) || typeof value === "function")
244
+ }
245
+ if ((typeof value === "object" && value !== null) || typeof value === "function") {
230
246
  seen.add(value);
247
+ }
231
248
  if (value instanceof AggregateError) {
232
249
  const countBefore = messages.length;
233
- for (const nested of value.errors)
250
+ for (const nested of value.errors) {
234
251
  collect(nested);
235
- if (value.cause !== undefined)
252
+ }
253
+ if (value.cause !== undefined) {
236
254
  collect(value.cause);
237
- if (messages.length === countBefore && value.message)
255
+ }
256
+ if (messages.length === countBefore && value.message) {
238
257
  messages.push(value.message);
258
+ }
239
259
  return;
240
260
  }
241
261
  if (value instanceof Error) {
242
- if (value.message)
262
+ if (value.message) {
243
263
  messages.push(value.message);
244
- if (value.cause !== undefined)
264
+ }
265
+ if (value.cause !== undefined) {
245
266
  collect(value.cause);
267
+ }
246
268
  return;
247
269
  }
248
270
  messages.push(String(value));
@@ -251,8 +273,9 @@ export function formatTerminalError(error) {
251
273
  return sanitizeTerminalText([...new Set(messages)].join(": "));
252
274
  }
253
275
  export function truncateAtWord(text, target) {
254
- if (!text || text.length <= target)
276
+ if (!text || text.length <= target) {
255
277
  return text;
278
+ }
256
279
  const truncated = text.slice(0, target);
257
280
  const lastSpace = truncated.lastIndexOf(" ");
258
281
  if (lastSpace > target * 0.6) {
@@ -270,8 +293,9 @@ export function formatAuthRequiredMessage(config, serverName, defaultMessage) {
270
293
  return template ? template.replaceAll("${server}", serverName) : defaultMessage;
271
294
  }
272
295
  export function formatMcpStatus(config, message) {
273
- if (config.settings?.mcpFooterStatus === "off")
296
+ if (config.settings?.mcpFooterStatus === "off") {
274
297
  return undefined;
298
+ }
275
299
  return `${config.settings?.showStatusIcon === false ? "MCP: " : "🔌 MCP: "}${message}`;
276
300
  }
277
301
  /**
@@ -279,8 +303,9 @@ export function formatMcpStatus(config, message) {
279
303
  */
280
304
  export function extractToolUiStreamMode(toolMeta) {
281
305
  const uiMeta = toolMeta?.ui;
282
- if (!uiMeta || typeof uiMeta !== "object")
306
+ if (!uiMeta || typeof uiMeta !== "object") {
283
307
  return undefined;
308
+ }
284
309
  const streamMode = uiMeta["pi-mcp-adapter.streamMode"];
285
310
  if (streamMode === "eager" || streamMode === "stream-first") {
286
311
  return streamMode;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fitchmultz/pi-mcp-adapter",
3
- "version": "8.0.6",
3
+ "version": "8.0.8",
4
4
  "description": "Token-efficient MCP adapter for the Pi coding agent",
5
5
  "type": "module",
6
6
  "types": "./dist/index.d.ts",
@@ -33,10 +33,32 @@
33
33
  "prepare": "node ./scripts/prepare.mjs",
34
34
  "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",
35
35
  "typecheck": "node ./scripts/tsc.mjs --noEmit",
36
+ "typecheck:all": "node ./scripts/typecheck-all.mjs",
36
37
  "test": "vitest run && npm run test:oauth",
37
38
  "test:watch": "vitest",
38
39
  "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",
39
- "test:conformance": "bash conformance/run.sh"
40
+ "test:conformance": "bash conformance/run.sh",
41
+ "check:directives": "node scripts/check-directives.mjs",
42
+ "check:policy": "node scripts/check-policy.mjs",
43
+ "lint": "oxlint --deny-warnings .",
44
+ "lint:conformance": "oxlint --deny-warnings conformance",
45
+ "lint:prompts": "oxlint --deny-warnings __tests__/prompts.test.ts __tests__/prompts-sdk-integration.test.ts __tests__/test-state.ts __tests__/test-extension-context.ts",
46
+ "lint:runtime-tests": "oxlint --deny-warnings __tests__/runtime",
47
+ "lint:tracing": "oxlint --deny-warnings mcp-trace.ts",
48
+ "lint:sdk-http": "oxlint --deny-warnings __tests__/sdk-v2-http.test.ts __tests__/fixtures/native-capture-extension.ts",
49
+ "lint:lifecycle-tests": "oxlint --deny-warnings __tests__/index-lifecycle.test.ts __tests__/fixtures/extension-lifecycle.ts __tests__/test-extension-context.ts",
50
+ "lint:auth-tests": "oxlint --deny-warnings __tests__/auth",
51
+ "lint:discovery-tests": "oxlint --deny-warnings __tests__/discovery __tests__/fixtures/pinned-tools.ts",
52
+ "lint:state-tests": "oxlint --deny-warnings __tests__/state",
53
+ "lint:output-tests": "oxlint --deny-warnings __tests__/output",
54
+ "lint:packaging-tests": "oxlint --deny-warnings __tests__/packaging",
55
+ "lint:quality": "oxlint --deny-warnings scripts/check-directives.mjs scripts/check-policy.mjs scripts/quality-exceptions.mjs __tests__/quality",
56
+ "lint:ui-host-tests": "oxlint --deny-warnings __tests__/ui-host",
57
+ "lint:panel-tests": "oxlint --deny-warnings __tests__/panel",
58
+ "lint:agent": "oxlint --deny-warnings --format=agent .",
59
+ "lint:fix": "oxlint --fix .",
60
+ "format": "oxfmt --write .",
61
+ "format:check": "oxfmt --check ."
40
62
  },
41
63
  "repository": {
42
64
  "type": "git",
@@ -112,6 +134,10 @@
112
134
  "@types/cross-spawn": "6.0.6",
113
135
  "@types/node": "24.19.1",
114
136
  "esbuild": "0.28.2",
137
+ "oxc-parser": "0.156.0",
138
+ "oxfmt": "0.75.0",
139
+ "oxlint": "1.90.0",
140
+ "oxlint-tsgolint": "7.0.2005",
115
141
  "typebox": "1.3.27",
116
142
  "typescript": "7.0.2",
117
143
  "vitest": "5.0.3"
@@ -66,12 +66,12 @@ return page;
66
66
  Gateway equivalents require explicit actions:
67
67
 
68
68
  ```js
69
- mcp({ action: "search", query: "search issues", server: "github" })
70
- mcp({ action: "describe", tool: "github_search_issues" })
71
- mcp({ action: "call", tool: "github_search_issues", args: { query: "is:open" } })
72
- mcp({ action: "resources", server: "docs" })
73
- mcp({ action: "read-resource", server: "docs", uri: "docs://guide" })
74
- mcp({ action: "read-result", ref: "/returned/result/path", path: "/structuredContent" })
69
+ mcp({ action: "search", query: "search issues", server: "github" });
70
+ mcp({ action: "describe", tool: "github_search_issues" });
71
+ mcp({ action: "call", tool: "github_search_issues", args: { query: "is:open" } });
72
+ mcp({ action: "resources", server: "docs" });
73
+ mcp({ action: "read-resource", server: "docs", uri: "docs://guide" });
74
+ mcp({ action: "read-result", ref: "/returned/result/path", path: "/structuredContent" });
75
75
  ```
76
76
 
77
77
  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.