@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,17 +1,29 @@
1
1
  import { Type } from "typebox";
2
2
  import { createDirectToolExecutor, directToolSelection } from "./direct-tools.js";
3
- import { isServerCacheValid, reconstructToolMetadata } from "./metadata-cache.js";
3
+ import { isServerCacheValid, reconstructToolMetadata, } from "./metadata-cache.js";
4
4
  import { createMcpDirectToolCallRenderer, renderMcpToolResult } from "./tool-result-renderer.js";
5
- import { isServerDisabled } from "./types.js";
5
+ import { isServerDisabled, } from "./types.js";
6
6
  import { normalizeDirectToolInputSchema } from "./utils.js";
7
7
  import { nativeMcpOutputSchema } from "./tool-registrar.js";
8
8
  import { toolErrorOverride } from "./error-signal.js";
9
9
  const featureNames = { gateway: "mcp", script: "mcp_script" };
10
10
  const ENTRY = "mcp-tool-selection";
11
11
  const key = (value) => JSON.stringify([value.server, value.tool]);
12
- const reserved = new Set(["read", "bash", "powershell", "edit", "write", "grep", "find", "ls", "mcp", "mcp_search", "mcp_script"]);
12
+ const reserved = new Set([
13
+ "read",
14
+ "bash",
15
+ "powershell",
16
+ "edit",
17
+ "write",
18
+ "grep",
19
+ "find",
20
+ "ls",
21
+ "mcp",
22
+ "mcp_search",
23
+ "mcp_script",
24
+ ]);
13
25
  export function createToolLoader(pi, getState, getInitPromise, beforeExecute) {
14
- pi.registerEntryRenderer(ENTRY, () => undefined);
26
+ pi.registerEntryRenderer(ENTRY, () => { });
15
27
  const registered = new Map();
16
28
  const fingerprints = new WeakMap();
17
29
  let available = new Set();
@@ -25,49 +37,64 @@ export function createToolLoader(pi, getState, getInitPromise, beforeExecute) {
25
37
  const active = () => pi.getActiveTools();
26
38
  const setActive = (names) => {
27
39
  const unique = [...new Set(names)];
28
- if (JSON.stringify(active()) === JSON.stringify(unique))
40
+ if (JSON.stringify(active()) === JSON.stringify(unique)) {
29
41
  return;
42
+ }
30
43
  pi.setActiveTools(unique);
31
44
  };
32
45
  function registerFeature(feature, definition) {
33
- pi.registerTool({ defaultActive: false, ...definition, async execute(...args) {
46
+ pi.registerTool({
47
+ defaultActive: false,
48
+ ...definition,
49
+ async execute(...args) {
34
50
  const result = await definition.execute(...args);
35
51
  return { ...result, ...toolErrorOverride(result.details) };
36
- } });
52
+ },
53
+ });
37
54
  registeredFeatures.add(feature);
38
55
  }
39
56
  function capture() {
40
57
  const current = new Set(active());
41
58
  for (const feature of Object.keys(features)) {
42
59
  if (current.has(featureNames[feature])) {
43
- if (!features[feature]?.eager)
60
+ if (!features[feature]?.eager) {
44
61
  selectedFeatures.add(feature);
62
+ }
45
63
  inactiveFeatures.delete(feature);
46
64
  }
47
65
  else {
48
66
  selectedFeatures.delete(feature);
49
- if (features[feature]?.eager)
67
+ if (features[feature]?.eager) {
50
68
  inactiveFeatures.add(feature);
69
+ }
51
70
  }
52
71
  }
53
72
  for (const [id, { selection, name, pinned }] of registered) {
54
- if (!available.has(id))
73
+ if (!available.has(id)) {
55
74
  continue;
75
+ }
56
76
  if (current.has(name)) {
57
- if (!pinned)
77
+ if (!pinned) {
58
78
  selected.set(id, selection);
79
+ }
59
80
  inactive.delete(id);
60
81
  }
61
82
  else {
62
83
  selected.delete(id);
63
- if (pinned)
84
+ if (pinned) {
64
85
  inactive.set(id, selection);
86
+ }
65
87
  }
66
88
  }
67
89
  }
68
90
  function persist() {
69
91
  capture();
70
- pi.appendEntry(ENTRY, { selected: [...selected.values()], inactive: [...inactive.values()], features: [...selectedFeatures], inactiveFeatures: [...inactiveFeatures] });
92
+ pi.appendEntry(ENTRY, {
93
+ selected: [...selected.values()],
94
+ inactive: [...inactive.values()],
95
+ features: [...selectedFeatures],
96
+ inactiveFeatures: [...inactiveFeatures],
97
+ });
71
98
  }
72
99
  function restore(ctx) {
73
100
  selected = new Map();
@@ -79,27 +106,43 @@ export function createToolLoader(pi, getState, getInitPromise, beforeExecute) {
79
106
  let id = manager.getLeafId();
80
107
  while (id) {
81
108
  const entry = manager.getEntry(id);
82
- if (!entry)
109
+ if (!entry) {
83
110
  break;
111
+ }
84
112
  yield entry;
85
113
  id = entry.parentId;
86
114
  }
87
115
  }
88
116
  for (const entry of snapshots()) {
89
- if (entry?.type !== "custom" || entry.customType !== ENTRY || !entry.data || typeof entry.data !== "object")
117
+ if (entry?.type !== "custom" ||
118
+ entry.customType !== ENTRY ||
119
+ !entry.data ||
120
+ typeof entry.data !== "object") {
90
121
  continue;
122
+ }
91
123
  const data = entry.data;
92
- if (!Array.isArray(data.selected) || [data.inactive, data.features, data.inactiveFeatures].some(value => value !== undefined && !Array.isArray(value)))
124
+ if (!Array.isArray(data.selected) ||
125
+ [data.inactive, data.features, data.inactiveFeatures].some((value) => value !== undefined && !Array.isArray(value))) {
93
126
  continue;
94
- if ([...data.selected, ...(data.inactive ?? [])].some(value => !value || typeof value.server !== "string" || typeof value.tool !== "string"))
127
+ }
128
+ if ([...data.selected, ...(data.inactive ?? [])].some((value) => !value || typeof value.server !== "string" || typeof value.tool !== "string")) {
95
129
  continue;
96
- selectedFeatures = new Set(Array.isArray(data.features) ? data.features.filter(value => value === "gateway" || value === "script") : []);
97
- inactiveFeatures = new Set(Array.isArray(data.inactiveFeatures) ? data.inactiveFeatures.filter(value => value === "gateway" || value === "script") : []);
98
- for (const [field, target] of [[data.selected, selected], [data.inactive, inactive]]) {
130
+ }
131
+ selectedFeatures = new Set(Array.isArray(data.features)
132
+ ? data.features.filter((value) => value === "gateway" || value === "script")
133
+ : []);
134
+ inactiveFeatures = new Set(Array.isArray(data.inactiveFeatures)
135
+ ? data.inactiveFeatures.filter((value) => value === "gateway" || value === "script")
136
+ : []);
137
+ for (const [field, target] of [
138
+ [data.selected, selected],
139
+ [data.inactive, inactive],
140
+ ]) {
99
141
  target.clear();
100
142
  for (const selection of Array.isArray(field) ? field : []) {
101
- if (typeof selection?.server === "string" && typeof selection.tool === "string")
143
+ if (typeof selection?.server === "string" && typeof selection.tool === "string") {
102
144
  target.set(key(selection), selection);
145
+ }
103
146
  }
104
147
  }
105
148
  break;
@@ -107,53 +150,86 @@ export function createToolLoader(pi, getState, getInitPromise, beforeExecute) {
107
150
  restoring = true;
108
151
  }
109
152
  function sync(config, cache, env, registerGateways) {
110
- if (!restoring)
153
+ if (!restoring) {
111
154
  capture();
112
- const previousNames = new Set([...registered.values()].map(item => item.name));
113
- const existing = new Set(pi.getAllTools().map(tool => tool.name));
155
+ }
156
+ const previousNames = new Set([...registered.values()].map((item) => item.name));
157
+ const existing = new Set(pi.getAllTools().map((tool) => tool.name));
114
158
  const specs = [];
115
159
  for (const [server, definition] of Object.entries(env === null ? {} : config.mcpServers)) {
116
- if (isServerDisabled(definition))
160
+ if (isServerDisabled(definition)) {
117
161
  continue;
162
+ }
118
163
  const entry = cache?.servers[server];
119
- const metadata = getState()?.toolMetadata.get(server) ?? (entry && isServerCacheValid(entry, definition)
120
- ? reconstructToolMetadata(server, entry, config.settings?.toolPrefix ?? "server", definition) : []);
164
+ const metadata = getState()?.toolMetadata.get(server) ??
165
+ (entry && isServerCacheValid(entry, definition)
166
+ ? reconstructToolMetadata(server, entry, config.settings?.toolPrefix ?? "server", definition)
167
+ : []);
121
168
  const selection = directToolSelection(config, server, env ?? undefined);
122
169
  for (const source of metadata) {
123
170
  const { name: prefixedName, ...tool } = source;
124
- if (tool.resourceUri)
171
+ if (tool.resourceUri) {
125
172
  continue;
173
+ }
126
174
  const pinned = selection === true || (Array.isArray(selection) && selection.includes(tool.originalName));
127
- specs.push({ spec: { ...tool, prefixedName, serverName: server }, source, selection: { server, tool: tool.originalName }, name: prefixedName, pinned });
175
+ specs.push({
176
+ spec: { ...tool, prefixedName, serverName: server },
177
+ source,
178
+ selection: { server, tool: tool.originalName },
179
+ name: prefixedName,
180
+ pinned,
181
+ });
128
182
  }
129
183
  }
130
184
  const counts = new Map();
131
- for (const { name } of specs)
185
+ for (const { name } of specs) {
132
186
  counts.set(name, (counts.get(name) ?? 0) + 1);
187
+ }
133
188
  available = new Set();
134
189
  const enabled = [];
135
190
  for (const { spec, source, selection, name, pinned } of specs) {
136
191
  const id = key(selection);
137
- if (reserved.has(name) || counts.get(name) > 1 || (existing.has(name) && !previousNames.has(name)))
192
+ if (reserved.has(name) ||
193
+ counts.get(name) > 1 ||
194
+ (existing.has(name) && !previousNames.has(name))) {
138
195
  continue;
196
+ }
139
197
  available.add(id);
140
- const instructions = getState()?.serverInstructions.get(spec.serverName) ?? cache?.servers[spec.serverName]?.instructions;
198
+ const instructions = getState()?.serverInstructions.get(spec.serverName) ??
199
+ cache?.servers[spec.serverName]?.instructions;
141
200
  const previous = Object.isFrozen(source) ? fingerprints.get(source) : undefined;
142
201
  const fingerprint = previous?.server === spec.serverName && previous.instructions === instructions
143
- ? previous.value : JSON.stringify({ spec, name, instructions });
144
- if (Object.isFrozen(source))
202
+ ? previous.value
203
+ : JSON.stringify({ spec, name, instructions });
204
+ if (Object.isFrozen(source)) {
145
205
  fingerprints.set(source, { server: spec.serverName, instructions, value: fingerprint });
206
+ }
146
207
  if (registered.get(id)?.fingerprint !== fingerprint) {
147
208
  pi.registerTool({
148
209
  name,
149
- namespace: { name: `mcp_${spec.serverName}`, ...(instructions !== undefined ? { instructions } : {}) },
210
+ namespace: {
211
+ name: `mcp_${spec.serverName}`,
212
+ ...(instructions !== undefined ? { instructions } : {}),
213
+ },
150
214
  ...(spec.outputSchema ? { outputSchema: nativeMcpOutputSchema(spec.outputSchema) } : {}),
151
- ...(spec.annotations ? { annotations: {
152
- ...(typeof spec.annotations.readOnlyHint === "boolean" ? { readOnlyHint: spec.annotations.readOnlyHint } : {}),
153
- ...(typeof spec.annotations.destructiveHint === "boolean" ? { destructiveHint: spec.annotations.destructiveHint } : {}),
154
- ...(typeof spec.annotations.idempotentHint === "boolean" ? { idempotentHint: spec.annotations.idempotentHint } : {}),
155
- ...(typeof spec.annotations.openWorldHint === "boolean" ? { openWorldHint: spec.annotations.openWorldHint } : {}),
156
- } } : {}),
215
+ ...(spec.annotations
216
+ ? {
217
+ annotations: {
218
+ ...(typeof spec.annotations.readOnlyHint === "boolean"
219
+ ? { readOnlyHint: spec.annotations.readOnlyHint }
220
+ : {}),
221
+ ...(typeof spec.annotations.destructiveHint === "boolean"
222
+ ? { destructiveHint: spec.annotations.destructiveHint }
223
+ : {}),
224
+ ...(typeof spec.annotations.idempotentHint === "boolean"
225
+ ? { idempotentHint: spec.annotations.idempotentHint }
226
+ : {}),
227
+ ...(typeof spec.annotations.openWorldHint === "boolean"
228
+ ? { openWorldHint: spec.annotations.openWorldHint }
229
+ : {}),
230
+ },
231
+ }
232
+ : {}),
157
233
  // Inactive direct tools are not callable through codemode or nested execution.
158
234
  defaultActive: false,
159
235
  ...(beforeExecute ? { executionMode: "sequential" } : {}),
@@ -166,21 +242,26 @@ export function createToolLoader(pi, getState, getInitPromise, beforeExecute) {
166
242
  });
167
243
  }
168
244
  registered.set(id, { selection, name, fingerprint, pinned });
169
- if (selected.has(id) || (pinned && !inactive.has(id)))
245
+ if (selected.has(id) || (pinned && !inactive.has(id))) {
170
246
  enabled.push(name);
247
+ }
171
248
  }
172
- const permitted = new Set(pi.getAllTools().map(tool => tool.name));
173
- const pins = specs.filter(item => item.pinned && available.has(key(item.selection)) && permitted.has(item.name)).map(item => item.spec);
249
+ const permitted = new Set(pi.getAllTools().map((tool) => tool.name));
250
+ const pins = specs
251
+ .filter((item) => item.pinned && available.has(key(item.selection)) && permitted.has(item.name))
252
+ .map((item) => item.spec);
174
253
  const policy = registerGateways?.(pins);
175
254
  if (policy) {
176
- const permittedFeatures = new Set(pi.getAllTools().map(tool => tool.name));
255
+ const permittedFeatures = new Set(pi.getAllTools().map((tool) => tool.name));
177
256
  const loaderMissing = !permittedFeatures.has("mcp_search");
178
257
  features = {};
179
258
  for (const feature of Object.keys(featureNames)) {
180
259
  if (policy[feature] && permittedFeatures.has(featureNames[feature])) {
181
260
  features[feature] = { eager: policy[feature].eager || loaderMissing };
182
- if (selectedFeatures.has(feature) || (features[feature]?.eager && !inactiveFeatures.has(feature)))
261
+ if (selectedFeatures.has(feature) ||
262
+ (features[feature]?.eager && !inactiveFeatures.has(feature))) {
183
263
  enabled.push(featureNames[feature]);
264
+ }
184
265
  }
185
266
  else {
186
267
  selectedFeatures.delete(feature);
@@ -189,10 +270,14 @@ export function createToolLoader(pi, getState, getInitPromise, beforeExecute) {
189
270
  }
190
271
  }
191
272
  // Retain the journal: official 1.0 initial SDK resume has a native loadout restoration gap.
192
- const ownedNames = new Set([...previousNames, ...[...registered.values()].map(item => item.name)]);
193
- for (const feature of registeredFeatures)
273
+ const ownedNames = new Set([
274
+ ...previousNames,
275
+ ...[...registered.values()].map((item) => item.name),
276
+ ]);
277
+ for (const feature of registeredFeatures) {
194
278
  ownedNames.add(featureNames[feature]);
195
- setActive([...active().filter(name => !ownedNames.has(name)), ...enabled]);
279
+ }
280
+ setActive([...active().filter((name) => !ownedNames.has(name)), ...enabled]);
196
281
  restoring = false;
197
282
  return pins;
198
283
  }
@@ -203,22 +288,36 @@ export function createToolLoader(pi, getState, getInitPromise, beforeExecute) {
203
288
  return registration ? [registration] : [];
204
289
  });
205
290
  // No await between reading and extending the current loadout: parallel searches union their selections.
206
- const permitted = new Set(pi.getAllTools().map(tool => tool.name));
207
- const requestedFeatures = [...new Set(enable)].filter(feature => features[feature] && permitted.has(featureNames[feature]));
208
- setActive([...active(), ...requested.map(item => item.name), ...requestedFeatures.map(feature => featureNames[feature])]);
291
+ const permitted = new Set(pi.getAllTools().map((tool) => tool.name));
292
+ const requestedFeatures = [...new Set(enable)].filter((feature) => features[feature] && permitted.has(featureNames[feature]));
293
+ setActive([
294
+ ...active(),
295
+ ...requested.map((item) => item.name),
296
+ ...requestedFeatures.map((feature) => featureNames[feature]),
297
+ ]);
209
298
  const current = new Set(active());
210
- const tools = requested.filter(item => current.has(item.name)).map(({ selection, name }) => {
299
+ const tools = requested
300
+ .filter((item) => current.has(item.name))
301
+ .map(({ selection, name }) => {
211
302
  selected.set(key(selection), selection);
212
303
  return { name };
213
304
  });
214
305
  for (const feature of requestedFeatures) {
215
- if (!current.has(featureNames[feature]))
306
+ if (!current.has(featureNames[feature])) {
216
307
  continue;
308
+ }
217
309
  selectedFeatures.add(feature);
218
310
  tools.push({ name: featureNames[feature] });
219
311
  }
220
312
  persist();
221
313
  return tools;
222
314
  }
223
- return { registerFeature, restore, sync, activate, persist, selectedServers: () => [...new Set([...selected.values()].map(item => item.server))] };
315
+ return {
316
+ registerFeature,
317
+ restore,
318
+ sync,
319
+ activate,
320
+ persist,
321
+ selectedServers: () => [...new Set([...selected.values()].map((item) => item.server))],
322
+ };
224
323
  }
@@ -1,5 +1,5 @@
1
1
  import type { McpExtensionState } from "./state.ts";
2
- import type { ToolMetadata, McpTool, McpResource, ServerEntry, ToolPrefix } from "./types.ts";
2
+ import { type ToolMetadata, type McpTool, type McpResource, type ServerEntry, type ToolPrefix } from "./types.ts";
3
3
  export declare function buildToolMetadata(tools: McpTool[], resources: McpResource[], definition: ServerEntry, serverName: string, prefix: ToolPrefix): {
4
4
  metadata: ToolMetadata[];
5
5
  failedTools: string[];
@@ -1,11 +1,12 @@
1
- import { formatToolName, isServerDisabled, isToolAllowed, resolveToolPrefix } from "./types.js";
1
+ import { formatToolName, isServerDisabled, isToolAllowed, resolveToolPrefix, } from "./types.js";
2
2
  import { extractToolUiStreamMode } from "./utils.js";
3
3
  import { extractUiToolVisibility, isUiToolVisibleToModel } from "./ui-tool-visibility.js";
4
4
  export function buildToolMetadata(tools, resources, definition, serverName, prefix) {
5
5
  const metadata = [];
6
6
  const failedTools = [];
7
- if (isServerDisabled(definition))
7
+ if (isServerDisabled(definition)) {
8
8
  return { metadata, failedTools };
9
+ }
9
10
  const effectivePrefix = resolveToolPrefix(definition, prefix);
10
11
  for (const tool of tools) {
11
12
  if (!tool?.name) {
@@ -25,10 +26,12 @@ export function buildToolMetadata(tools, resources, definition, serverName, pref
25
26
  // MCP Apps metadata must also be readable from the cache before loading the SDK.
26
27
  const uri = tool._meta?.ui?.resourceUri;
27
28
  const resourceUri = uri === undefined ? tool._meta?.["ui/resourceUri"] : uri;
28
- if (typeof resourceUri === "string" && resourceUri.startsWith("ui://"))
29
+ if (typeof resourceUri === "string" && resourceUri.startsWith("ui://")) {
29
30
  uiResourceUri = resourceUri;
30
- else if (resourceUri !== undefined)
31
+ }
32
+ else if (resourceUri !== undefined) {
31
33
  failedTools.push(tool.name);
34
+ }
32
35
  }
33
36
  catch {
34
37
  failedTools.push(tool.name);
@@ -46,8 +49,9 @@ export function buildToolMetadata(tools, resources, definition, serverName, pref
46
49
  }
47
50
  if (definition.exposeResources !== false) {
48
51
  for (const resource of resources) {
49
- if (!resource?.name || !resource?.uri)
52
+ if (!resource?.name || !resource?.uri) {
50
53
  continue;
54
+ }
51
55
  const baseName = `read_${resourceNameToToolName(resource.name)}`;
52
56
  if (!isToolAllowed(baseName, serverName, effectivePrefix, definition.includeTools, definition.excludeTools)) {
53
57
  continue;
@@ -67,7 +71,7 @@ export function buildToolMetadata(tools, resources, definition, serverName, pref
67
71
  export function totalToolCount(state) {
68
72
  let count = 0;
69
73
  for (const metadata of state.toolMetadata.values()) {
70
- count += metadata.filter(tool => tool.resourceUri === undefined).length;
74
+ count += metadata.filter((tool) => tool.resourceUri === undefined).length;
71
75
  }
72
76
  return count;
73
77
  }
@@ -76,19 +80,21 @@ export function toToolDescriptor(server, tool) {
76
80
  return { ...descriptor, server, path, name };
77
81
  }
78
82
  export function catalogCoverage(state, server) {
79
- const servers = Object.keys(state.config.mcpServers).filter(name => (!server || name === server) && !isServerDisabled(state.config.mcpServers[name]));
80
- const knownServers = servers.filter(name => state.toolMetadata.has(name));
81
- const unknownServers = servers.filter(name => !state.toolMetadata.has(name));
83
+ const servers = Object.keys(state.config.mcpServers).filter((name) => (!server || name === server) && !isServerDisabled(state.config.mcpServers[name]));
84
+ const knownServers = servers.filter((name) => state.toolMetadata.has(name));
85
+ const unknownServers = servers.filter((name) => !state.toolMetadata.has(name));
82
86
  return { complete: unknownServers.length === 0, knownServers, unknownServers };
83
87
  }
84
88
  export function findToolByName(metadata, toolName) {
85
- if (!metadata)
89
+ if (!metadata) {
86
90
  return undefined;
87
- const exact = metadata.filter(m => m.name === toolName);
88
- if (exact.length > 0)
91
+ }
92
+ const exact = metadata.filter((m) => m.name === toolName);
93
+ if (exact.length > 0) {
89
94
  return exact.length === 1 ? exact[0] : undefined;
95
+ }
90
96
  const normalized = toolName.replace(/-/g, "_");
91
- const matches = metadata.filter(m => m.name.replace(/-/g, "_") === normalized);
97
+ const matches = metadata.filter((m) => m.name.replace(/-/g, "_") === normalized);
92
98
  return matches.length === 1 ? matches[0] : undefined;
93
99
  }
94
100
  export function formatSchema(schema, indent = " ") {
@@ -96,9 +102,14 @@ export function formatSchema(schema, indent = " ") {
96
102
  return `${indent}(no schema)`;
97
103
  }
98
104
  const s = schema;
99
- if (s.type === "object" && s.properties && typeof s.properties === "object" && !Array.isArray(s.properties)) {
105
+ if (s.type === "object" &&
106
+ s.properties &&
107
+ typeof s.properties === "object" &&
108
+ !Array.isArray(s.properties)) {
100
109
  const props = s.properties;
101
- const required = Array.isArray(s.required) ? s.required.filter((name) => typeof name === "string") : [];
110
+ const required = Array.isArray(s.required)
111
+ ? s.required.filter((name) => typeof name === "string")
112
+ : [];
102
113
  if (Object.keys(props).length === 0) {
103
114
  return `${indent}(no parameters)`;
104
115
  }
@@ -125,10 +136,12 @@ function formatProperty(name, schema, required, indent) {
125
136
  const s = schema;
126
137
  const parts = [`${indent}${name}`];
127
138
  const typeStr = formatType(s);
128
- if (typeStr)
139
+ if (typeStr) {
129
140
  parts.push(`(${typeStr})`);
130
- if (required)
141
+ }
142
+ if (required) {
131
143
  parts.push("*required*");
144
+ }
132
145
  appendSchemaAnnotations(parts, s);
133
146
  return [parts.join(" "), ...formatNestedSchema(s, `${indent} `)];
134
147
  }
@@ -143,8 +156,12 @@ function formatNestedSchema(schema, indent) {
143
156
  if (schema.items !== undefined) {
144
157
  lines.push(...formatProperty("items", schema.items, false, indent));
145
158
  }
146
- if (schema.properties && typeof schema.properties === "object" && !Array.isArray(schema.properties)) {
147
- const required = Array.isArray(schema.required) ? schema.required.filter((name) => typeof name === "string") : [];
159
+ if (schema.properties &&
160
+ typeof schema.properties === "object" &&
161
+ !Array.isArray(schema.properties)) {
162
+ const required = Array.isArray(schema.required)
163
+ ? schema.required.filter((name) => typeof name === "string")
164
+ : [];
148
165
  for (const [name, propSchema] of Object.entries(schema.properties)) {
149
166
  lines.push(...formatProperty(name, propSchema, required.includes(name), indent));
150
167
  }
@@ -172,15 +189,17 @@ function formatType(schema) {
172
189
  return `const ${JSON.stringify(schema.const)}`;
173
190
  }
174
191
  if (Array.isArray(schema.enum)) {
175
- return `enum: ${schema.enum.map(v => JSON.stringify(v)).join(", ")}`;
192
+ return `enum: ${schema.enum.map((v) => JSON.stringify(v)).join(", ")}`;
176
193
  }
177
194
  if (Array.isArray(schema.type)) {
178
- return schema.type.map(type => String(type)).join(" | ");
195
+ return schema.type.map((type) => String(type)).join(" | ");
179
196
  }
180
197
  if (schema.type) {
181
198
  return String(schema.type);
182
199
  }
183
- if (schema.properties && typeof schema.properties === "object" && !Array.isArray(schema.properties)) {
200
+ if (schema.properties &&
201
+ typeof schema.properties === "object" &&
202
+ !Array.isArray(schema.properties)) {
184
203
  return "object";
185
204
  }
186
205
  if (schema.items !== undefined) {
@@ -192,7 +211,16 @@ function appendSchemaAnnotations(parts, schema) {
192
211
  if (schema.description && typeof schema.description === "string") {
193
212
  parts.push(`- ${schema.description}`);
194
213
  }
195
- for (const key of ["minLength", "maxLength", "minimum", "maximum", "minItems", "maxItems", "format", "pattern"]) {
214
+ for (const key of [
215
+ "minLength",
216
+ "maxLength",
217
+ "minimum",
218
+ "maximum",
219
+ "minItems",
220
+ "maxItems",
221
+ "format",
222
+ "pattern",
223
+ ]) {
196
224
  if (schema[key] !== undefined) {
197
225
  parts.push(`[${key}: ${JSON.stringify(schema[key])}]`);
198
226
  }
@@ -4,13 +4,16 @@ import { Type } from "typebox";
4
4
  import { toolErrorOverride } from "./error-signal.js";
5
5
  /** Overflow/not-run outcomes are receipts, never a fabricated server-schema result. */
6
6
  export function nativeMcpOutputSchema(schema) {
7
- return Type.Union([Type.Unsafe(schema), Type.Object({
7
+ return Type.Union([
8
+ Type.Unsafe(schema),
9
+ Type.Object({
8
10
  mcpAdapter: Type.Object({
9
11
  omitted: Type.Literal(true),
10
12
  resultRef: Type.Optional(Type.String()),
11
13
  error: Type.Optional(Type.String()),
12
14
  }),
13
- })]);
15
+ }),
16
+ ]);
14
17
  }
15
18
  export function nativeMcpResult(result, structured = false) {
16
19
  const { details } = result;
@@ -18,16 +21,30 @@ export function nativeMcpResult(result, structured = false) {
18
21
  return {
19
22
  ...result,
20
23
  ...toolErrorOverride(details),
21
- ...(structured ? { structuredContent: !details.outputGuard && (!details.error || details.error === "tool_error") && raw?.structuredContent !== undefined
22
- ? raw.structuredContent
23
- : { mcpAdapter: { omitted: true, ...(typeof details.resultRef === "string" ? { resultRef: details.resultRef } : {}), ...(typeof details.error === "string" ? { error: details.error } : {}) } } } : {}),
24
+ ...(structured
25
+ ? {
26
+ structuredContent: !details.outputGuard &&
27
+ (!details.error || details.error === "tool_error") &&
28
+ raw?.structuredContent !== undefined
29
+ ? raw.structuredContent
30
+ : {
31
+ mcpAdapter: {
32
+ omitted: true,
33
+ ...(typeof details.resultRef === "string"
34
+ ? { resultRef: details.resultRef }
35
+ : {}),
36
+ ...(typeof details.error === "string" ? { error: details.error } : {}),
37
+ },
38
+ },
39
+ }
40
+ : {}),
24
41
  };
25
42
  }
26
43
  /**
27
44
  * Transform MCP content types to Pi content blocks.
28
45
  */
29
46
  export function transformMcpContent(content) {
30
- return content.map(c => {
47
+ return content.map((c) => {
31
48
  if (c.type === "text") {
32
49
  return { type: "text", text: c.text ?? "" };
33
50
  }
@@ -40,7 +57,8 @@ export function transformMcpContent(content) {
40
57
  }
41
58
  if (c.type === "resource") {
42
59
  const resourceUri = c.resource?.uri ?? "(no URI)";
43
- const resourceContent = c.resource?.text ?? (c.resource?.blob !== undefined ? "[Binary content; not rendered]" : "(no content)");
60
+ const resourceContent = c.resource?.text ??
61
+ (c.resource?.blob !== undefined ? "[Binary content; not rendered]" : "(no content)");
44
62
  return {
45
63
  type: "text",
46
64
  text: `[Resource: ${resourceUri}]\n${resourceContent}`,
@@ -74,10 +92,14 @@ export function resolveMcpResultContent(result) {
74
92
  /** Persist content the model cannot consume; never imply an audio/blob payload was rendered. */
75
93
  export function renderMcpResultContent(result, payloadFiles = []) {
76
94
  const content = Array.isArray(result.contents)
77
- ? result.contents.map(resource => typeof resource.text === "string" ? { type: "text", text: resource.text } : { type: "resource", resource })
78
- : Array.isArray(result.content) ? result.content : [];
95
+ ? result.contents.map((resource) => typeof resource.text === "string"
96
+ ? { type: "text", text: resource.text }
97
+ : { type: "resource", resource })
98
+ : Array.isArray(result.content)
99
+ ? result.content
100
+ : [];
79
101
  const rendered = content.map((block, index) => {
80
- const file = payloadFiles.find(file => file.index === index);
102
+ const file = payloadFiles.find((file) => file.index === index);
81
103
  return file ? { type: "text", text: formatMcpPayloadFile(file) } : block;
82
104
  });
83
105
  return resolveMcpResultContent({ ...result, content: rendered });