@fitchmultz/pi-mcp-adapter 5.0.0 → 6.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 (73) hide show
  1. package/CHANGELOG.md +25 -1
  2. package/OAUTH.md +3 -3
  3. package/README.md +153 -85
  4. package/app-bridge.bundle.js +75 -33
  5. package/commands.ts +9 -6
  6. package/direct-tools.ts +25 -358
  7. package/dist/app-bridge.bundle.js +75 -33
  8. package/dist/commands.js +9 -6
  9. package/dist/direct-tools.d.ts +2 -4
  10. package/dist/direct-tools.js +21 -319
  11. package/dist/gateway-arguments.d.ts +33 -0
  12. package/dist/gateway-arguments.js +63 -0
  13. package/dist/index.js +181 -276
  14. package/dist/init.js +6 -20
  15. package/dist/mcp-code.d.ts +2 -1
  16. package/dist/mcp-code.js +87 -41
  17. package/dist/mcp-output-guard.d.ts +57 -2
  18. package/dist/mcp-output-guard.js +129 -24
  19. package/dist/mcp-panel.d.ts +1 -1
  20. package/dist/mcp-panel.js +34 -97
  21. package/dist/mcp-script-worker.mjs +5 -2
  22. package/dist/mcp-status.js +2 -1
  23. package/dist/metadata-cache.d.ts +1 -1
  24. package/dist/metadata-cache.js +25 -88
  25. package/dist/prompts.d.ts +1 -1
  26. package/dist/prompts.js +1 -1
  27. package/dist/proxy-modes.d.ts +40 -6
  28. package/dist/proxy-modes.js +270 -485
  29. package/dist/resource-tools.d.ts +15 -0
  30. package/dist/resource-tools.js +26 -1
  31. package/dist/sampling-handler.js +4 -1
  32. package/dist/search-ranking.d.ts +0 -2
  33. package/dist/search-ranking.js +66 -137
  34. package/dist/state.d.ts +1 -1
  35. package/dist/tool-loader.d.ts +32 -0
  36. package/dist/tool-loader.js +152 -0
  37. package/dist/tool-metadata.d.ts +67 -0
  38. package/dist/tool-metadata.js +23 -17
  39. package/dist/tool-registrar.d.ts +4 -4
  40. package/dist/tool-registrar.js +18 -13
  41. package/dist/tool-result-renderer.d.ts +3 -0
  42. package/dist/tool-result-renderer.js +8 -3
  43. package/dist/types.d.ts +11 -36
  44. package/dist/utils.js +2 -4
  45. package/gateway-arguments.ts +73 -0
  46. package/index.ts +166 -299
  47. package/init.ts +6 -20
  48. package/mcp-code.ts +71 -45
  49. package/mcp-output-guard.ts +145 -27
  50. package/mcp-panel.ts +38 -109
  51. package/mcp-script-worker.mjs +5 -2
  52. package/mcp-status.ts +2 -1
  53. package/metadata-cache.ts +31 -93
  54. package/package.json +14 -9
  55. package/prompts.ts +1 -1
  56. package/proxy-modes.ts +265 -517
  57. package/resource-tools.ts +36 -1
  58. package/sampling-handler.ts +4 -1
  59. package/scripts/build-app-bridge.mjs +15 -0
  60. package/scripts/build.mjs +3 -2
  61. package/scripts/prepare.mjs +1 -0
  62. package/search-ranking.ts +71 -135
  63. package/skills/mcp-scripting/SKILL.md +67 -20
  64. package/state.ts +1 -1
  65. package/tool-loader.ts +153 -0
  66. package/tool-metadata.ts +22 -17
  67. package/tool-registrar.ts +18 -13
  68. package/tool-result-renderer.ts +10 -2
  69. package/types.ts +15 -40
  70. package/utils.ts +2 -4
  71. package/dist/ts-shape.d.ts +0 -2
  72. package/dist/ts-shape.js +0 -147
  73. package/ts-shape.ts +0 -145
package/mcp-panel.ts CHANGED
@@ -1,11 +1,9 @@
1
1
  import { matchesKey, truncateToWidth, visibleWidth } from "@earendil-works/pi-tui";
2
2
  import { copyToClipboard } from "@earendil-works/pi-coding-agent";
3
3
  import { createPanelKeys, type PanelKeybindings, type PanelKeys } from "./panel-keys.ts";
4
- import { isServerDisabled, isToolAllowed } from "./types.ts";
5
- import type { McpConfig, McpPanelCallbacks, McpPanelResult, ServerProvenance, ToolPrefix } from "./types.ts";
6
- import { resourceNameToToolName } from "./resource-tools.ts";
4
+ import type { McpConfig, McpPanelCallbacks, McpPanelResult, ServerEntry, ServerProvenance, ToolPrefix } from "./types.ts";
7
5
  import { sanitizeTerminalText, stripOscSequences } from "./utils.ts";
8
- import type { MetadataCache, ServerCacheEntry, CachedTool } from "./metadata-cache.ts";
6
+ import { reconstructToolMetadata, type MetadataCache, type ServerCacheEntry, type CachedTool } from "./metadata-cache.ts";
9
7
 
10
8
  interface PanelTheme {
11
9
  border: string;
@@ -130,9 +128,9 @@ interface ServerState {
130
128
  expanded: boolean;
131
129
  source: "user" | "project" | "import";
132
130
  importKind?: string;
133
- includeTools?: string[];
134
- excludeTools?: string[];
135
- exposeResources: boolean;
131
+ definition: ServerEntry;
132
+ toolFilter: true | string[] | false;
133
+ resourceCount: number;
136
134
  connectionStatus: ConnectionStatus;
137
135
  failureMessage?: string | null;
138
136
  tools: ToolState[];
@@ -197,61 +195,24 @@ class McpPanel {
197
195
  toolFilter = globalDirect;
198
196
  }
199
197
 
200
- const tools: ToolState[] = [];
201
- if (serverCache && !this.authOnly && !isServerDisabled(definition)) {
202
- for (const tool of serverCache.tools ?? []) {
203
- if (!isToolAllowed(tool.name, serverName, this.prefix, definition.includeTools, definition.excludeTools)) {
204
- continue;
205
- }
206
-
207
- const isDirect = toolFilter === true || (Array.isArray(toolFilter) && toolFilter.includes(tool.name));
208
- tools.push({
209
- name: tool.name,
210
- description: tool.description ?? "",
211
- isDirect,
212
- wasDirect: isDirect,
213
- estimatedTokens: estimateTokens(tool),
214
- });
215
- }
216
- if (definition.exposeResources !== false) {
217
- for (const resource of serverCache.resources ?? []) {
218
- const baseName = `read_${resourceNameToToolName(resource.name)}`;
219
- if (!isToolAllowed(baseName, serverName, this.prefix, definition.includeTools, definition.excludeTools)) {
220
- continue;
221
- }
222
-
223
- const isDirect = toolFilter === true || (Array.isArray(toolFilter) && toolFilter.includes(baseName));
224
- const ct: CachedTool = {
225
- name: baseName,
226
- ...(resource.description !== undefined ? { description: resource.description } : {}),
227
- };
228
- tools.push({
229
- name: baseName,
230
- description: resource.description ?? `Read resource: ${resource.uri}`,
231
- isDirect,
232
- wasDirect: isDirect,
233
- estimatedTokens: estimateTokens(ct),
234
- });
235
- }
236
- }
237
- }
238
-
239
198
  const status = callbacks.getConnectionStatus(serverName);
240
199
  const failureMessage = callbacks.getFailureMessage?.(serverName) ?? null;
241
200
 
242
- this.servers.push({
201
+ const server: ServerState = {
243
202
  name: serverName,
244
203
  expanded: false,
245
204
  source: prov?.kind ?? "user",
246
205
  ...(prov?.importKind !== undefined ? { importKind: prov.importKind } : {}),
247
- ...(definition.includeTools !== undefined ? { includeTools: definition.includeTools } : {}),
248
- ...(definition.excludeTools !== undefined ? { excludeTools: definition.excludeTools } : {}),
249
- exposeResources: definition.exposeResources !== false,
206
+ definition,
207
+ toolFilter,
208
+ resourceCount: 0,
250
209
  connectionStatus: status,
251
210
  failureMessage,
252
- tools,
211
+ tools: [],
253
212
  hasCachedData: !!serverCache,
254
- });
213
+ };
214
+ this.servers.push(server);
215
+ if (serverCache && !this.authOnly) this.rebuildServerTools(server, serverCache);
255
216
  }
256
217
 
257
218
  this.rebuildVisibleItems();
@@ -565,7 +526,6 @@ class McpPanel {
565
526
  if (entry) {
566
527
  this.rebuildServerTools(server, entry);
567
528
  }
568
- server.hasCachedData = true;
569
529
  }
570
530
  if (options.afterAuth) {
571
531
  this.authNotice = connected && server.connectionStatus === "connected"
@@ -637,50 +597,22 @@ class McpPanel {
637
597
  }
638
598
 
639
599
  private rebuildServerTools(server: ServerState, entry: ServerCacheEntry): void {
640
- const existingState = new Map<string, boolean>();
641
- for (const t of server.tools) existingState.set(t.name, t.isDirect);
642
-
643
- const newTools: ToolState[] = [];
644
- for (const tool of entry.tools ?? []) {
645
- if (!isToolAllowed(tool.name, server.name, this.prefix, server.includeTools, server.excludeTools)) {
646
- continue;
647
- }
648
-
649
- const prev = existingState.get(tool.name);
650
- const isDirect = prev !== undefined ? prev : false;
651
- newTools.push({
652
- name: tool.name,
653
- description: tool.description ?? "",
654
- isDirect,
655
- wasDirect: prev !== undefined ? server.tools.find((t) => t.name === tool.name)?.wasDirect ?? false : false,
600
+ const existingState = new Map(server.tools.map(tool => [tool.name, tool]));
601
+ const metadata = reconstructToolMetadata(server.name, entry, this.prefix, server.definition);
602
+ server.resourceCount = metadata.filter(tool => tool.resourceUri !== undefined).length;
603
+ server.tools = metadata.filter(tool => tool.resourceUri === undefined).map(tool => {
604
+ const previous = existingState.get(tool.originalName);
605
+ const pinned = server.toolFilter === true
606
+ || (Array.isArray(server.toolFilter) && server.toolFilter.includes(tool.originalName));
607
+ return {
608
+ name: tool.originalName,
609
+ description: tool.description,
610
+ isDirect: previous?.isDirect ?? pinned,
611
+ wasDirect: previous?.wasDirect ?? pinned,
656
612
  estimatedTokens: estimateTokens(tool),
657
- });
658
- }
659
-
660
- if (server.exposeResources) {
661
- for (const resource of entry.resources ?? []) {
662
- const baseName = `read_${resourceNameToToolName(resource.name)}`;
663
- if (!isToolAllowed(baseName, server.name, this.prefix, server.includeTools, server.excludeTools)) {
664
- continue;
665
- }
666
-
667
- const prev = existingState.get(baseName);
668
- const isDirect = prev !== undefined ? prev : false;
669
- const ct: CachedTool = {
670
- name: baseName,
671
- ...(resource.description !== undefined ? { description: resource.description } : {}),
672
- };
673
- newTools.push({
674
- name: baseName,
675
- description: resource.description ?? `Read resource: ${resource.uri}`,
676
- isDirect,
677
- wasDirect: prev !== undefined ? server.tools.find((t) => t.name === baseName)?.wasDirect ?? false : false,
678
- estimatedTokens: estimateTokens(ct),
679
- });
680
- }
681
- }
682
-
683
- server.tools = newTools;
613
+ };
614
+ });
615
+ server.hasCachedData = true;
684
616
  this.rebuildVisibleItems();
685
617
  this.updateDirty();
686
618
  }
@@ -746,6 +678,9 @@ class McpPanel {
746
678
 
747
679
  if (item.type === "server") {
748
680
  lines.push(row(this.renderServerRow(server, isCursor)));
681
+ if (isCursor && !this.authOnly && server.resourceCount > 0) {
682
+ lines.push(row(fg(t.hint, " Resources: mcp actions resources / read-resource")));
683
+ }
749
684
  if (isCursor && server.connectionStatus === "failed" && server.failureMessage) {
750
685
  for (const line of this.wrapText(sanitizeDisplayText(server.failureMessage), innerW - 6)) {
751
686
  lines.push(row(` ${fg(t.cancel, line)}`));
@@ -796,8 +731,9 @@ class McpPanel {
796
731
  0,
797
732
  );
798
733
  const stats =
799
- directCount > 0 ? `${directCount} direct ~${totalTokens.toLocaleString()} tokens` : "no direct tools";
734
+ directCount > 0 ? `${directCount} pinned ~${totalTokens.toLocaleString()} tokens` : "no pinned tools";
800
735
  lines.push(row(fg(t.description, stats + (this.dirty ? fg(t.needsAuth, " (unsaved)") : ""))));
736
+ lines.push(row(fg(t.hint, "Pins load at startup; discover more with mcp_search.")));
801
737
  }
802
738
  }
803
739
 
@@ -812,7 +748,7 @@ class McpPanel {
812
748
  ]
813
749
  : [
814
750
  italic("↑↓") + " navigate",
815
- italic("space") + " toggle",
751
+ italic("space") + " pin/unpin",
816
752
  italic("⏎") + " expand/auth",
817
753
  italic("ctrl+a") + " auth",
818
754
  italic("ctrl+r") + " reconnect",
@@ -860,7 +796,7 @@ class McpPanel {
860
796
  const statusLabel = this.renderConnectionStatus(server);
861
797
 
862
798
  if (!server.hasCachedData && !this.authOnly) {
863
- return `${prefix} ${nameStr}${importLabel} ${fg(t.description, "(not cached)")}${statusLabel}`;
799
+ return `${prefix} ${nameStr}${importLabel}${statusLabel} ${fg(t.description, "(undiscovered)")}`;
864
800
  }
865
801
 
866
802
  const directCount = server.tools.filter((t) => t.isDirect).length;
@@ -872,17 +808,10 @@ class McpPanel {
872
808
  toggleIcon = fg(t.needsAuth, "◐");
873
809
  }
874
810
 
875
- let toolInfo = "";
876
- if (totalCount > 0) {
877
- toolInfo = `${directCount}/${totalCount}`;
878
- if (directCount > 0) {
879
- const tokens = server.tools.filter((t) => t.isDirect).reduce((s, t) => s + t.estimatedTokens, 0);
880
- toolInfo += ` ~${tokens.toLocaleString()}`;
881
- }
882
- toolInfo = fg(t.description, toolInfo);
883
- }
811
+ const toolInfo = this.authOnly ? "" : fg(t.description,
812
+ `${totalCount} tool${totalCount === 1 ? "" : "s"}, ${directCount} pinned${server.resourceCount > 0 ? `, ${server.resourceCount} resource${server.resourceCount === 1 ? "" : "s"}` : ""}`);
884
813
 
885
- return `${prefix} ${toggleIcon} ${nameStr}${importLabel} ${toolInfo}${statusLabel}`;
814
+ return `${prefix} ${toggleIcon} ${nameStr}${importLabel}${statusLabel} ${toolInfo}`;
886
815
  }
887
816
 
888
817
  private selectedServerHasFailureMessage(): boolean {
@@ -69,7 +69,7 @@ const tools = new Proxy(Object.create(null), {
69
69
  return async (input) => request("search", { input });
70
70
  }
71
71
  if (property === "call") {
72
- return async (path, args) => {
72
+ return async (path, args, server) => {
73
73
  // Invalid paths never reach dispatch and therefore never appear in the call trace.
74
74
  if (typeof path !== "string" || path.trim() === "") {
75
75
  return {
@@ -80,9 +80,12 @@ const tools = new Proxy(Object.create(null), {
80
80
  },
81
81
  };
82
82
  }
83
- return request("call", { path, args });
83
+ if (server !== undefined && (typeof server !== "string" || !server)) return { ok: false, error: { code: "invalid_arguments", message: "server must be a non-empty string" } };
84
+ return request("call", { path, args, server });
84
85
  };
85
86
  }
87
+ if (property === "resources" || property === "listResources") return async (input) => request("resources", { input });
88
+ if (property === "readResource" || property === "readResult") return async (input) => request(property, { input });
86
89
  if (property === "describe") {
87
90
  return async (input) => request("describe", { input });
88
91
  }
package/mcp-status.ts CHANGED
@@ -33,7 +33,7 @@ export function createMcpStatusSnapshot(state: McpExtensionState): McpStatusSnap
33
33
  const disabled = definition?.disabled === true;
34
34
  const connection = disabled ? undefined : state.manager.getConnection(name);
35
35
  const metadata = disabled ? undefined : state.toolMetadata.get(name);
36
- const toolCount = metadata?.length ?? (connection?.status === "connected" ? connection.tools.length : 0);
36
+ const toolCount = metadata?.filter(tool => !tool.resourceUri).length ?? (connection?.status === "connected" ? connection.tools.length : 0);
37
37
  const resourceCount = disabled
38
38
  ? undefined
39
39
  : state.resourceCounts?.get(name) ?? (connection?.status === "connected" ? connection.resources.length : undefined);
@@ -60,6 +60,7 @@ export function createMcpStatusSnapshot(state: McpExtensionState): McpStatusSnap
60
60
  name,
61
61
  status,
62
62
  toolCount,
63
+ catalogKnown: metadata !== undefined || connection?.status === "connected",
63
64
  ...(resourceCount !== undefined ? { resourceCount } : {}),
64
65
  ...(status === "failed" && failedAgoSeconds !== undefined ? { failedAgoSeconds } : {}),
65
66
  disabled,
package/metadata-cache.ts CHANGED
@@ -3,7 +3,6 @@ import { existsSync, readFileSync, writeFileSync, renameSync, mkdirSync } from "
3
3
  import { dirname } from "node:path";
4
4
  import { getAdapterPath } from "./agent-dir.ts";
5
5
  import { createHash } from "node:crypto";
6
- import { getToolUiResourceUri } from "@modelcontextprotocol/ext-apps/app-bridge";
7
6
  import type {
8
7
  CachedPrompt,
9
8
  CachedResource,
@@ -19,18 +18,17 @@ import type {
19
18
  ToolMetadata,
20
19
  PromptMetadata,
21
20
  } from "./types.ts";
22
- import { formatPromptCommandName, formatToolName, isServerDisabled, isToolAllowed, resolveToolPrefix, type ToolPrefix } from "./types.ts";
23
- import { resourceNameToToolName } from "./resource-tools.ts";
21
+ import { formatPromptCommandName, isServerDisabled, resolveToolPrefix, type ToolPrefix } from "./types.ts";
22
+ import { buildToolMetadata } from "./tool-metadata.ts";
24
23
  import {
25
- extractToolUiStreamMode,
26
24
  interpolateEnvRecord,
27
25
  resolveBearerToken,
28
26
  resolveConfigPath,
29
27
  resolveServerUrl,
30
28
  } from "./utils.ts";
31
- import { extractUiToolVisibility, isUiToolVisibleToModel } from "./ui-tool-visibility.ts";
32
29
 
33
- const CACHE_VERSION = 1;
30
+ // Version 2 stores raw descriptors; version 1 readers cannot enforce raw UI visibility.
31
+ const CACHE_VERSION = 2;
34
32
  const CACHE_MAX_AGE_MS = 7 * 24 * 60 * 60 * 1000;
35
33
 
36
34
  export type { CachedPrompt, CachedResource, CachedTool, MetadataCache, ServerCacheEntry } from "./types.ts";
@@ -46,7 +44,7 @@ export function loadMetadataCache(enabled = true): MetadataCache | null {
46
44
  try {
47
45
  const raw = JSON.parse(readFileSync(cachePath, "utf-8"));
48
46
  if (!raw || typeof raw !== "object") return null;
49
- if (raw.version !== CACHE_VERSION) return null;
47
+ if (raw.version !== 1 && raw.version !== CACHE_VERSION) return null;
50
48
  if (!raw.servers || typeof raw.servers !== "object") return null;
51
49
  return raw as MetadataCache;
52
50
  } catch {
@@ -64,7 +62,7 @@ export function saveMetadataCache(cache: MetadataCache, enabled = true): void {
64
62
  try {
65
63
  if (existsSync(cachePath)) {
66
64
  const existing = JSON.parse(readFileSync(cachePath, "utf-8")) as MetadataCache;
67
- if (existing && existing.version === CACHE_VERSION && existing.servers) {
65
+ if (existing && (existing.version === 1 || existing.version === CACHE_VERSION) && existing.servers) {
68
66
  merged.servers = { ...existing.servers };
69
67
  }
70
68
  }
@@ -178,92 +176,40 @@ export function reconstructToolMetadata(
178
176
  serverName: string,
179
177
  entry: ServerCacheEntry,
180
178
  prefix: ToolPrefix,
181
- definition: Pick<ServerEntry, "exposeResources" | "includeTools" | "excludeTools" | "toolPrefix">
179
+ definition: Pick<ServerEntry, "exposeResources" | "includeTools" | "excludeTools" | "toolPrefix" | "disabled">
182
180
  ): ToolMetadata[] {
183
- const metadata: ToolMetadata[] = [];
184
- const seenNames = new Set<string>();
185
- const effectivePrefix = resolveToolPrefix(definition, prefix);
186
-
187
- for (const tool of entry.tools ?? []) {
188
- if (!tool?.name) continue;
189
- if (!isUiToolVisibleToModel(tool.uiVisibility)) {
190
- continue;
191
- }
192
- if (!isToolAllowed(tool.name, serverName, effectivePrefix, definition.includeTools, definition.excludeTools)) {
193
- continue;
194
- }
195
-
196
- const name = formatToolName(tool.name, serverName, effectivePrefix);
197
- if (seenNames.has(name)) {
198
- continue;
199
- }
200
- seenNames.add(name);
201
-
202
- metadata.push({
203
- name,
204
- originalName: tool.name,
205
- description: tool.description ?? "",
206
- ...(tool.inputSchema !== undefined ? { inputSchema: tool.inputSchema } : {}),
207
- ...(tool.annotations !== undefined ? { annotations: tool.annotations } : {}),
208
- ...(tool.uiResourceUri !== undefined ? { uiResourceUri: tool.uiResourceUri } : {}),
209
- ...(tool.uiVisibility !== undefined ? { uiVisibility: tool.uiVisibility } : {}),
210
- ...(tool.uiStreamMode !== undefined ? { uiStreamMode: tool.uiStreamMode } : {}),
211
- });
212
- }
213
-
214
- if (definition.exposeResources !== false) {
215
- for (const resource of entry.resources ?? []) {
216
- if (!resource?.name || !resource?.uri) continue;
217
- const baseName = `read_${resourceNameToToolName(resource.name)}`;
218
- if (!isToolAllowed(baseName, serverName, effectivePrefix, definition.includeTools, definition.excludeTools)) {
219
- continue;
220
- }
221
-
222
- const name = formatToolName(baseName, serverName, effectivePrefix);
223
- if (seenNames.has(name)) {
224
- continue;
225
- }
226
- seenNames.add(name);
181
+ return buildToolMetadata(
182
+ (entry.tools ?? []).filter(tool => tool?.name).map(restoreCachedTool),
183
+ entry.resources ?? [],
184
+ definition,
185
+ serverName,
186
+ prefix,
187
+ ).metadata;
188
+ }
227
189
 
228
- metadata.push({
229
- name,
230
- originalName: baseName,
231
- description: resource.description ?? `Read resource: ${resource.uri}`,
232
- resourceUri: resource.uri,
233
- });
234
- }
190
+ function restoreCachedTool(tool: CachedTool): McpTool {
191
+ const { uiResourceUri, uiVisibility, uiStreamMode, ...descriptor } = tool;
192
+ if (descriptor._meta !== undefined || (uiResourceUri === undefined && uiVisibility === undefined && uiStreamMode === undefined)) {
193
+ return descriptor;
235
194
  }
236
-
237
- return metadata;
195
+ return {
196
+ ...descriptor,
197
+ _meta: {
198
+ ui: {
199
+ ...(uiResourceUri !== undefined ? { resourceUri: uiResourceUri } : {}),
200
+ ...(uiVisibility !== undefined ? { visibility: uiVisibility } : {}),
201
+ ...(uiStreamMode !== undefined ? { "pi-mcp-adapter.streamMode": uiStreamMode } : {}),
202
+ },
203
+ },
204
+ };
238
205
  }
239
206
 
240
207
  export function serializeTools(tools: McpTool[]): CachedTool[] {
241
- return tools
242
- .filter(t => t?.name)
243
- .map(t => {
244
- const uiResourceUri = tryGetToolUiResourceUri(t);
245
- const uiVisibility = extractUiToolVisibility(t._meta);
246
- const uiStreamMode = extractToolUiStreamMode(t._meta);
247
- return {
248
- name: t.name,
249
- ...(t.description !== undefined ? { description: t.description } : {}),
250
- ...(t.inputSchema !== undefined ? { inputSchema: t.inputSchema } : {}),
251
- ...(t.annotations !== undefined ? { annotations: t.annotations } : {}),
252
- ...(uiResourceUri !== undefined ? { uiResourceUri } : {}),
253
- ...(uiVisibility !== undefined ? { uiVisibility } : {}),
254
- ...(uiStreamMode !== undefined ? { uiStreamMode } : {}),
255
- };
256
- });
208
+ return tools.filter(tool => tool?.name);
257
209
  }
258
210
 
259
211
  export function serializeResources(resources: McpResource[]): CachedResource[] {
260
- return resources
261
- .filter(r => r?.name && r?.uri)
262
- .map(r => ({
263
- uri: r.uri,
264
- name: r.name,
265
- ...(r.description !== undefined ? { description: r.description } : {}),
266
- }));
212
+ return resources.filter(resource => resource?.name && resource?.uri);
267
213
  }
268
214
 
269
215
  export function serializePrompts(prompts: McpPrompt[]): CachedPrompt[] {
@@ -323,11 +269,3 @@ function stableStringify(value: unknown): string {
323
269
  const keys = Object.keys(obj).sort();
324
270
  return `{${keys.map(k => `${JSON.stringify(k)}:${stableStringify(obj[k])}`).join(",")}}`;
325
271
  }
326
-
327
- function tryGetToolUiResourceUri(tool: McpTool): string | undefined {
328
- try {
329
- return getToolUiResourceUri({ _meta: tool._meta });
330
- } catch {
331
- return undefined;
332
- }
333
- }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fitchmultz/pi-mcp-adapter",
3
- "version": "5.0.0",
3
+ "version": "6.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",
@@ -30,7 +30,9 @@
30
30
  },
31
31
  "scripts": {
32
32
  "build": "node ./scripts/build.mjs",
33
+ "build:bridge": "node ./scripts/build-app-bridge.mjs",
33
34
  "prepare": "node ./scripts/prepare.mjs",
35
+ "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
36
  "typecheck": "tsc --noEmit",
35
37
  "test": "vitest run",
36
38
  "test:watch": "vitest",
@@ -67,6 +69,7 @@
67
69
  "dist",
68
70
  "docs/client-metadata.json",
69
71
  "scripts/build.mjs",
72
+ "scripts/build-app-bridge.mjs",
70
73
  "scripts/prepare.mjs",
71
74
  "tsconfig.build.json",
72
75
  "tsconfig.json",
@@ -74,6 +77,8 @@
74
77
  "agent-dir.ts",
75
78
  "legacy-migration.ts",
76
79
  "index.ts",
80
+ "tool-loader.ts",
81
+ "gateway-arguments.ts",
77
82
  "error-signal.ts",
78
83
  "state.ts",
79
84
  "utils.ts",
@@ -81,7 +86,6 @@
81
86
  "runtime-owner.ts",
82
87
  "checkpoint.ts",
83
88
  "tool-metadata.ts",
84
- "ts-shape.ts",
85
89
  "search-ranking.ts",
86
90
  "tool-approval.ts",
87
91
  "init.ts",
@@ -136,21 +140,21 @@
136
140
  "dependencies": {
137
141
  "@modelcontextprotocol/client": "2.0.0",
138
142
  "@modelcontextprotocol/core": "2.0.0",
139
- "@modelcontextprotocol/ext-apps": "^1.7.5",
140
- "@modelcontextprotocol/sdk": "^1.30.0",
143
+ "@modelcontextprotocol/ext-apps": "2.0.0",
141
144
  "@napi-rs/keyring": "^1.3.0",
142
145
  "cross-spawn": "^7.0.6",
146
+ "minisearch": "^7.2.0",
143
147
  "open": "^10.2.0",
144
148
  "smol-toml": "^1.8.0",
145
149
  "strip-json-comments": "^5.0.3",
146
- "zod": "^3.25.0 || ^4.0.0"
150
+ "zod": "^4.6.5"
147
151
  },
148
152
  "peerDependencies": {
149
153
  "@earendil-works/pi-ai": "*",
150
154
  "@earendil-works/pi-coding-agent": "*",
151
155
  "@earendil-works/pi-tui": "*",
152
156
  "typebox": "*",
153
- "zod": "^3.25.0 || ^4.0.0"
157
+ "zod": "^4.6.5"
154
158
  },
155
159
  "peerDependenciesMeta": {
156
160
  "@earendil-works/pi-ai": {
@@ -167,13 +171,14 @@
167
171
  }
168
172
  },
169
173
  "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",
174
+ "@earendil-works/pi-ai": "0.87.0",
175
+ "@earendil-works/pi-coding-agent": "0.87.0",
176
+ "@earendil-works/pi-tui": "0.87.0",
173
177
  "@modelcontextprotocol/conformance": "0.1.16",
174
178
  "@modelcontextprotocol/server": "2.0.0",
175
179
  "@types/cross-spawn": "6.0.6",
176
180
  "@types/node": "20.19.43",
181
+ "esbuild": "^0.28.2",
177
182
  "tsx": "^4.21.0",
178
183
  "typebox": "1.3.7",
179
184
  "typescript": "5.9.3",
package/prompts.ts CHANGED
@@ -12,7 +12,7 @@ import { truncateAtWord } from "./utils.ts";
12
12
 
13
13
  /**
14
14
  * Resolve prompt metadata for slash-command registration at extension load
15
- * time. Mirrors `resolveDirectTools`: reads the persistent metadata cache so
15
+ * time. Like typed tool registration, reads the persistent metadata cache so
16
16
  * commands are available before any server connects.
17
17
  */
18
18
  export function resolveCachedPrompts(config: McpConfig, cacheEnabled = true): PromptMetadata[] {