@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
package/dist/mcp-trace.js CHANGED
@@ -5,8 +5,9 @@ export const MCP_TRACE_SCHEMA_VERSION = 1;
5
5
  export const DEFAULT_MCP_TRACE_MAX_BYTES = 256 * 1024;
6
6
  export const DEFAULT_MCP_TRACE_MAX_EVENTS = 10_000;
7
7
  function boundedPositiveInteger(value, fallback) {
8
- if (!Number.isFinite(value) || value === undefined || value <= 0)
8
+ if (!Number.isFinite(value) || value === undefined || value <= 0) {
9
9
  return fallback;
10
+ }
10
11
  return Math.floor(value);
11
12
  }
12
13
  /** Redact strings before they can become durable trace metadata. */
@@ -18,22 +19,27 @@ export function redactTraceText(value, maxLength = 160) {
18
19
  .replace(/\b[a-z][a-z\d+.-]*:\/\/[^\s"'<>]+/gi, "[REDACTED_URL]")
19
20
  .replace(/\b(?:bearer|basic)\s+[A-Za-z0-9._~+/=-]+/gi, "[REDACTED_AUTH]")
20
21
  .replace(/\b(?:token|secret|password|passwd|api[_-]?key|authorization|cookie)\s*[:=]\s*[^\s,;]+/gi, "$1=[REDACTED]");
21
- if (redacted.length > maxLength)
22
+ if (redacted.length > maxLength) {
22
23
  redacted = `${redacted.slice(0, maxLength - 1)}…`;
24
+ }
23
25
  return redacted;
24
26
  }
25
27
  function messageKind(message) {
26
- if ("method" in message)
28
+ if ("method" in message) {
27
29
  return "id" in message ? "request" : "notification";
30
+ }
28
31
  return "response";
29
32
  }
30
33
  function traceId(value) {
31
- if (value === null)
34
+ if (value === null) {
32
35
  return null;
33
- if (typeof value === "number" && Number.isFinite(value))
36
+ }
37
+ if (typeof value === "number" && Number.isFinite(value)) {
34
38
  return value;
35
- if (typeof value === "string")
39
+ }
40
+ if (typeof value === "string") {
36
41
  return "[REDACTED_ID]";
42
+ }
37
43
  return undefined;
38
44
  }
39
45
  function messageBytes(message) {
@@ -44,33 +50,49 @@ function messageBytes(message) {
44
50
  return undefined;
45
51
  }
46
52
  }
47
- export function createMcpTraceEvent(direction, server, transport, message, status, options) {
48
- const kind = messageKind(message);
49
- const bytes = messageBytes(message);
50
- const event = {
53
+ export function createMcpTraceEvent(request) {
54
+ const kind = messageKind(request.message);
55
+ const bytes = messageBytes(request.message);
56
+ return {
51
57
  version: MCP_TRACE_SCHEMA_VERSION,
52
58
  timestamp: new Date().toISOString(),
53
- direction,
54
- server: redactTraceText(server, 120),
55
- transport,
59
+ direction: request.direction,
60
+ server: redactTraceText(request.server, 120),
61
+ transport: request.transport,
56
62
  kind,
57
- status,
63
+ status: request.status,
58
64
  ...(bytes !== undefined ? { bytes } : {}),
65
+ ...traceMessageMetadata(request.message),
66
+ ...traceOutcomeMetadata(request),
59
67
  };
60
- if ("method" in message)
61
- event.method = redactTraceText(message.method, 120);
62
- if ("id" in message)
63
- event.id = traceId(message.id) ?? null;
64
- const relatedRequestId = traceId(options?.relatedRequestId);
65
- if (relatedRequestId !== undefined && relatedRequestId !== null)
66
- event.relatedRequestId = relatedRequestId;
67
- if ("error" in message && message.error && typeof message.error.code === "number") {
68
- event.errorCode = message.error.code;
69
- }
70
- if (options?.durationMs !== undefined && Number.isFinite(options.durationMs)) {
71
- event.durationMs = Math.max(0, Math.round(options.durationMs * 100) / 100);
72
- }
73
- return event;
68
+ }
69
+ function traceMessageMetadata(message) {
70
+ const fields = {};
71
+ if ("method" in message) {
72
+ fields.method = redactTraceText(message.method, 120);
73
+ }
74
+ if ("id" in message) {
75
+ fields.id = traceId(message.id) ?? null;
76
+ }
77
+ return fields;
78
+ }
79
+ function traceOutcomeMetadata(request) {
80
+ const fields = {};
81
+ const relatedRequestId = traceId(request.relatedRequestId);
82
+ if (relatedRequestId !== undefined && relatedRequestId !== null) {
83
+ fields.relatedRequestId = relatedRequestId;
84
+ }
85
+ const wireError = "error" in request.message ? request.message.error : undefined;
86
+ if (typeof wireError === "object" &&
87
+ wireError !== null &&
88
+ "code" in wireError &&
89
+ typeof wireError.code === "number") {
90
+ fields.errorCode = wireError.code;
91
+ }
92
+ if (request.durationMs !== undefined && Number.isFinite(request.durationMs)) {
93
+ fields.durationMs = Math.max(0, Math.round(request.durationMs * 100) / 100);
94
+ }
95
+ return fields;
74
96
  }
75
97
  export class McpTraceWriter {
76
98
  maxBytes;
@@ -90,19 +112,25 @@ export class McpTraceWriter {
90
112
  this.options = options;
91
113
  this.maxBytes = boundedPositiveInteger(options.maxBytes, DEFAULT_MCP_TRACE_MAX_BYTES);
92
114
  this.maxEvents = boundedPositiveInteger(options.maxEvents, DEFAULT_MCP_TRACE_MAX_EVENTS);
93
- this.append = options.appendFile ?? (async (path, data, appendOptions) => {
94
- await appendFile(path, data, appendOptions);
95
- });
96
- this.resetFile = options.writeFile ?? (async (path, data, writeOptions) => {
97
- await writeFile(path, data, writeOptions);
98
- });
99
- this.makeDirectory = options.mkdir ?? (async (path) => {
100
- await mkdir(path, { recursive: true });
101
- return undefined;
102
- });
115
+ this.append =
116
+ options.appendFile ??
117
+ (async (path, data, appendOptions) => {
118
+ await appendFile(path, data, appendOptions);
119
+ });
120
+ this.resetFile =
121
+ options.writeFile ??
122
+ (async (path, data, writeOptions) => {
123
+ await writeFile(path, data, writeOptions);
124
+ });
125
+ this.makeDirectory =
126
+ options.mkdir ??
127
+ (async (path) => {
128
+ await mkdir(path, { recursive: true });
129
+ return;
130
+ });
103
131
  this.fileReady = this.makeDirectory(dirname(this.options.filePath), { recursive: true })
104
132
  .then(() => this.resetFile(this.options.filePath, "", { encoding: "utf8" }))
105
- .catch(error => {
133
+ .catch((error) => {
106
134
  // Tracing must never change MCP request/response behavior.
107
135
  this.persistenceError = error;
108
136
  this.initializationFailed = true;
@@ -119,8 +147,9 @@ export class McpTraceWriter {
119
147
  return { bytes: this.bytesWritten, events: this.eventsWritten };
120
148
  }
121
149
  write(event) {
122
- if (this.disabled || this.eventsWritten >= this.maxEvents)
150
+ if (this.disabled || this.eventsWritten >= this.maxEvents) {
123
151
  return;
152
+ }
124
153
  let line;
125
154
  try {
126
155
  line = `${JSON.stringify(event)}\n`;
@@ -136,12 +165,16 @@ export class McpTraceWriter {
136
165
  }
137
166
  this.bytesWritten += bytes;
138
167
  this.eventsWritten += 1;
139
- this.queue = this.queue.then(async () => {
168
+ this.queue = this.queue
169
+ .then(async () => {
140
170
  await this.fileReady;
141
- if (this.initializationFailed)
171
+ if (this.initializationFailed) {
142
172
  return;
173
+ }
143
174
  await this.append(this.options.filePath, line, { encoding: "utf8" });
144
- }).catch(error => {
175
+ return;
176
+ })
177
+ .catch((error) => {
145
178
  // Tracing must never change MCP request/response behavior.
146
179
  this.persistenceError ??= error;
147
180
  this.disabled = true;
@@ -150,16 +183,24 @@ export class McpTraceWriter {
150
183
  async flush(strict = false) {
151
184
  await this.fileReady;
152
185
  await this.queue;
153
- if (strict && this.persistenceError !== undefined)
186
+ if (strict && this.persistenceError !== undefined) {
187
+ // oxlint-disable-next-line typescript/only-throw-error -- QX-009: preserve the recorded persistence failure.
154
188
  throw this.persistenceError;
189
+ }
155
190
  }
156
191
  }
157
192
  export function createMcpTraceWriter(sessionCwd, settings = {}, randomSuffix = Math.random().toString(36).slice(2, 10)) {
158
193
  const timestamp = new Date().toISOString().replace(/[:.]/g, "-");
159
194
  const configuredPath = settings.file;
160
- const filePath = configuredPath
161
- ? (isAbsolute(configuredPath) ? configuredPath : resolve(sessionCwd ?? process.cwd(), configuredPath))
162
- : resolve(sessionCwd ?? process.cwd(), ".pi", "fitch-mcp-adapter", "traces", `mcp-${timestamp}-${randomSuffix}.jsonl`);
195
+ let filePath;
196
+ if (typeof configuredPath === "string" && configuredPath !== "") {
197
+ filePath = isAbsolute(configuredPath)
198
+ ? configuredPath
199
+ : resolve(sessionCwd ?? process.cwd(), configuredPath);
200
+ }
201
+ else {
202
+ filePath = resolve(sessionCwd ?? process.cwd(), ".pi", "fitch-mcp-adapter", "traces", `mcp-${timestamp}-${randomSuffix}.jsonl`);
203
+ }
163
204
  return new McpTraceWriter({
164
205
  filePath,
165
206
  ...(settings.maxBytes !== undefined ? { maxBytes: settings.maxBytes } : {}),
@@ -184,23 +225,34 @@ export function wrapTransportWithMcpTrace(transport, server, transportKind, obse
184
225
  // An observer failure must never alter SDK transport behavior.
185
226
  }
186
227
  };
187
- // Instrument the native instance: the SDK inspects stdio identity and HTTP
188
- // hasPerRequestStream to choose negotiation and cancellation behavior.
228
+ // The SDK requires the native stdio prototype for sibling probing and reads
229
+ // HTTP hasPerRequestStream for cancellation. Compose callbacks in place.
189
230
  const send = transport.send.bind(transport);
231
+ // oxlint-disable-next-line eslint/no-param-reassign -- QX-001: SDK invokes this member on the instance it holds.
190
232
  transport.send = async (message, options) => {
191
233
  const started = performance.now();
192
234
  const messages = Array.isArray(message) ? message : [message];
193
235
  try {
194
236
  await send(message, options);
195
237
  for (const item of messages) {
196
- record(createMcpTraceEvent("outbound", server, transportKind, item, "sent", {
238
+ record(createMcpTraceEvent({
239
+ direction: "outbound",
240
+ server,
241
+ transport: transportKind,
242
+ message: item,
243
+ status: "sent",
197
244
  durationMs: performance.now() - started,
198
245
  }));
199
246
  }
200
247
  }
201
248
  catch (error) {
202
249
  for (const item of messages) {
203
- record(createMcpTraceEvent("outbound", server, transportKind, item, "error", {
250
+ record(createMcpTraceEvent({
251
+ direction: "outbound",
252
+ server,
253
+ transport: transportKind,
254
+ message: item,
255
+ status: "error",
204
256
  durationMs: performance.now() - started,
205
257
  }));
206
258
  }
@@ -211,23 +263,37 @@ export function wrapTransportWithMcpTrace(transport, server, transportKind, obse
211
263
  configurable: true,
212
264
  get: () => messageHandler,
213
265
  set: (handler) => {
214
- messageHandler = handler ? (message, extra) => {
215
- record(createMcpTraceEvent("inbound", server, transportKind, message, "received"));
216
- handler(message, extra);
217
- } : undefined;
266
+ messageHandler = handler
267
+ ? (message, extra) => {
268
+ record(createMcpTraceEvent({
269
+ direction: "inbound",
270
+ server,
271
+ transport: transportKind,
272
+ message,
273
+ status: "received",
274
+ }));
275
+ handler(message, extra);
276
+ }
277
+ : undefined;
218
278
  },
219
279
  });
220
280
  return transport;
221
281
  }
222
282
  export function traceTransportKind(definition, transport) {
223
- if (definition.command)
283
+ if (typeof definition.command === "string" && definition.command !== "") {
224
284
  return "stdio";
225
- if (definition.socket)
285
+ }
286
+ if (typeof definition.socket === "string" && definition.socket !== "") {
226
287
  return "unix-socket";
227
- const constructorName = transport.constructor?.name.toLowerCase() ?? "";
228
- if (constructorName.includes("sse"))
288
+ }
289
+ const constructorName = transport.constructor.name.toLowerCase();
290
+ if (constructorName.includes("sse")) {
229
291
  return "sse";
230
- if (constructorName.includes("streamable"))
292
+ }
293
+ if (constructorName.includes("streamable")) {
231
294
  return "streamable-http";
232
- return definition.url ? "streamable-http" : "unknown";
295
+ }
296
+ return typeof definition.url === "string" && definition.url !== ""
297
+ ? "streamable-http"
298
+ : "unknown";
233
299
  }
@@ -1,11 +1,10 @@
1
- import type { CachedPrompt, CachedResource, CachedTool, McpConfig, McpTool, McpResource, McpPrompt, MetadataCache, ServerCacheEntry, ServerEntry, ToolMetadata, PromptMetadata } from "./types.ts";
2
- import { type ToolPrefix } from "./types.ts";
3
- export type { CachedPrompt, CachedResource, CachedTool, MetadataCache, ServerCacheEntry } from "./types.ts";
1
+ import { type CachedPrompt, type CachedResource, type CachedTool, type McpConfig, type McpTool, type McpResource, type McpPrompt, type MetadataCache, type ServerCacheEntry, type ServerEntry, type ToolMetadata, type PromptMetadata, type ToolPrefix } from "./types.ts";
2
+ export type { CachedPrompt, CachedResource, CachedTool, MetadataCache, ServerCacheEntry, } from "./types.ts";
4
3
  export declare function getMetadataCachePath(): string;
5
4
  export declare function loadMetadataCache(enabled?: boolean): MetadataCache | null;
6
5
  export declare function saveMetadataCache(cache: MetadataCache, enabled?: boolean, selectedServers?: Iterable<string>): void;
7
6
  export declare function computeServerHash(definition: ServerEntry): string;
8
- export declare function isServerCacheValid(entry: ServerCacheEntry, definition: ServerEntry, maxAgeMs?: number): boolean;
7
+ export declare function isServerCacheValid(entry: unknown, definition: ServerEntry, maxAgeMs?: number): boolean;
9
8
  export declare function parseDirectToolSelectors(selectors: string[]): {
10
9
  servers: Set<string>;
11
10
  tools: Map<string, Set<string>>;
@@ -3,7 +3,7 @@ import { existsSync, readFileSync, writeFileSync, renameSync, mkdirSync, statSyn
3
3
  import { dirname } from "node:path";
4
4
  import { getAdapterPath } from "./agent-dir.js";
5
5
  import { createHash } from "node:crypto";
6
- import { formatPromptCommandName, isServerDisabled, resolveToolPrefix } from "./types.js";
6
+ import { formatPromptCommandName, isServerDisabled, resolveToolPrefix, } from "./types.js";
7
7
  import { buildToolMetadata } from "./tool-metadata.js";
8
8
  import { interpolateEnvRecord, resolveBearerToken, resolveConfigPath, resolveServerUrl, } from "./utils.js";
9
9
  // Version 2 stores raw descriptors; version 1 readers cannot enforce raw UI visibility.
@@ -17,8 +17,9 @@ function cacheRevision(path) {
17
17
  }
18
18
  function freezeMetadata(value) {
19
19
  if (value && typeof value === "object" && !Object.isFrozen(value)) {
20
- for (const child of Object.values(value))
20
+ for (const child of Object.values(value)) {
21
21
  freezeMetadata(child);
22
+ }
22
23
  Object.freeze(value);
23
24
  }
24
25
  return value;
@@ -27,20 +28,28 @@ export function getMetadataCachePath() {
27
28
  return getAdapterPath("mcp-cache.json");
28
29
  }
29
30
  export function loadMetadataCache(enabled = true) {
30
- if (!enabled)
31
+ if (!enabled) {
31
32
  return null;
33
+ }
32
34
  const cachePath = getMetadataCachePath();
33
35
  try {
34
36
  const revision = cacheRevision(cachePath);
35
- if (snapshot?.path === cachePath && snapshot.revision === revision)
37
+ if (snapshot?.path === cachePath && snapshot.revision === revision) {
36
38
  return snapshot.cache;
39
+ }
37
40
  const raw = JSON.parse(readFileSync(cachePath, "utf-8"));
38
- const cache = raw && typeof raw === "object" && !Array.isArray(raw)
39
- && (raw.version === 1 || raw.version === CACHE_VERSION)
40
- && raw.servers && typeof raw.servers === "object" && !Array.isArray(raw.servers)
41
- ? freezeMetadata(raw) : null;
41
+ const cache = raw &&
42
+ typeof raw === "object" &&
43
+ !Array.isArray(raw) &&
44
+ (raw.version === 1 || raw.version === CACHE_VERSION) &&
45
+ raw.servers &&
46
+ typeof raw.servers === "object" &&
47
+ !Array.isArray(raw.servers)
48
+ ? freezeMetadata(raw)
49
+ : null;
42
50
  // A concurrent rename must not associate the old bytes with the new file's revision.
43
- snapshot = cacheRevision(cachePath) === revision ? { path: cachePath, revision, cache } : undefined;
51
+ snapshot =
52
+ cacheRevision(cachePath) === revision ? { path: cachePath, revision, cache } : undefined;
44
53
  return cache;
45
54
  }
46
55
  catch {
@@ -49,16 +58,19 @@ export function loadMetadataCache(enabled = true) {
49
58
  }
50
59
  }
51
60
  export function saveMetadataCache(cache, enabled = true, selectedServers = []) {
52
- if (!enabled)
61
+ if (!enabled) {
53
62
  return;
63
+ }
54
64
  const cachePath = getMetadataCachePath();
55
65
  const dir = dirname(cachePath);
56
66
  mkdirSync(dir, { recursive: true });
57
- let merged = { version: CACHE_VERSION, servers: {} };
67
+ const merged = { version: CACHE_VERSION, servers: {} };
58
68
  try {
59
69
  if (existsSync(cachePath)) {
60
70
  const existing = JSON.parse(readFileSync(cachePath, "utf-8"));
61
- if (existing && (existing.version === 1 || existing.version === CACHE_VERSION) && existing.servers) {
71
+ if (existing &&
72
+ (existing.version === 1 || existing.version === CACHE_VERSION) &&
73
+ existing.servers) {
62
74
  merged.servers = { ...existing.servers };
63
75
  }
64
76
  }
@@ -71,8 +83,9 @@ export function saveMetadataCache(cache, enabled = true, selectedServers = []) {
71
83
  const protectedServers = new Set([...selectedServers, ...Object.keys(cache.servers)]);
72
84
  const now = Date.now();
73
85
  for (const [name, entry] of Object.entries(merged.servers)) {
74
- if (!protectedServers.has(name) && isCacheEntry(entry)
75
- && (entry.cachedAt > now || now - entry.cachedAt > CACHE_MAX_AGE_MS)) {
86
+ if (!protectedServers.has(name) &&
87
+ isCacheEntry(entry) &&
88
+ (entry.cachedAt > now || now - entry.cachedAt > CACHE_MAX_AGE_MS)) {
76
89
  delete merged.servers[name];
77
90
  }
78
91
  }
@@ -110,23 +123,30 @@ export function isServerCacheValid(entry, definition, maxAgeMs = CACHE_MAX_AGE_M
110
123
  catch {
111
124
  return false;
112
125
  }
113
- if (!isCacheEntry(entry) || entry.configHash !== configHash)
126
+ if (!isCacheEntry(entry) || entry.configHash !== configHash) {
114
127
  return false;
115
- if (entry.cachedAt > Date.now())
128
+ }
129
+ if (entry.cachedAt > Date.now()) {
116
130
  return false;
117
- if (maxAgeMs > 0 && Date.now() - entry.cachedAt > maxAgeMs)
131
+ }
132
+ if (maxAgeMs > 0 && Date.now() - entry.cachedAt > maxAgeMs) {
118
133
  return false;
134
+ }
119
135
  return true;
120
136
  }
121
137
  function isCacheEntry(entry) {
122
- if (!entry || typeof entry !== "object" || Array.isArray(entry))
138
+ if (!entry || typeof entry !== "object" || Array.isArray(entry)) {
123
139
  return false;
140
+ }
124
141
  const value = entry;
125
- return typeof value.configHash === "string"
126
- && typeof value.cachedAt === "number" && Number.isFinite(value.cachedAt) && value.cachedAt > 0
127
- && Array.isArray(value.tools) && Array.isArray(value.resources)
128
- && (value.prompts === undefined || Array.isArray(value.prompts))
129
- && (value.instructions === undefined || typeof value.instructions === "string");
142
+ return (typeof value.configHash === "string" &&
143
+ typeof value.cachedAt === "number" &&
144
+ Number.isFinite(value.cachedAt) &&
145
+ value.cachedAt > 0 &&
146
+ Array.isArray(value.tools) &&
147
+ Array.isArray(value.resources) &&
148
+ (value.prompts === undefined || Array.isArray(value.prompts)) &&
149
+ (value.instructions === undefined || typeof value.instructions === "string"));
130
150
  }
131
151
  export function parseDirectToolSelectors(selectors) {
132
152
  const servers = new Set();
@@ -155,15 +175,17 @@ export function getMissingConfiguredDirectToolServers(config, cache, envOverride
155
175
  const globalDirect = config.settings?.directTools;
156
176
  const envSelection = envOverride ? parseDirectToolSelectors(envOverride) : null;
157
177
  for (const [serverName, definition] of Object.entries(config.mcpServers)) {
158
- if (isServerDisabled(definition))
178
+ if (isServerDisabled(definition)) {
159
179
  continue;
180
+ }
160
181
  const hasDirectTools = envSelection
161
182
  ? envSelection.servers.has(serverName) || envSelection.tools.has(serverName)
162
183
  : definition.directTools !== undefined
163
- ? !!definition.directTools
164
- : !!globalDirect;
165
- if (!hasDirectTools)
184
+ ? Boolean(definition.directTools)
185
+ : Boolean(globalDirect);
186
+ if (!hasDirectTools) {
166
187
  continue;
188
+ }
167
189
  const serverCache = cache?.servers?.[serverName];
168
190
  if (!serverCache || !isServerCacheValid(serverCache, definition)) {
169
191
  missing.push(serverName);
@@ -172,19 +194,29 @@ export function getMissingConfiguredDirectToolServers(config, cache, envOverride
172
194
  return missing;
173
195
  }
174
196
  export function reconstructToolMetadata(serverName, entry, prefix, definition) {
175
- const key = JSON.stringify([serverName, prefix, definition.exposeResources, definition.includeTools,
176
- definition.excludeTools, definition.toolPrefix, definition.disabled]);
197
+ const key = JSON.stringify([
198
+ serverName,
199
+ prefix,
200
+ definition.exposeResources,
201
+ definition.includeTools,
202
+ definition.excludeTools,
203
+ definition.toolPrefix,
204
+ definition.disabled,
205
+ ]);
177
206
  const previous = toolProjections.get(entry);
178
- if (Object.isFrozen(entry) && previous?.key === key)
207
+ if (Object.isFrozen(entry) && previous?.key === key) {
179
208
  return previous.metadata;
180
- const metadata = buildToolMetadata((entry.tools ?? []).filter(tool => tool?.name).map(restoreCachedTool), entry.resources ?? [], definition, serverName, prefix).metadata;
181
- if (Object.isFrozen(entry))
209
+ }
210
+ const metadata = buildToolMetadata((entry.tools ?? []).filter((tool) => tool?.name).map(restoreCachedTool), entry.resources ?? [], definition, serverName, prefix).metadata;
211
+ if (Object.isFrozen(entry)) {
182
212
  toolProjections.set(entry, { key, metadata: freezeMetadata(metadata) });
213
+ }
183
214
  return metadata;
184
215
  }
185
216
  function restoreCachedTool(tool) {
186
217
  const { uiResourceUri, uiVisibility, uiStreamMode, ...descriptor } = tool;
187
- if (descriptor._meta !== undefined || (uiResourceUri === undefined && uiVisibility === undefined && uiStreamMode === undefined)) {
218
+ if (descriptor._meta !== undefined ||
219
+ (uiResourceUri === undefined && uiVisibility === undefined && uiStreamMode === undefined)) {
188
220
  return descriptor;
189
221
  }
190
222
  return {
@@ -199,23 +231,27 @@ function restoreCachedTool(tool) {
199
231
  };
200
232
  }
201
233
  export function serializeTools(tools) {
202
- return tools.filter(tool => tool?.name);
234
+ return tools.filter((tool) => tool?.name);
203
235
  }
204
236
  export function serializeResources(resources) {
205
- return resources.filter(resource => resource?.name && resource?.uri);
237
+ return resources.filter((resource) => resource?.name && resource?.uri);
206
238
  }
207
239
  export function serializePrompts(prompts) {
208
240
  return (prompts ?? [])
209
- .filter(prompt => prompt?.name)
210
- .map(prompt => ({
241
+ .filter((prompt) => prompt?.name)
242
+ .map((prompt) => ({
211
243
  name: prompt.name,
212
244
  ...(prompt.title !== undefined ? { title: prompt.title } : {}),
213
245
  ...(prompt.description !== undefined ? { description: prompt.description } : {}),
214
246
  ...(Array.isArray(prompt.arguments)
215
247
  ? {
216
- arguments: prompt.arguments.filter(argument => argument?.name).map(argument => ({
248
+ arguments: prompt.arguments
249
+ .filter((argument) => argument?.name)
250
+ .map((argument) => ({
217
251
  name: argument.name,
218
- ...(argument.description !== undefined ? { description: argument.description } : {}),
252
+ ...(argument.description !== undefined
253
+ ? { description: argument.description }
254
+ : {}),
219
255
  ...(argument.required !== undefined ? { required: argument.required } : {}),
220
256
  })),
221
257
  }
@@ -224,9 +260,13 @@ export function serializePrompts(prompts) {
224
260
  }
225
261
  export function reconstructPromptMetadata(serverName, prompts, prefix, definition) {
226
262
  const effectivePrefix = resolveToolPrefix(definition, prefix);
227
- return (prompts ?? []).filter(prompt => prompt?.name).map(prompt => {
263
+ return (prompts ?? [])
264
+ .filter((prompt) => prompt?.name)
265
+ .map((prompt) => {
228
266
  const args = Array.isArray(prompt.arguments)
229
- ? prompt.arguments.filter(argument => argument?.name).map(argument => ({
267
+ ? prompt.arguments
268
+ .filter((argument) => argument?.name)
269
+ .map((argument) => ({
230
270
  name: argument.name,
231
271
  ...(argument.description !== undefined ? { description: argument.description } : {}),
232
272
  ...(argument.required !== undefined ? { required: argument.required } : {}),
@@ -248,9 +288,9 @@ function stableStringify(value) {
248
288
  return serialized === undefined ? "undefined" : serialized;
249
289
  }
250
290
  if (Array.isArray(value)) {
251
- return `[${value.map(v => stableStringify(v)).join(",")}]`;
291
+ return `[${value.map((v) => stableStringify(v)).join(",")}]`;
252
292
  }
253
293
  const obj = value;
254
294
  const keys = Object.keys(obj).sort();
255
- return `{${keys.map(k => `${JSON.stringify(k)}:${stableStringify(obj[k])}`).join(",")}}`;
295
+ return `{${keys.map((k) => `${JSON.stringify(k)}:${stableStringify(obj[k])}`).join(",")}}`;
256
296
  }