@fitchmultz/pi-mcp-adapter 6.0.0 → 7.1.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 (111) hide show
  1. package/CHANGELOG.md +33 -0
  2. package/OAUTH.md +1 -1
  3. package/README.md +27 -18
  4. package/dist/app-bridge.bundle.js +14 -14
  5. package/dist/config.js +41 -16
  6. package/dist/direct-tools.js +1 -1
  7. package/dist/gateway-arguments.d.ts +1 -1
  8. package/dist/index.js +38 -59
  9. package/dist/lifecycle.d.ts +2 -2
  10. package/dist/lifecycle.js +2 -2
  11. package/dist/mcp-auth-flow.js +2 -2
  12. package/dist/mcp-auth.d.ts +1 -1
  13. package/dist/mcp-auth.js +3 -3
  14. package/dist/mcp-code.d.ts +6 -6
  15. package/dist/mcp-code.js +11 -10
  16. package/dist/mcp-oauth-provider.d.ts +5 -5
  17. package/dist/mcp-oauth-provider.js +5 -5
  18. package/dist/mcp-output-guard.d.ts +1 -1
  19. package/dist/mcp-output-guard.js +1 -1
  20. package/dist/mcp-panel.d.ts +2 -2
  21. package/dist/mcp-panel.js +2 -2
  22. package/dist/mcp-setup-panel.d.ts +5 -4
  23. package/dist/mcp-setup-panel.js +15 -7
  24. package/dist/mcp-trace.d.ts +1 -1
  25. package/dist/mcp-trace.js +1 -1
  26. package/dist/npx-resolver.js +8 -4
  27. package/dist/proxy-modes.d.ts +2 -2
  28. package/dist/proxy-modes.js +20 -19
  29. package/dist/sampling-handler.js +1 -0
  30. package/dist/search-ranking.js +6 -2
  31. package/dist/server-manager.d.ts +2 -2
  32. package/dist/server-manager.js +44 -27
  33. package/dist/session-recovery.d.ts +7 -4
  34. package/dist/session-recovery.js +76 -10
  35. package/dist/tool-loader.d.ts +7 -2
  36. package/dist/tool-loader.js +56 -5
  37. package/dist/tool-metadata.d.ts +43 -43
  38. package/dist/tool-result-renderer.d.ts +3 -3
  39. package/dist/tool-result-renderer.js +3 -3
  40. package/dist/ui-resource-handler.d.ts +2 -2
  41. package/dist/ui-resource-handler.js +2 -2
  42. package/dist/ui-server.js +5 -4
  43. package/dist/ui-stream-types.d.ts +8 -8
  44. package/dist/unix-socket-transport.d.ts +1 -1
  45. package/dist/unix-socket-transport.js +1 -1
  46. package/dist/utils.d.ts +1 -0
  47. package/dist/utils.js +14 -0
  48. package/package.json +34 -98
  49. package/skills/mcp-scripting/SKILL.md +2 -0
  50. package/abort.ts +0 -34
  51. package/agent-dir.ts +0 -24
  52. package/app-bridge.bundle.js +0 -109
  53. package/banner.png +0 -0
  54. package/checkpoint.ts +0 -55
  55. package/commands.ts +0 -622
  56. package/config.ts +0 -1204
  57. package/consent-manager.ts +0 -25
  58. package/direct-tools.ts +0 -75
  59. package/elicitation-handler.ts +0 -349
  60. package/error-signal.ts +0 -21
  61. package/gateway-arguments.ts +0 -73
  62. package/glimpse-ui.ts +0 -80
  63. package/host-html-template.ts +0 -456
  64. package/index.ts +0 -767
  65. package/init.ts +0 -613
  66. package/legacy-migration.ts +0 -47
  67. package/lifecycle.ts +0 -165
  68. package/logger.ts +0 -127
  69. package/mcp-auth-flow.ts +0 -975
  70. package/mcp-auth.ts +0 -793
  71. package/mcp-callback-server.ts +0 -450
  72. package/mcp-code.ts +0 -412
  73. package/mcp-keyring-helper.cjs +0 -89
  74. package/mcp-oauth-provider.ts +0 -774
  75. package/mcp-output-guard.ts +0 -530
  76. package/mcp-panel.ts +0 -907
  77. package/mcp-script-worker.mjs +0 -131
  78. package/mcp-setup-panel.ts +0 -666
  79. package/mcp-status.ts +0 -110
  80. package/mcp-trace.ts +0 -295
  81. package/metadata-cache.ts +0 -271
  82. package/npx-resolver.ts +0 -488
  83. package/onboarding-state.ts +0 -58
  84. package/panel-keys.ts +0 -37
  85. package/prompts.ts +0 -337
  86. package/proxy-modes.ts +0 -1109
  87. package/resource-tools.ts +0 -52
  88. package/runtime-owner.ts +0 -123
  89. package/sampling-handler.ts +0 -276
  90. package/scripts/build-app-bridge.mjs +0 -15
  91. package/scripts/build.mjs +0 -132
  92. package/scripts/prepare.mjs +0 -103
  93. package/search-ranking.ts +0 -132
  94. package/server-manager.ts +0 -1320
  95. package/session-recovery.ts +0 -316
  96. package/state.ts +0 -64
  97. package/tool-approval.ts +0 -78
  98. package/tool-loader.ts +0 -153
  99. package/tool-metadata.ts +0 -240
  100. package/tool-registrar.ts +0 -74
  101. package/tool-result-renderer.ts +0 -294
  102. package/tsconfig.build.json +0 -13
  103. package/tsconfig.json +0 -16
  104. package/types.ts +0 -785
  105. package/ui-resource-handler.ts +0 -249
  106. package/ui-server.ts +0 -808
  107. package/ui-session.ts +0 -568
  108. package/ui-stream-types.ts +0 -89
  109. package/ui-tool-visibility.ts +0 -25
  110. package/unix-socket-transport.ts +0 -85
  111. package/utils.ts +0 -307
@@ -21,7 +21,7 @@
21
21
  // - treat generic -32000/ConnectionClosed errors as session expiry
22
22
  // - treat AbortError/cancellation as a session failure
23
23
  import { AsyncLocalStorage } from "node:async_hooks";
24
- import { SdkHttpError, SdkErrorCode, ProtocolError, InsufficientScopeError, UnauthorizedError } from "@modelcontextprotocol/client";
24
+ import { SdkError, SdkHttpError, SdkErrorCode, ProtocolError, InsufficientScopeError, UnauthorizedError } from "@modelcontextprotocol/client";
25
25
  import { logger } from "./logger.js";
26
26
  import { abortable, throwIfAborted } from "./abort.js";
27
27
  import { isServerDisabled, isNonInteractiveOAuth } from "./types.js";
@@ -30,18 +30,62 @@ const toolTransportFailures = new WeakSet();
30
30
  export function isToolTransportFailure(error) {
31
31
  return error instanceof Error && toolTransportFailures.has(error);
32
32
  }
33
+ const interruptedToolCalls = new WeakSet();
34
+ export function isInterruptedToolCall(error) {
35
+ return error instanceof Error && interruptedToolCalls.has(error);
36
+ }
37
+ const toolCallOutcome = new AsyncLocalStorage();
38
+ /** Interrupted outcome reporting is separate from the transport retry policy. */
39
+ export async function trackToolCallOutcome(fn, signal) {
40
+ const outcome = { pending: false, cleanup: new Set() };
41
+ return toolCallOutcome.run(outcome, async () => {
42
+ try {
43
+ return await fn();
44
+ }
45
+ catch (error) {
46
+ if (outcome.pending && error instanceof Error
47
+ && (signal?.aborted || (error instanceof SdkError
48
+ && (error.code === SdkErrorCode.RequestTimeout || error.code === SdkErrorCode.ConnectionClosed)))) {
49
+ // Abort reasons can be shared by calls that never reached dispatch.
50
+ const interrupted = new Error(error.message, { cause: error });
51
+ interruptedToolCalls.add(interrupted);
52
+ throw interrupted;
53
+ }
54
+ throw error;
55
+ }
56
+ finally {
57
+ for (const cleanup of outcome.cleanup)
58
+ cleanup();
59
+ }
60
+ });
61
+ }
33
62
  const toolSend = new AsyncLocalStorage();
34
- /** Retain fetch/body-error origin without parsing or buffering the SSE response. */
63
+ /** Retain fetch/body-error origin without parsing or buffering the response. */
35
64
  export const trackToolTransportFailure = async (input, init) => {
36
65
  const pending = toolSend.getStore();
37
66
  if (pending)
38
67
  pending.bodyFailure = undefined;
68
+ let toolPost = false;
69
+ if (pending && init?.method === "POST" && typeof init.body === "string") {
70
+ try {
71
+ const request = JSON.parse(init.body);
72
+ toolPost = request.method === "tools/call" && request.id === pending.id;
73
+ }
74
+ catch { }
75
+ }
76
+ if (toolPost && pending?.outcome)
77
+ pending.outcome.pending = true;
39
78
  try {
40
79
  const response = await fetch(input, init);
41
- if (!pending || !response.ok || !response.body
42
- || response.headers.get("content-type")?.split(";", 1)[0]?.trim().toLowerCase() !== "text/event-stream")
80
+ if (toolPost && !response.ok && response.status < 500 && pending?.outcome)
81
+ pending.outcome.pending = false;
82
+ const mediaType = response.headers.get("content-type")?.split(";", 1)[0]?.trim().toLowerCase();
83
+ const observeBody = response.ok
84
+ ? mediaType === "text/event-stream" || (toolPost && mediaType === "application/json")
85
+ : toolPost && response.status >= 500;
86
+ if (!pending || !response.body || !observeBody)
43
87
  return response;
44
- pending.streaming = true;
88
+ pending.streaming = response.ok && mediaType === "text/event-stream";
45
89
  // The native end hook has no error argument. Only a failed body read proves loss.
46
90
  const reader = response.body.getReader();
47
91
  return new Response(new ReadableStream({
@@ -59,8 +103,11 @@ export const trackToolTransportFailure = async (input, init) => {
59
103
  // Native intentional closure suppresses onRequestStreamEnd.
60
104
  if (init?.signal?.aborted)
61
105
  pending.finishSend();
62
- else if (error instanceof Error)
106
+ else if (error instanceof Error) {
63
107
  pending.bodyFailure = error;
108
+ if (!pending.streaming)
109
+ interruptedToolCalls.add(error);
110
+ }
64
111
  controller.error(error);
65
112
  reader.releaseLock();
66
113
  }
@@ -77,12 +124,16 @@ export const trackToolTransportFailure = async (input, init) => {
77
124
  }
78
125
  };
79
126
  /** Observe only the originating tool send, not OAuth or catalog requests. */
80
- export function trackToolHttpFailures(transport) {
127
+ export function trackToolTransport(transport, http = false) {
128
+ const requests = new Map();
81
129
  const onmessage = transport.onmessage;
82
130
  transport.onmessage = (message, extra) => {
83
- const pending = toolSend.getStore();
131
+ const pending = ("id" in message && message.id !== undefined ? requests.get(message.id) : undefined) ?? toolSend.getStore();
84
132
  if (pending && "id" in message && message.id === pending.id && ("result" in message || "error" in message)) {
85
133
  pending.responseReceived = true;
134
+ if (pending.outcome)
135
+ pending.outcome.pending = false;
136
+ requests.delete(pending.id);
86
137
  pending.finishSend();
87
138
  }
88
139
  onmessage?.(message, extra);
@@ -98,15 +149,24 @@ export function trackToolHttpFailures(transport) {
98
149
  const send = transport.send.bind(transport);
99
150
  transport.send = async (message, options) => {
100
151
  try {
101
- if (!("method" in message && message.method === "tools/call" && "id" in message && options?.requestSignal)) {
152
+ if (!("method" in message && message.method === "tools/call" && "id" in message)) {
102
153
  return await toolSend.run(undefined, () => send(message, options));
103
154
  }
104
155
  const pending = {
105
- id: message.id, streaming: false, responseReceived: false,
156
+ id: message.id, outcome: toolCallOutcome.getStore(), streaming: false, responseReceived: false,
106
157
  bodyFailure: undefined, otherFailure: undefined,
107
158
  finishSend: () => { },
108
159
  };
160
+ if (pending.outcome) {
161
+ requests.set(message.id, pending);
162
+ pending.outcome.cleanup.add(() => requests.delete(message.id));
163
+ if (!http)
164
+ pending.outcome.pending = true;
165
+ }
109
166
  try {
167
+ if (!options?.requestSignal) {
168
+ return await toolSend.run(pending, () => send(message, options));
169
+ }
110
170
  await abortable(new Promise((resolve, reject) => {
111
171
  pending.finishSend = resolve;
112
172
  void toolSend.run(pending, () => send(message, {
@@ -126,6 +186,12 @@ export function trackToolHttpFailures(transport) {
126
186
  resolve(); }, reject);
127
187
  }), options.requestSignal);
128
188
  }
189
+ catch (error) {
190
+ // Local send failures are distinct from a sent request waiting for its reply.
191
+ if (!http && pending.outcome)
192
+ pending.outcome.pending = false;
193
+ throw error;
194
+ }
129
195
  finally {
130
196
  pending.finishSend = () => { };
131
197
  }
@@ -7,6 +7,10 @@ type Reference = {
7
7
  name: string;
8
8
  namespace?: string;
9
9
  };
10
+ export type Feature = "gateway" | "script";
11
+ export type FeaturePolicy = Partial<Record<Feature, {
12
+ eager?: boolean;
13
+ }>>;
10
14
  type SearchDefinition<TParams extends TSchema, TDetails> = Omit<ToolDefinition<TParams, TDetails>, "execute"> & {
11
15
  execute: (...args: Parameters<ToolDefinition<TParams, TDetails>["execute"]>) => Promise<AgentToolResult<TDetails> & {
12
16
  tools: Reference[];
@@ -20,12 +24,13 @@ export type SearchAPI = ExtensionAPI & {
20
24
  export declare function supportsNativeAsync(ctx: ExtensionContext): boolean;
21
25
  export declare function createToolLoader(pi: SearchAPI, getState: () => McpExtensionState | null, getInitPromise: () => Promise<McpExtensionState> | null, beforeExecute?: McpAdapterOptions["beforeExecute"]): {
22
26
  native: boolean;
27
+ registerFeature: <TParams extends TSchema, TDetails>(feature: Feature, definition: ToolDefinition<TParams, TDetails>) => void;
23
28
  restore: (ctx: ExtensionContext) => void;
24
- sync: (config: McpConfig, cache: MetadataCache | null, env?: string[] | null, asyncSupported?: boolean, registerGateways?: (pins: DirectToolSpec[]) => void) => DirectToolSpec[];
29
+ sync: (config: McpConfig, cache: MetadataCache | null, env?: string[] | null, asyncSupported?: boolean, registerGateways?: (pins: DirectToolSpec[]) => FeaturePolicy) => DirectToolSpec[];
25
30
  activate: (matches: Array<{
26
31
  server: string;
27
32
  tool: ToolMetadata;
28
- }>) => Reference[];
33
+ }>, enable?: Feature[]) => Reference[];
29
34
  persist: () => void;
30
35
  selectedServers: () => string[];
31
36
  };
@@ -5,6 +5,7 @@ import { createMcpDirectToolCallRenderer, renderMcpToolResult } from "./tool-res
5
5
  import { isServerDisabled } from "./types.js";
6
6
  import { normalizeDirectToolInputSchema } from "./utils.js";
7
7
  import { isToolCallApprovalRequired } from "./tool-approval.js";
8
+ const featureRefs = { gateway: { name: "mcp" }, script: { name: "mcp_script" } };
8
9
  const ENTRY = "mcp-tool-selection";
9
10
  const key = (value) => JSON.stringify([value.server, value.tool]);
10
11
  const refKey = (value) => JSON.stringify([value.namespace ?? null, value.name]);
@@ -23,6 +24,10 @@ export function createToolLoader(pi, getState, getInitPromise, beforeExecute) {
23
24
  let available = new Set();
24
25
  let selected = new Map();
25
26
  let inactive = new Map();
27
+ let features = {};
28
+ let selectedFeatures = new Set();
29
+ let inactiveFeatures = new Set();
30
+ const registeredFeatures = new Set();
26
31
  let restoring = true;
27
32
  const active = () => native ? pi.getActiveToolReferences() : pi.getActiveTools().map(name => ({ name }));
28
33
  const setActive = (refs) => {
@@ -34,8 +39,24 @@ export function createToolLoader(pi, getState, getInitPromise, beforeExecute) {
34
39
  else
35
40
  pi.setActiveTools(unique.map(ref => ref.name));
36
41
  };
42
+ function registerFeature(feature, definition) {
43
+ pi.registerTool(definition);
44
+ registeredFeatures.add(feature);
45
+ }
37
46
  function capture() {
38
47
  const current = new Set(active().map(refKey));
48
+ for (const feature of Object.keys(features)) {
49
+ if (current.has(refKey(featureRefs[feature]))) {
50
+ if (!features[feature]?.eager)
51
+ selectedFeatures.add(feature);
52
+ inactiveFeatures.delete(feature);
53
+ }
54
+ else {
55
+ selectedFeatures.delete(feature);
56
+ if (features[feature]?.eager)
57
+ inactiveFeatures.add(feature);
58
+ }
59
+ }
39
60
  for (const [id, { selection, ref, pinned }] of registered) {
40
61
  if (!available.has(id))
41
62
  continue;
@@ -53,15 +74,19 @@ export function createToolLoader(pi, getState, getInitPromise, beforeExecute) {
53
74
  }
54
75
  function persist() {
55
76
  capture();
56
- pi.appendEntry(ENTRY, { selected: [...selected.values()], inactive: [...inactive.values()] });
77
+ pi.appendEntry(ENTRY, { selected: [...selected.values()], inactive: [...inactive.values()], features: [...selectedFeatures], inactiveFeatures: [...inactiveFeatures] });
57
78
  }
58
79
  function restore(ctx) {
59
80
  selected = new Map();
60
81
  inactive = new Map();
82
+ selectedFeatures = new Set();
83
+ inactiveFeatures = new Set();
61
84
  for (const entry of ctx.sessionManager.getBranch()) {
62
85
  if (entry.type !== "custom" || entry.customType !== ENTRY || !entry.data || typeof entry.data !== "object")
63
86
  continue;
64
87
  const data = entry.data;
88
+ selectedFeatures = new Set(Array.isArray(data.features) ? data.features.filter(value => value === "gateway" || value === "script") : []);
89
+ inactiveFeatures = new Set(Array.isArray(data.inactiveFeatures) ? data.inactiveFeatures.filter(value => value === "gateway" || value === "script") : []);
65
90
  for (const [field, target] of [[data.selected, selected], [data.inactive, inactive]]) {
66
91
  target.clear();
67
92
  for (const selection of Array.isArray(field) ? field : []) {
@@ -125,28 +150,54 @@ export function createToolLoader(pi, getState, getInitPromise, beforeExecute) {
125
150
  }
126
151
  const permitted = new Set(pi.getAllTools().map(refKey));
127
152
  const pins = specs.filter(item => item.pinned && available.has(key(item.selection)) && permitted.has(refKey(item.ref))).map(item => item.spec);
128
- registerGateways?.(pins);
153
+ const policy = registerGateways?.(pins);
154
+ if (policy) {
155
+ const permittedFeatures = new Set(pi.getAllTools().map(refKey));
156
+ const loaderMissing = !permittedFeatures.has(refKey({ name: "mcp_search" }));
157
+ features = {};
158
+ for (const feature of Object.keys(featureRefs)) {
159
+ if (policy[feature] && permittedFeatures.has(refKey(featureRefs[feature]))) {
160
+ features[feature] = { eager: policy[feature].eager || loaderMissing };
161
+ if (selectedFeatures.has(feature) || (features[feature]?.eager && !inactiveFeatures.has(feature)))
162
+ enabled.push(featureRefs[feature]);
163
+ }
164
+ else {
165
+ selectedFeatures.delete(feature);
166
+ inactiveFeatures.delete(feature);
167
+ }
168
+ }
169
+ }
129
170
  // Official 0.87 reactivates allowlisted definitions on registration. Apply our loadout last.
130
171
  const ownedRefs = new Set([...previousRefs, ...[...registered.values()].map(item => refKey(item.ref))]);
172
+ for (const feature of registeredFeatures)
173
+ ownedRefs.add(refKey(featureRefs[feature]));
131
174
  setActive([...active().filter(ref => !ownedRefs.has(refKey(ref))), ...enabled]);
132
175
  restoring = false;
133
176
  return pins;
134
177
  }
135
- function activate(matches) {
178
+ function activate(matches, enable = []) {
136
179
  const requested = matches.flatMap(({ server, tool }) => {
137
180
  const id = key({ server, tool: tool.originalName });
138
181
  const registration = available.has(id) ? registered.get(id) : undefined;
139
182
  return registration ? [registration] : [];
140
183
  });
141
184
  // No await between reading and extending the current loadout: parallel searches union their selections.
142
- setActive([...active(), ...requested.map(item => item.ref)]);
185
+ const permitted = new Set(pi.getAllTools().map(refKey));
186
+ const requestedFeatures = [...new Set(enable)].filter(feature => features[feature] && permitted.has(refKey(featureRefs[feature])));
187
+ setActive([...active(), ...requested.map(item => item.ref), ...requestedFeatures.map(feature => featureRefs[feature])]);
143
188
  const current = new Set(active().map(refKey));
144
189
  const tools = requested.filter(item => current.has(refKey(item.ref))).map(({ selection, ref }) => {
145
190
  selected.set(key(selection), selection);
146
191
  return ref;
147
192
  });
193
+ for (const feature of requestedFeatures) {
194
+ if (!current.has(refKey(featureRefs[feature])))
195
+ continue;
196
+ selectedFeatures.add(feature);
197
+ tools.push(featureRefs[feature]);
198
+ }
148
199
  persist();
149
200
  return tools;
150
201
  }
151
- return { native, restore, sync, activate, persist, selectedServers: () => [...new Set([...selected.values()].map(item => item.server))] };
202
+ return { native, registerFeature, restore, sync, activate, persist, selectedServers: () => [...new Set([...selected.values()].map(item => item.server))] };
152
203
  }
@@ -6,10 +6,20 @@ export declare function buildToolMetadata(tools: McpTool[], resources: McpResour
6
6
  };
7
7
  export declare function totalToolCount(state: McpExtensionState): number;
8
8
  export declare function toToolDescriptor(server: string, tool: ToolMetadata): {
9
- server: string;
10
- path: string;
11
- name: string;
12
- description: string;
9
+ outputSchema?: {
10
+ [x: string]: unknown;
11
+ $schema?: string | undefined;
12
+ } | undefined;
13
+ annotations?: {
14
+ title?: string | undefined;
15
+ readOnlyHint?: boolean | undefined;
16
+ destructiveHint?: boolean | undefined;
17
+ idempotentHint?: boolean | undefined;
18
+ openWorldHint?: boolean | undefined;
19
+ } | undefined;
20
+ execution?: {
21
+ taskSupport?: "forbidden" | "optional" | "required" | undefined;
22
+ } | undefined;
13
23
  _meta?: {
14
24
  [x: string]: unknown;
15
25
  } | undefined;
@@ -17,55 +27,45 @@ export declare function toToolDescriptor(server: string, tool: ToolMetadata): {
17
27
  src: string;
18
28
  mimeType?: string | undefined;
19
29
  sizes?: string[] | undefined;
20
- theme?: "light" | "dark" | undefined;
30
+ theme?: "dark" | "light" | undefined;
21
31
  }[] | undefined;
22
32
  title?: string | undefined;
23
33
  inputSchema?: {
24
34
  [x: string]: unknown;
25
35
  type: "object";
26
36
  properties?: {
27
- [x: string]: string | number | boolean | {
28
- [x: string]: string | number | boolean | /*elided*/ any | (string | number | boolean | /*elided*/ any | (string | number | boolean | /*elided*/ any | (string | number | boolean | /*elided*/ any | (string | number | boolean | /*elided*/ any | (string | number | boolean | /*elided*/ any | (string | number | boolean | /*elided*/ any | (string | number | boolean | /*elided*/ any | (string | number | boolean | /*elided*/ any | (string | number | boolean | /*elided*/ any | (string | number | boolean | /*elided*/ any | (string | number | boolean | /*elided*/ any | /*elided*/ any | null)[] | null)[] | null)[] | null)[] | null)[] | null)[] | null)[] | null)[] | null)[] | null)[] | null)[] | null;
29
- } | (string | number | boolean | {
30
- [x: string]: string | number | boolean | /*elided*/ any | (string | number | boolean | /*elided*/ any | (string | number | boolean | /*elided*/ any | (string | number | boolean | /*elided*/ any | (string | number | boolean | /*elided*/ any | (string | number | boolean | /*elided*/ any | (string | number | boolean | /*elided*/ any | (string | number | boolean | /*elided*/ any | (string | number | boolean | /*elided*/ any | (string | number | boolean | /*elided*/ any | (string | number | boolean | /*elided*/ any | /*elided*/ any | null)[] | null)[] | null)[] | null)[] | null)[] | null)[] | null)[] | null)[] | null)[] | null)[] | null;
31
- } | (string | number | boolean | {
32
- [x: string]: string | number | boolean | /*elided*/ any | (string | number | boolean | /*elided*/ any | (string | number | boolean | /*elided*/ any | (string | number | boolean | /*elided*/ any | (string | number | boolean | /*elided*/ any | (string | number | boolean | /*elided*/ any | (string | number | boolean | /*elided*/ any | (string | number | boolean | /*elided*/ any | (string | number | boolean | /*elided*/ any | (string | number | boolean | /*elided*/ any | /*elided*/ any | null)[] | null)[] | null)[] | null)[] | null)[] | null)[] | null)[] | null)[] | null)[] | null;
33
- } | (string | number | boolean | {
34
- [x: string]: string | number | boolean | /*elided*/ any | (string | number | boolean | /*elided*/ any | (string | number | boolean | /*elided*/ any | (string | number | boolean | /*elided*/ any | (string | number | boolean | /*elided*/ any | (string | number | boolean | /*elided*/ any | (string | number | boolean | /*elided*/ any | (string | number | boolean | /*elided*/ any | (string | number | boolean | /*elided*/ any | /*elided*/ any | null)[] | null)[] | null)[] | null)[] | null)[] | null)[] | null)[] | null)[] | null;
35
- } | (string | number | boolean | {
36
- [x: string]: string | number | boolean | /*elided*/ any | (string | number | boolean | /*elided*/ any | (string | number | boolean | /*elided*/ any | (string | number | boolean | /*elided*/ any | (string | number | boolean | /*elided*/ any | (string | number | boolean | /*elided*/ any | (string | number | boolean | /*elided*/ any | (string | number | boolean | /*elided*/ any | /*elided*/ any | null)[] | null)[] | null)[] | null)[] | null)[] | null)[] | null)[] | null;
37
- } | (string | number | boolean | {
38
- [x: string]: string | number | boolean | /*elided*/ any | (string | number | boolean | /*elided*/ any | (string | number | boolean | /*elided*/ any | (string | number | boolean | /*elided*/ any | (string | number | boolean | /*elided*/ any | (string | number | boolean | /*elided*/ any | (string | number | boolean | /*elided*/ any | /*elided*/ any | null)[] | null)[] | null)[] | null)[] | null)[] | null)[] | null;
39
- } | (string | number | boolean | {
40
- [x: string]: string | number | boolean | /*elided*/ any | (string | number | boolean | /*elided*/ any | (string | number | boolean | /*elided*/ any | (string | number | boolean | /*elided*/ any | (string | number | boolean | /*elided*/ any | (string | number | boolean | /*elided*/ any | /*elided*/ any | null)[] | null)[] | null)[] | null)[] | null)[] | null;
41
- } | (string | number | boolean | {
42
- [x: string]: string | number | boolean | /*elided*/ any | (string | number | boolean | /*elided*/ any | (string | number | boolean | /*elided*/ any | (string | number | boolean | /*elided*/ any | (string | number | boolean | /*elided*/ any | /*elided*/ any | null)[] | null)[] | null)[] | null)[] | null;
43
- } | (string | number | boolean | {
44
- [x: string]: string | number | boolean | /*elided*/ any | (string | number | boolean | /*elided*/ any | (string | number | boolean | /*elided*/ any | (string | number | boolean | /*elided*/ any | /*elided*/ any | null)[] | null)[] | null)[] | null;
45
- } | (string | number | boolean | {
46
- [x: string]: string | number | boolean | /*elided*/ any | (string | number | boolean | /*elided*/ any | (string | number | boolean | /*elided*/ any | /*elided*/ any | null)[] | null)[] | null;
47
- } | (string | number | boolean | {
48
- [x: string]: string | number | boolean | /*elided*/ any | (string | number | boolean | /*elided*/ any | /*elided*/ any | null)[] | null;
49
- } | (string | number | boolean | {
37
+ [x: string]: string | number | boolean | (string | number | boolean | (string | number | boolean | (string | number | boolean | (string | number | boolean | (string | number | boolean | (string | number | boolean | (string | number | boolean | (string | number | boolean | (string | number | boolean | (string | number | boolean | (string | number | boolean | /*elided*/ any | {
50
38
  [x: string]: string | number | boolean | /*elided*/ any | /*elided*/ any | null;
51
- } | /*elided*/ any | null)[] | null)[] | null)[] | null)[] | null)[] | null)[] | null)[] | null)[] | null)[] | null)[] | null)[] | null;
39
+ } | null)[] | {
40
+ [x: string]: string | number | boolean | any | any | null;
41
+ } | null)[] | {
42
+ [x: string]: string | number | boolean | any | any | null;
43
+ } | null)[] | {
44
+ [x: string]: string | number | boolean | any | any | null;
45
+ } | null)[] | {
46
+ [x: string]: string | number | boolean | any | any | null;
47
+ } | null)[] | {
48
+ [x: string]: string | number | boolean | any | any | null;
49
+ } | null)[] | {
50
+ [x: string]: string | number | boolean | any | any | null;
51
+ } | null)[] | {
52
+ [x: string]: string | number | boolean | any | any | null;
53
+ } | null)[] | {
54
+ [x: string]: string | number | boolean | any | any | null;
55
+ } | null)[] | {
56
+ [x: string]: string | number | boolean | any | any | null;
57
+ } | null)[] | {
58
+ [x: string]: string | number | boolean | any | any | null;
59
+ } | null)[] | {
60
+ [x: string]: string | number | boolean | any | any | null;
61
+ } | null;
52
62
  } | undefined;
53
63
  required?: string[] | undefined;
54
64
  };
55
- outputSchema?: {
56
- [x: string]: unknown;
57
- $schema?: string | undefined;
58
- } | undefined;
59
- annotations?: {
60
- title?: string | undefined;
61
- readOnlyHint?: boolean | undefined;
62
- destructiveHint?: boolean | undefined;
63
- idempotentHint?: boolean | undefined;
64
- openWorldHint?: boolean | undefined;
65
- } | undefined;
66
- execution?: {
67
- taskSupport?: "optional" | "required" | "forbidden" | undefined;
68
- } | undefined;
65
+ description: string;
66
+ server: string;
67
+ path: string;
68
+ name: string;
69
69
  };
70
70
  export declare function catalogCoverage(state: McpExtensionState, server?: string): {
71
71
  complete: boolean;
@@ -25,15 +25,15 @@ export interface McpToolResultDisplay {
25
25
  truncated: boolean;
26
26
  }
27
27
  declare class CollapsibleText implements Component {
28
+ private readonly fullText;
29
+ private readonly footerText;
30
+ private collapsedText;
28
31
  private readonly text;
29
32
  private readonly expanded;
30
33
  private readonly maxCollapsedLines;
31
34
  private readonly ellipsis;
32
35
  private readonly expandHint;
33
36
  private readonly preTruncated;
34
- private readonly fullText;
35
- private readonly footerText;
36
- private collapsedText;
37
37
  constructor(text: string, expanded: boolean, maxCollapsedLines: number, ellipsis: string, expandHint: string, preTruncated?: boolean);
38
38
  render(width: number): string[];
39
39
  invalidate(): void;
@@ -9,15 +9,15 @@ const DEFAULT_MAX_COLLAPSED_LINES = 3;
9
9
  const DEFAULT_MAX_COLLAPSED_CHARS = 8000;
10
10
  const COLLAPSED_RENDER_CHAR_SLACK = 8;
11
11
  class CollapsibleText {
12
+ fullText;
13
+ footerText;
14
+ collapsedText = null;
12
15
  text;
13
16
  expanded;
14
17
  maxCollapsedLines;
15
18
  ellipsis;
16
19
  expandHint;
17
20
  preTruncated;
18
- fullText;
19
- footerText;
20
- collapsedText = null;
21
21
  constructor(text, expanded, maxCollapsedLines, ellipsis, expandHint, preTruncated = false) {
22
22
  this.text = text;
23
23
  this.expanded = expanded;
@@ -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
  }
@@ -480,7 +481,7 @@ export async function startUiServer(options) {
480
481
  }
481
482
  catch (error) {
482
483
  if (error instanceof SessionRecoveryAuthRequiredError) {
483
- const fallback = `Server "${options.serverName}" requires OAuth authentication. Run mcp({ action: "auth-start", server: "${options.serverName}" }) to get a browser URL, or /mcp-auth ${options.serverName} in an interactive local session.`;
484
+ const fallback = `Server "${options.serverName}" requires OAuth authentication. If mcp is inactive, first mcp_search({ enable: ["gateway"] }). Run mcp({ action: "auth-start", server: "${options.serverName}" }) to get a browser URL, or /mcp-auth ${options.serverName} in an interactive local session.`;
484
485
  const message = error.authMessage ?? (options.config
485
486
  ? formatAuthRequiredMessage(options.config, options.serverName, fallback)
486
487
  : fallback);
@@ -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