@fitchmultz/pi-mcp-adapter 5.0.0 → 7.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (129) hide show
  1. package/CHANGELOG.md +50 -0
  2. package/OAUTH.md +3 -3
  3. package/README.md +160 -88
  4. package/dist/app-bridge.bundle.js +75 -33
  5. package/dist/commands.js +9 -6
  6. package/dist/config.js +41 -16
  7. package/dist/direct-tools.d.ts +2 -4
  8. package/dist/direct-tools.js +21 -319
  9. package/dist/gateway-arguments.d.ts +33 -0
  10. package/dist/gateway-arguments.js +63 -0
  11. package/dist/index.js +181 -276
  12. package/dist/init.js +6 -20
  13. package/dist/lifecycle.d.ts +2 -2
  14. package/dist/lifecycle.js +2 -2
  15. package/dist/mcp-auth-flow.js +2 -2
  16. package/dist/mcp-auth.d.ts +1 -1
  17. package/dist/mcp-auth.js +3 -3
  18. package/dist/mcp-code.d.ts +7 -6
  19. package/dist/mcp-code.js +98 -51
  20. package/dist/mcp-oauth-provider.d.ts +5 -5
  21. package/dist/mcp-oauth-provider.js +5 -5
  22. package/dist/mcp-output-guard.d.ts +57 -2
  23. package/dist/mcp-output-guard.js +129 -24
  24. package/dist/mcp-panel.d.ts +3 -3
  25. package/dist/mcp-panel.js +36 -99
  26. package/dist/mcp-script-worker.mjs +5 -2
  27. package/dist/mcp-setup-panel.d.ts +5 -4
  28. package/dist/mcp-setup-panel.js +15 -7
  29. package/dist/mcp-status.js +2 -1
  30. package/dist/mcp-trace.d.ts +1 -1
  31. package/dist/mcp-trace.js +1 -1
  32. package/dist/metadata-cache.d.ts +1 -1
  33. package/dist/metadata-cache.js +25 -88
  34. package/dist/npx-resolver.js +8 -4
  35. package/dist/prompts.d.ts +1 -1
  36. package/dist/prompts.js +1 -1
  37. package/dist/proxy-modes.d.ts +41 -7
  38. package/dist/proxy-modes.js +275 -490
  39. package/dist/resource-tools.d.ts +15 -0
  40. package/dist/resource-tools.js +26 -1
  41. package/dist/sampling-handler.js +5 -1
  42. package/dist/search-ranking.d.ts +0 -2
  43. package/dist/search-ranking.js +70 -137
  44. package/dist/server-manager.d.ts +2 -2
  45. package/dist/server-manager.js +44 -27
  46. package/dist/session-recovery.d.ts +7 -4
  47. package/dist/session-recovery.js +76 -10
  48. package/dist/state.d.ts +1 -1
  49. package/dist/tool-loader.d.ts +32 -0
  50. package/dist/tool-loader.js +152 -0
  51. package/dist/tool-metadata.d.ts +67 -0
  52. package/dist/tool-metadata.js +23 -17
  53. package/dist/tool-registrar.d.ts +4 -4
  54. package/dist/tool-registrar.js +18 -13
  55. package/dist/tool-result-renderer.d.ts +6 -3
  56. package/dist/tool-result-renderer.js +11 -6
  57. package/dist/types.d.ts +11 -36
  58. package/dist/ui-resource-handler.d.ts +2 -2
  59. package/dist/ui-resource-handler.js +2 -2
  60. package/dist/ui-server.js +4 -3
  61. package/dist/ui-stream-types.d.ts +8 -8
  62. package/dist/unix-socket-transport.d.ts +1 -1
  63. package/dist/unix-socket-transport.js +1 -1
  64. package/dist/utils.d.ts +1 -0
  65. package/dist/utils.js +16 -4
  66. package/package.json +37 -96
  67. package/skills/mcp-scripting/SKILL.md +67 -20
  68. package/abort.ts +0 -34
  69. package/agent-dir.ts +0 -24
  70. package/app-bridge.bundle.js +0 -67
  71. package/banner.png +0 -0
  72. package/checkpoint.ts +0 -55
  73. package/commands.ts +0 -619
  74. package/config.ts +0 -1204
  75. package/consent-manager.ts +0 -25
  76. package/direct-tools.ts +0 -408
  77. package/dist/ts-shape.d.ts +0 -2
  78. package/dist/ts-shape.js +0 -147
  79. package/elicitation-handler.ts +0 -349
  80. package/error-signal.ts +0 -21
  81. package/glimpse-ui.ts +0 -80
  82. package/host-html-template.ts +0 -456
  83. package/index.ts +0 -900
  84. package/init.ts +0 -627
  85. package/legacy-migration.ts +0 -47
  86. package/lifecycle.ts +0 -165
  87. package/logger.ts +0 -127
  88. package/mcp-auth-flow.ts +0 -975
  89. package/mcp-auth.ts +0 -793
  90. package/mcp-callback-server.ts +0 -450
  91. package/mcp-code.ts +0 -386
  92. package/mcp-keyring-helper.cjs +0 -89
  93. package/mcp-oauth-provider.ts +0 -774
  94. package/mcp-output-guard.ts +0 -412
  95. package/mcp-panel.ts +0 -978
  96. package/mcp-script-worker.mjs +0 -128
  97. package/mcp-setup-panel.ts +0 -666
  98. package/mcp-status.ts +0 -109
  99. package/mcp-trace.ts +0 -295
  100. package/metadata-cache.ts +0 -333
  101. package/npx-resolver.ts +0 -488
  102. package/onboarding-state.ts +0 -58
  103. package/panel-keys.ts +0 -37
  104. package/prompts.ts +0 -337
  105. package/proxy-modes.ts +0 -1361
  106. package/resource-tools.ts +0 -17
  107. package/runtime-owner.ts +0 -123
  108. package/sampling-handler.ts +0 -273
  109. package/scripts/build.mjs +0 -131
  110. package/scripts/prepare.mjs +0 -102
  111. package/search-ranking.ts +0 -196
  112. package/server-manager.ts +0 -1320
  113. package/session-recovery.ts +0 -316
  114. package/state.ts +0 -64
  115. package/tool-approval.ts +0 -78
  116. package/tool-metadata.ts +0 -235
  117. package/tool-registrar.ts +0 -69
  118. package/tool-result-renderer.ts +0 -286
  119. package/ts-shape.ts +0 -145
  120. package/tsconfig.build.json +0 -13
  121. package/tsconfig.json +0 -16
  122. package/types.ts +0 -810
  123. package/ui-resource-handler.ts +0 -249
  124. package/ui-server.ts +0 -808
  125. package/ui-session.ts +0 -568
  126. package/ui-stream-types.ts +0 -89
  127. package/ui-tool-visibility.ts +0 -25
  128. package/unix-socket-transport.ts +0 -85
  129. package/utils.ts +0 -309
package/types.ts DELETED
@@ -1,810 +0,0 @@
1
- // types.ts - Core type definitions
2
- import type { Transport as McpTransport } from "@modelcontextprotocol/client";
3
- import type {
4
- ContentBlock as McpContentBlock,
5
- ListPromptsResult,
6
- ListResourcesResult,
7
- ListToolsResult,
8
- } from "@modelcontextprotocol/client";
9
- import type { TextContent, ImageContent } from "@earendil-works/pi-ai";
10
- import type { ExtensionContext } from "@earendil-works/pi-coding-agent";
11
- import type { UiStreamMode } from "./ui-stream-types.ts";
12
- import type { UiToolVisibility } from "./ui-tool-visibility.ts";
13
-
14
- export type Transport = McpTransport;
15
-
16
- /** Versioned shared-event-bus channel for read-only MCP runtime snapshots. */
17
- export const MCP_STATUS_EVENT = "fitch-mcp-adapter/status/v1";
18
-
19
- export const MCP_STATUS_SNAPSHOT_VERSION = 1 as const;
20
-
21
- export type McpServerRuntimeStatus =
22
- | "connected"
23
- | "cached"
24
- | "failed"
25
- | "needs-auth"
26
- | "not-connected"
27
- | "disabled";
28
-
29
- export interface McpServerStatusSnapshot {
30
- readonly name: string;
31
- readonly status: McpServerRuntimeStatus;
32
- readonly toolCount: number;
33
- readonly resourceCount?: number;
34
- readonly failedAgoSeconds?: number;
35
- readonly disabled: boolean;
36
- }
37
-
38
- export interface McpStatusSnapshot {
39
- readonly version: typeof MCP_STATUS_SNAPSHOT_VERSION;
40
- readonly servers: ReadonlyArray<McpServerStatusSnapshot>;
41
- readonly totalTools: number;
42
- readonly totalResources: number;
43
- readonly connectedCount: number;
44
- readonly disabledCount: number;
45
- }
46
-
47
- // Import sources for config
48
- export type ImportKind =
49
- | "cursor"
50
- | "claude-code"
51
- | "claude-desktop"
52
- | "codex"
53
- | "opencode"
54
- | "windsurf"
55
- | "vscode";
56
-
57
- type SdkTool = ListToolsResult["tools"][number];
58
- type SdkResource = ListResourcesResult["resources"][number];
59
- type SdkPrompt = ListPromptsResult["prompts"][number];
60
- type SdkPromptArgument = NonNullable<SdkPrompt["arguments"]>[number];
61
-
62
- // MCP wire definitions derive their field types from the installed SDK while
63
- // retaining the adapter's deliberately smaller public surface.
64
- export interface McpTool {
65
- name: SdkTool["name"];
66
- title?: SdkTool["title"];
67
- description?: SdkTool["description"];
68
- inputSchema?: SdkTool["inputSchema"]; // JSON Schema
69
- annotations?: SdkTool["annotations"];
70
- _meta?: SdkTool["_meta"];
71
- }
72
-
73
- export interface McpResource {
74
- uri: SdkResource["uri"];
75
- name: SdkResource["name"];
76
- description?: SdkResource["description"];
77
- mimeType?: SdkResource["mimeType"];
78
- _meta?: SdkResource["_meta"];
79
- }
80
-
81
- export interface McpPromptArgument {
82
- name: SdkPromptArgument["name"];
83
- description?: SdkPromptArgument["description"];
84
- required?: SdkPromptArgument["required"];
85
- }
86
-
87
- export interface McpPrompt {
88
- name: SdkPrompt["name"];
89
- title?: SdkPrompt["title"];
90
- description?: SdkPrompt["description"];
91
- arguments?: SdkPrompt["arguments"];
92
- _meta?: SdkPrompt["_meta"];
93
- }
94
-
95
- export interface UiResourceMeta {
96
- csp?: UiResourceCsp;
97
- permissions?: UiResourcePermissions;
98
- domain?: string;
99
- prefersBorder?: boolean;
100
- }
101
-
102
- export interface UiResourceContent {
103
- uri: string;
104
- html: string;
105
- mimeType?: string;
106
- meta: UiResourceMeta;
107
- }
108
-
109
- export interface UiProxyRequestBody<TParams> {
110
- token: string;
111
- params: TParams;
112
- }
113
-
114
- export interface UiProxyResult<T = Record<string, unknown>> {
115
- ok: boolean;
116
- result?: T;
117
- error?: string;
118
- }
119
-
120
- export interface UiResourceCsp {
121
- resourceDomains?: string[];
122
- connectDomains?: string[];
123
- frameDomains?: string[];
124
- baseUriDomains?: string[];
125
- }
126
-
127
- export interface UiResourcePermissions {
128
- camera?: {};
129
- microphone?: {};
130
- geolocation?: {};
131
- clipboardWrite?: {};
132
- }
133
-
134
- export interface UiToolInfo {
135
- id?: string | number;
136
- tool: {
137
- name: string;
138
- description?: string;
139
- inputSchema?: unknown;
140
- };
141
- }
142
-
143
- export interface UiHostContext {
144
- toolInfo?: UiToolInfo;
145
- theme?: "light" | "dark";
146
- styles?: Record<string, unknown>;
147
- displayMode?: UiDisplayMode;
148
- availableDisplayModes?: UiDisplayMode[];
149
- containerDimensions?: {
150
- width?: number;
151
- maxWidth?: number;
152
- height?: number;
153
- maxHeight?: number;
154
- };
155
- [key: string]: unknown;
156
- }
157
-
158
- export type UiDisplayMode = "inline" | "fullscreen" | "pip";
159
-
160
- // Re-export stream types from the shared lightweight module.
161
- // This allows the example package to import stream schemas without pulling the full types.ts dependency graph.
162
- export {
163
- UI_STREAM_HOST_CONTEXT_KEY,
164
- UI_STREAM_REQUEST_META_KEY,
165
- UI_STREAM_RESULT_PATCH_METHOD,
166
- SERVER_STREAM_RESULT_PATCH_METHOD,
167
- UI_STREAM_STRUCTURED_CONTENT_KEY,
168
- uiStreamModeSchema,
169
- visualizationStreamPhaseSchema,
170
- visualizationStreamFrameTypeSchema,
171
- visualizationStreamStatusSchema,
172
- uiStreamHostContextSchema,
173
- visualizationStreamEnvelopeSchema,
174
- uiStreamCallToolResultSchema,
175
- uiStreamResultPatchNotificationSchema,
176
- serverStreamResultPatchNotificationSchema,
177
- getUiStreamHostContext,
178
- getVisualizationStreamEnvelope,
179
- type UiStreamMode,
180
- type VisualizationStreamPhase,
181
- type VisualizationStreamFrameType,
182
- type VisualizationStreamStatus,
183
- type UiStreamHostContext,
184
- type VisualizationStreamEnvelope,
185
- type UiStreamCallToolResult,
186
- type UiStreamResultPatchNotification,
187
- type ServerStreamResultPatchNotification,
188
- type UiStreamSummary,
189
- } from "./ui-stream-types.ts";
190
-
191
- export interface UiMessageParams {
192
- role?: string;
193
- content?: unknown[];
194
- type?: "prompt" | "notify" | "intent" | "message";
195
- message?: string;
196
- prompt?: string;
197
- intent?: string;
198
- params?: Record<string, unknown>;
199
- [key: string]: unknown;
200
- }
201
-
202
- /**
203
- * Extract prompt text from either legacy MCP UI message shapes or native AppBridge user messages.
204
- */
205
- export function extractUiPromptText(params: UiMessageParams): string | undefined {
206
- if (params.type === "prompt" || params.prompt) {
207
- const prompt = params.prompt ?? String(params.message ?? "");
208
- return prompt || undefined;
209
- }
210
-
211
- if (params.role === "user" && Array.isArray(params.content)) {
212
- const text = params.content
213
- .map((block) => (block && typeof block === "object" && "text" in block ? String((block as { text?: unknown }).text ?? "") : ""))
214
- .filter(Boolean)
215
- .join("\n\n");
216
- return text || undefined;
217
- }
218
-
219
- return undefined;
220
- }
221
-
222
- /**
223
- * Structured UI handoff recovered from a canonical prompt envelope.
224
- */
225
- export interface UiPromptHandoff {
226
- intent: string;
227
- params: Record<string, unknown>;
228
- raw: string;
229
- }
230
-
231
- /**
232
- * Parse a canonical named UI handoff encoded as `intent\n{json}`.
233
- */
234
- export function parseUiPromptHandoff(prompt: string): UiPromptHandoff | undefined {
235
- const newlineIndex = prompt.indexOf("\n");
236
- if (newlineIndex <= 0) {
237
- return undefined;
238
- }
239
-
240
- const intent = prompt.slice(0, newlineIndex).trim();
241
- const payloadText = prompt.slice(newlineIndex + 1).trim();
242
- if (!intent || !payloadText) {
243
- return undefined;
244
- }
245
-
246
- if (!/^[A-Za-z][A-Za-z0-9_-]*$/.test(intent)) {
247
- return undefined;
248
- }
249
-
250
- try {
251
- const parsed = JSON.parse(payloadText);
252
- if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
253
- return undefined;
254
- }
255
- return {
256
- intent,
257
- params: parsed as Record<string, unknown>,
258
- raw: prompt,
259
- };
260
- } catch {
261
- return undefined;
262
- }
263
- }
264
-
265
- /**
266
- * Accumulated messages from a UI session.
267
- * Collected during the session and available when it ends.
268
- */
269
- export interface UiSessionMessages {
270
- prompts: string[];
271
- notifications: string[];
272
- intents: Array<{ intent: string; params?: Record<string, unknown> }>;
273
- contexts: UiModelContextUpdate[];
274
- }
275
-
276
- export interface UiModelContextUpdate {
277
- summary: string;
278
- truncated: boolean;
279
- payload?: Record<string, unknown>;
280
- }
281
-
282
- export interface UiModelContextParams {
283
- content?: McpContentBlock[];
284
- structuredContent?: Record<string, unknown>;
285
- }
286
-
287
- export function createUiModelContextUpdate(params: UiModelContextParams, maxChars = 12_000): UiModelContextUpdate | undefined {
288
- const payload = Object.fromEntries(
289
- Object.entries(params).filter(([, value]) => value !== undefined),
290
- );
291
- if (Object.keys(payload).length === 0) return undefined;
292
-
293
- const serialized = JSON.stringify(payload);
294
- if (serialized.length <= maxChars) {
295
- return { payload, summary: serialized, truncated: false };
296
- }
297
-
298
- return {
299
- summary: `${serialized.slice(0, Math.max(0, maxChars - 1))}…`,
300
- truncated: true,
301
- };
302
- }
303
-
304
- export interface UiOpenLinkResult {
305
- isError?: boolean;
306
- [key: string]: unknown;
307
- }
308
-
309
- export interface UiDisplayModeRequest {
310
- mode?: UiDisplayMode;
311
- }
312
-
313
- export interface UiDisplayModeResult {
314
- mode: UiDisplayMode;
315
- [key: string]: unknown;
316
- }
317
-
318
- // Content types from MCP
319
- export interface McpContent {
320
- type: "text" | "image" | "audio" | "resource" | "resource_link";
321
- text?: string;
322
- data?: string;
323
- mimeType?: string;
324
- resource?: {
325
- uri: string;
326
- text?: string;
327
- blob?: string;
328
- };
329
- uri?: string;
330
- name?: string;
331
- description?: string;
332
- }
333
-
334
- // Pi content block type
335
- export type ContentBlock = TextContent | ImageContent;
336
-
337
- /** Native private_key_jwt authentication; public verification keys are provisioned separately. */
338
- export interface OAuthPrivateKeyJwtConfig {
339
- /** PKCS#8 PEM, JWK object/JSON, or an existing environment/!command source. Resolved only when signing. */
340
- privateKey: string | Record<string, unknown>;
341
- /** Asymmetric JOSE algorithm, such as ES256, RS256, PS256 or EdDSA (JWK only). */
342
- algorithm: string;
343
- /** JWT audience override only; defaults to the discovered issuer, then token URL. */
344
- audience?: string;
345
- /** Positive integer assertion lifetime in seconds; native default is 300. */
346
- lifetimeSeconds?: number;
347
- /** Additional claims. Native iss/sub/aud/iat/exp/jti always take precedence. */
348
- claims?: Record<string, unknown>;
349
- }
350
-
351
- /** Enterprise OIDC token exchange. IdP credentials are separate from the MCP client identity. */
352
- export interface OAuthCrossAppAccessConfig {
353
- /** IdP issuer used by native discovery. */
354
- idpUrl: string;
355
- /** IdP client ID, not the outer MCP clientId. */
356
- clientId: string;
357
- /** Existing OIDC ID token; environment/!command sources are resolved for each exchange. */
358
- idToken: string;
359
- /** Optional IdP client_secret_post credential, also resolved lazily. */
360
- clientSecret?: string;
361
- }
362
-
363
- export function isNonInteractiveOAuth(config: OAuthConfig | false | undefined): boolean {
364
- return !!config && (config.grantType === "client_credentials" || config.crossAppAccess !== undefined);
365
- }
366
-
367
- // OAuth configuration (SDK handles discovery and client registration)
368
- export interface OAuthConfig {
369
- /** Skip only authorization-server metadata issuer validation. Defaults to false. */
370
- skipIssuerMetadataValidation?: boolean;
371
- /** OAuth grant type (defaults to authorization_code) */
372
- grantType?: "authorization_code" | "client_credentials";
373
- /** Pre-registered client ID; takes priority over stored clients, CIMD and dynamic registration. */
374
- clientId?: string;
375
- /** Client secret for confidential clients */
376
- clientSecret?: string;
377
- /** Private-key authentication instead of clientSecret; requires clientId or a custom clientMetadataUrl. */
378
- privateKeyJwt?: OAuthPrivateKeyJwtConfig;
379
- /** Noninteractive ID-token -> ID-JAG -> MCP access token flow; omit grantType. */
380
- crossAppAccess?: OAuthCrossAppAccessConfig;
381
- /** Requested OAuth scopes */
382
- scope?: string;
383
- /** Extra authorization URL parameters for provider-specific extensions. Flow-owned parameters cannot be overridden. */
384
- authorizationParams?: Record<string, string>;
385
- /** Exact authorization-code redirect URI for pre-registered clients */
386
- redirectUri?: string;
387
- /** Client display name for dynamic registration */
388
- clientName?: string;
389
- /** Client homepage URI for dynamic registration */
390
- clientUri?: string;
391
- /** Client metadata document URL. Eligible browser clients default to Pi MCP Adapter's shared identity; false opts out for new registrations. Stored clients keep priority. */
392
- clientMetadataUrl?: string | false;
393
- }
394
-
395
- // Server configuration
396
- export interface ServerEntry {
397
- command?: string;
398
- args?: string[];
399
- /** Explicit rmcp-mux Unix-domain socket path. Mutually exclusive with command and url. */
400
- socket?: string;
401
- env?: Record<string, string>;
402
- cwd?: string;
403
- /** HTTP defaults to auto negotiation; stdio/socket default to legacy. */
404
- protocolVersion?: "auto" | "legacy";
405
- /** Trust explicit readOnlyHint/idempotentHint annotations for one modern transport retry. Defaults to false. */
406
- retryOnTransportFailure?: boolean;
407
- // HTTP fields
408
- url?: string;
409
- headers?: Record<string, string>;
410
- /**
411
- * Authentication type:
412
- * - 'oauth' - Use OAuth 2.1 (auto-discovers endpoints, supports dynamic client registration)
413
- * - 'bearer' - Use static Bearer token
414
- * - false - Disable authentication
415
- * If not specified and url is present, OAuth will be auto-detected unless custom headers are configured
416
- */
417
- auth?: "oauth" | "bearer" | false;
418
- bearerToken?: string;
419
- bearerTokenEnv?: string;
420
- /**
421
- * OAuth configuration (optional).
422
- * Eligible browser clients use the shared client metadata document when supported, otherwise dynamic registration.
423
- * Set to false to explicitly disable OAuth for this server.
424
- */
425
- oauth?: OAuthConfig | false;
426
- lifecycle?: "keep-alive" | "lazy" | "lazy-keep-alive" | "eager";
427
- idleTimeout?: number; // minutes, overrides global setting
428
- requestTimeoutMs?: number; // milliseconds, overrides global request timeout when > 0
429
- // Resource handling
430
- exposeResources?: boolean;
431
- // Direct tool registration
432
- directTools?: boolean | string[];
433
- // Override settings.toolPrefix for this server.
434
- toolPrefix?: ToolPrefix;
435
- // Include/exclude specific MCP tools/resources by original or prefixed name
436
- includeTools?: string[];
437
- excludeTools?: string[];
438
- // Require interactive approval before calling matching MCP tools/resources.
439
- approveTools?: boolean | string[];
440
- // Debug
441
- debug?: boolean; // Show server stderr (default: false)
442
- /** Enable metadata-only JSONL protocol tracing for this server. */
443
- trace?: boolean;
444
- // Keep configuration visible without allowing connections or execution.
445
- disabled?: boolean;
446
- }
447
-
448
- /** Validate protocol options for both file and in-memory configurations. */
449
- export function validateServerProtocolConfig(definition: ServerEntry): void {
450
- if (definition.protocolVersion !== undefined && definition.protocolVersion !== "auto" && definition.protocolVersion !== "legacy") {
451
- throw new Error("protocolVersion must be auto or legacy");
452
- }
453
- if (definition.retryOnTransportFailure !== undefined && typeof definition.retryOnTransportFailure !== "boolean") {
454
- throw new Error("retryOnTransportFailure must be a boolean");
455
- }
456
- if (definition.oauth && definition.oauth.skipIssuerMetadataValidation !== undefined
457
- && typeof definition.oauth.skipIssuerMetadataValidation !== "boolean") {
458
- throw new Error("oauth.skipIssuerMetadataValidation must be a boolean");
459
- }
460
- }
461
-
462
- /** Only the literal boolean `true` disables a server. */
463
- export function isServerDisabled(definition: ServerEntry | undefined): boolean {
464
- return definition?.disabled === true;
465
- }
466
-
467
- // Output guard tuning (settings.outputGuard object form)
468
- export interface McpOutputGuardSettings {
469
- /** Maximum inline MCP text output bytes before truncation/spill-to-disk. Defaults to 51200 (50 KiB). */
470
- maxBytes?: number;
471
- /** Maximum inline MCP text output lines before truncation/spill-to-disk. Defaults to 2000. */
472
- maxLines?: number;
473
- /** Maximum details.mcpResult JSON bytes kept raw; larger results are summarized and spilled to disk. Defaults to 16384 (16 KiB). */
474
- detailsMaxBytes?: number;
475
- }
476
-
477
- // Settings
478
- export type ToolPrefix = "server" | "none" | "short" | "mcp";
479
- export type HostConfigDiscovery = "off" | "on";
480
- export type McpFooterStatus = "full" | "compact" | "off";
481
-
482
- export interface McpTraceSettings {
483
- /** Enable tracing for all servers unless a server sets trace to false. */
484
- enabled?: boolean;
485
- /** JSONL destination; relative paths are resolved from the session cwd. */
486
- file?: string;
487
- /** Maximum per-session trace file size in bytes. */
488
- maxBytes?: number;
489
- /** Maximum events retained in the per-session trace file. */
490
- maxEvents?: number;
491
- }
492
-
493
- export interface McpSettings {
494
- toolPrefix?: ToolPrefix;
495
- /** Show the plug prefix in MCP status and connection text (default: true). Set to false to disable it. */
496
- showStatusIcon?: boolean;
497
- /** Footer status verbosity: full details, compact connected/enabled count, or no footer status. Defaults to compact. */
498
- mcpFooterStatus?: McpFooterStatus;
499
- /** Discover detected host-specific MCP configs only when explicitly enabled. */
500
- hostConfigDiscovery?: HostConfigDiscovery;
501
- idleTimeout?: number; // minutes, default 10, 0 to disable
502
- requestTimeoutMs?: number; // milliseconds, overrides the SDK request timeout when > 0
503
- directTools?: boolean;
504
- /** Register the trusted MCP-only JavaScript scripting tool. Defaults to true; set false to hide it. */
505
- scriptMode?: boolean;
506
- /** Default approval gate for matching tools/resources; per-server settings override it. */
507
- approveTools?: boolean | string[];
508
- disableProxyTool?: boolean;
509
- /** Freeze direct-tool registration after the initial sync. Automatic metadata updates
510
- * (reconnects, lazy-connect, tool-list-changed) won't rebuild the system prompt,
511
- * preserving the prompt-cache prefix. The agent rediscovers explicitly via
512
- * mcp({ connect: "server" }). Default: false. */
513
- freezeDirectTools?: boolean;
514
- autoAuth?: boolean;
515
- sampling?: boolean;
516
- samplingAutoApprove?: boolean;
517
- elicitation?: boolean;
518
- /**
519
- * Guard oversized MCP tool/resource output before it is returned to the model.
520
- * Defaults to true (50 KiB / 2,000 lines inline text, 16 KiB details.mcpResult).
521
- * Set to false to restore raw MCP output behavior, or pass an object to tune
522
- * the limits. Env kill switch: MCP_OUTPUT_GUARD=0.
523
- */
524
- outputGuard?: boolean | McpOutputGuardSettings;
525
- /**
526
- * Opt-in metadata-only MCP protocol tracing. Payloads, prompts, tool
527
- * arguments/results, authorization data, and URLs are never persisted.
528
- */
529
- trace?: McpTraceSettings;
530
- /**
531
- * Message returned in tool results when a server needs (re-)authentication.
532
- * "${server}" is substituted with the server name. Defaults to a TUI
533
- * instruction when unset.
534
- */
535
- authRequiredMessage?: string;
536
- /**
537
- * Parent of the fitch-mcp-adapter/ legacy OAuth import directory.
538
- * Relative paths resolve from the project root (cwd). Explicit v4 migration
539
- * reads the original directory; runtime never consumes those source files.
540
- * FITCH_MCP_OAUTH_DIR overrides the final runtime import path.
541
- * Persistent credentials live in the OS credential store. Plaintext files
542
- * in the runtime import directory are imported once and removed.
543
- */
544
- oauthDir?: string;
545
- }
546
-
547
- // Root config
548
- export interface McpConfig {
549
- mcpServers: Record<string, ServerEntry>;
550
- imports?: ImportKind[];
551
- settings?: McpSettings;
552
- }
553
-
554
- export interface McpToolCallIdentity {
555
- /** Native Pi call ID; correlation only, never a provider idempotency key. */
556
- toolCallId?: string;
557
- /** Existing mcp_script IPC sequence ID, scoped to toolCallId. */
558
- innerCallId?: number;
559
- }
560
-
561
- export type McpToolCallEvent = McpToolCallIdentity & {
562
- server: string;
563
- tool: string;
564
- args: Record<string, unknown>;
565
- annotations?: McpTool["annotations"];
566
- resourceUri?: string;
567
- signal: AbortSignal;
568
- } & (
569
- | { phase: "before" }
570
- | { phase: "after"; result: unknown }
571
- | { phase: "after"; error: unknown }
572
- );
573
-
574
- /** Resolved operation facts before the service deadline; no connection/configuration objects. */
575
- export type McpOperationContext = Omit<Extract<McpToolCallEvent, { phase: "before" }>, "phase" | "signal"> & {
576
- /** The configured server opted into trusting annotations via retryOnTransportFailure. */
577
- annotationsTrusted: boolean;
578
- };
579
-
580
- export interface McpAdapterOptions {
581
- /** Awaited before the service deadline for each resolved call; operation is absent for other proxy modes. Honor ctx.signal. Selects native sequential sibling scheduling. */
582
- beforeExecute?: (toolCallId: string, ctx: ExtensionContext, operation?: McpOperationContext) => Promise<void>;
583
- config?: McpConfig;
584
- configPath?: string;
585
- /** Synchronously transform a clone of the resolved config once per session_start, before registration/initialization.
586
- * The result is cloned for session ownership. Ambient config/trust and panels remain enabled unless config is supplied. */
587
- transformConfig?: (config: McpConfig, ctx: ExtensionContext) => McpConfig;
588
- /** Default overall mcp_script timeout: integer 1–2,147,483,647ms, or null to disable. Omit for 30,000ms. */
589
- defaultScriptTimeoutMs?: number | null;
590
- /** Parent directory for oversized text/raw results, including script output. Defaults to the system temp directory. */
591
- outputDirectory?: string;
592
- /** Awaited before dispatch and after its raw outcome, before any dependent script call. */
593
- onToolCall?: (event: McpToolCallEvent) => Promise<void>;
594
- }
595
-
596
- // Alias for clarity
597
- export type ServerDefinition = ServerEntry;
598
-
599
- export interface ToolMetadata {
600
- name: string; // Prefixed tool name (e.g., "xcodebuild_list_sims")
601
- originalName: string; // Original MCP tool name (e.g., "list_sims")
602
- description: string;
603
- resourceUri?: string; // For resource tools: the URI to read
604
- uiResourceUri?: string; // For app-enabled tools: the UI resource URI
605
- uiVisibility?: UiToolVisibility[];
606
- inputSchema?: unknown; // JSON Schema for parameters (stored for describe/errors)
607
- annotations?: McpTool["annotations"];
608
- uiStreamMode?: UiStreamMode;
609
- }
610
-
611
- export interface PromptMetadata {
612
- serverName: string;
613
- originalName: string;
614
- commandName: string;
615
- title?: string;
616
- description: string;
617
- arguments: McpPromptArgument[];
618
- }
619
-
620
- export interface DirectToolSpec {
621
- serverName: string;
622
- originalName: string;
623
- prefixedName: string;
624
- description: string;
625
- inputSchema?: unknown;
626
- annotations?: McpTool["annotations"];
627
- resourceUri?: string;
628
- uiResourceUri?: string;
629
- uiStreamMode?: UiStreamMode;
630
- }
631
-
632
- export interface ServerProvenance {
633
- path: string;
634
- kind: "user" | "project" | "import";
635
- importKind?: string;
636
- }
637
-
638
- export interface McpAuthResult {
639
- ok: boolean;
640
- message?: string;
641
- }
642
-
643
- export interface CachedTool {
644
- name: string;
645
- description?: string;
646
- inputSchema?: unknown;
647
- annotations?: McpTool["annotations"];
648
- uiResourceUri?: string;
649
- uiVisibility?: UiToolVisibility[];
650
- uiStreamMode?: "eager" | "stream-first";
651
- }
652
-
653
- export interface CachedResource {
654
- uri: string;
655
- name: string;
656
- description?: string;
657
- }
658
-
659
- export interface CachedPrompt {
660
- name: string;
661
- title?: string;
662
- description?: string;
663
- arguments?: { name: string; description?: string; required?: boolean }[];
664
- }
665
-
666
- export interface ServerCacheEntry {
667
- configHash: string;
668
- tools: CachedTool[];
669
- resources: CachedResource[];
670
- prompts?: CachedPrompt[];
671
- instructions?: string;
672
- cachedAt: number;
673
- }
674
-
675
- export interface MetadataCache {
676
- version: number;
677
- servers: Record<string, ServerCacheEntry>;
678
- }
679
-
680
- export interface McpPanelCallbacks {
681
- reconnect: (serverName: string, afterAuth?: boolean) => Promise<boolean>;
682
- canAuthenticate: (serverName: string) => boolean;
683
- authenticate: (serverName: string) => Promise<McpAuthResult>;
684
- getConnectionStatus: (serverName: string) => "connected" | "idle" | "failed" | "needs-auth" | "disabled";
685
- getFailureMessage?: (serverName: string) => string | null;
686
- refreshCacheAfterReconnect: (serverName: string) => ServerCacheEntry | null;
687
- }
688
-
689
- export interface McpPanelResult {
690
- changes: Map<string, true | string[] | false>;
691
- cancelled: boolean;
692
- }
693
-
694
- /**
695
- * Get server prefix based on tool prefix mode.
696
- */
697
- export function getServerPrefix(
698
- serverName: string,
699
- mode: ToolPrefix
700
- ): string {
701
- if (mode === "none") return "";
702
- if (mode === "short") {
703
- let short = serverName.replace(/-?mcp$/i, "").replace(/-/g, "_");
704
- if (!short) short = "mcp";
705
- return short;
706
- }
707
- if (mode === "mcp") return `mcp__${serverName.replace(/-/g, "_")}`;
708
- return serverName.replace(/-/g, "_");
709
- }
710
-
711
- /**
712
- * Format a tool name with server prefix.
713
- */
714
- export function formatToolName(
715
- toolName: string,
716
- serverName: string,
717
- prefix: ToolPrefix
718
- ): string {
719
- const p = getServerPrefix(serverName, prefix);
720
- const sanitized = toolName.replace(/\./g, "_");
721
- return p ? `${p}_${sanitized}` : sanitized;
722
- }
723
-
724
- export function resolveToolPrefix(
725
- definition?: Pick<ServerEntry, "toolPrefix">,
726
- globalPrefix?: ToolPrefix,
727
- ): ToolPrefix {
728
- return definition?.toolPrefix ?? globalPrefix ?? "server";
729
- }
730
-
731
- export function sanitizePromptName(name: string): string {
732
- const cleaned = name.replace(/[^A-Za-z0-9_-]+/g, "_").replace(/^[_-]+|[_-]+$/g, "");
733
- if (!cleaned) return "prompt";
734
- return /^[0-9]/.test(cleaned) ? `_${cleaned}` : cleaned;
735
- }
736
-
737
- export function formatPromptCommandName(
738
- promptName: string,
739
- serverName: string,
740
- prefix: ToolPrefix,
741
- ): string {
742
- const serverPart = getServerPrefix(serverName, prefix) || serverName.replace(/-/g, "_") || "server";
743
- return `mcp__${serverPart}__${sanitizePromptName(promptName)}`;
744
- }
745
-
746
- function normalizeToolName(value: string): string {
747
- return value.replace(/-/g, "_");
748
- }
749
-
750
- export function getToolNameCandidates(toolName: string, serverName: string, prefix: ToolPrefix): Set<string> {
751
- return new Set<string>([
752
- normalizeToolName(toolName),
753
- normalizeToolName(formatToolName(toolName, serverName, prefix)),
754
- normalizeToolName(formatToolName(toolName, serverName, "server")),
755
- normalizeToolName(formatToolName(toolName, serverName, "short")),
756
- normalizeToolName(formatToolName(toolName, serverName, "mcp")),
757
- ]);
758
- }
759
-
760
- function globToRegExp(pattern: string): RegExp {
761
- const escaped = pattern.replace(/[.+^${}()|[\]\\]/g, "\\$&").replace(/\*/g, ".*").replace(/\?/g, ".");
762
- return new RegExp(`^${escaped}$`);
763
- }
764
-
765
- export function matchesToolPattern(candidates: Set<string>, patterns?: unknown): boolean {
766
- if (!Array.isArray(patterns) || patterns.length === 0) return false;
767
-
768
- for (const pattern of patterns) {
769
- if (typeof pattern !== "string") continue;
770
- const normalized = normalizeToolName(pattern);
771
- if (!normalized.includes("*") && !normalized.includes("?") && candidates.has(normalized)) {
772
- return true;
773
- }
774
- if ((normalized.includes("*") || normalized.includes("?")) && [...candidates].some(candidate => globToRegExp(normalized).test(candidate))) {
775
- return true;
776
- }
777
- }
778
-
779
- return false;
780
- }
781
-
782
- export function isToolIncluded(
783
- toolName: string,
784
- serverName: string,
785
- prefix: ToolPrefix,
786
- includeTools?: unknown
787
- ): boolean {
788
- if (!Array.isArray(includeTools) || includeTools.length === 0) return true;
789
- return matchesToolPattern(getToolNameCandidates(toolName, serverName, prefix), includeTools);
790
- }
791
-
792
- export function isToolExcluded(
793
- toolName: string,
794
- serverName: string,
795
- prefix: ToolPrefix,
796
- excludeTools?: unknown
797
- ): boolean {
798
- return matchesToolPattern(getToolNameCandidates(toolName, serverName, prefix), excludeTools);
799
- }
800
-
801
- export function isToolAllowed(
802
- toolName: string,
803
- serverName: string,
804
- prefix: ToolPrefix,
805
- includeTools?: unknown,
806
- excludeTools?: unknown,
807
- ): boolean {
808
- return isToolIncluded(toolName, serverName, prefix, includeTools)
809
- && !isToolExcluded(toolName, serverName, prefix, excludeTools);
810
- }