@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/config.js CHANGED
@@ -1,5 +1,5 @@
1
1
  // config.ts - Config loading with import support
2
- import { existsSync, readFileSync, writeFileSync, mkdirSync, realpathSync, renameSync, statSync, chmodSync } from "node:fs";
2
+ import { existsSync, readFileSync, writeFileSync, mkdirSync, realpathSync, renameSync, statSync, chmodSync, } from "node:fs";
3
3
  import { randomUUID } from "node:crypto";
4
4
  import { homedir } from "node:os";
5
5
  import { dirname, isAbsolute, join, relative, resolve, sep } from "node:path";
@@ -7,7 +7,7 @@ import { isDeepStrictEqual } from "node:util";
7
7
  import { parse as parseToml } from "smol-toml";
8
8
  import stripJsonComments from "strip-json-comments";
9
9
  import { getAdapterPath } from "./agent-dir.js";
10
- import { isServerDisabled, validateServerProtocolConfig } from "./types.js";
10
+ import { isServerDisabled, validateServerProtocolConfig, } from "./types.js";
11
11
  import { toStringRecord } from "./utils.js";
12
12
  const GENERIC_GLOBAL_CONFIG_PATH = join(homedir(), ".config", "mcp", "mcp.json");
13
13
  const AGENTS_GLOBAL_CONFIG_PATHS = [
@@ -59,15 +59,11 @@ const IMPORT_PATHS = {
59
59
  join(homedir(), ".claude.json"),
60
60
  join(homedir(), ".claude", "claude_desktop_config.json"),
61
61
  ],
62
- "claude-desktop": [join(homedir(), "Library", "Application Support", "Claude", "claude_desktop_config.json")],
63
- codex: [
64
- join(homedir(), ".codex", "config.toml"),
65
- join(homedir(), ".codex", "config.json"),
66
- ],
67
- opencode: [
68
- join(homedir(), ".config", "opencode", "opencode.json"),
69
- "./opencode.json",
62
+ "claude-desktop": [
63
+ join(homedir(), "Library", "Application Support", "Claude", "claude_desktop_config.json"),
70
64
  ],
65
+ codex: [join(homedir(), ".codex", "config.toml"), join(homedir(), ".codex", "config.json")],
66
+ opencode: [join(homedir(), ".config", "opencode", "opencode.json"), "./opencode.json"],
71
67
  windsurf: [join(homedir(), ".windsurf", "mcp.json")],
72
68
  vscode: [".vscode/mcp.json"],
73
69
  };
@@ -97,8 +93,9 @@ export function getMcpDiscoverySummary(overridePath, cwd = process.cwd()) {
97
93
  const imports = Object.keys(IMPORT_PATHS)
98
94
  .map((kind) => {
99
95
  const imported = loadImportedConfig(kind, cwd, `Failed to inspect imported MCP config from ${kind}:`);
100
- if (!imported)
96
+ if (!imported) {
101
97
  return null;
98
+ }
102
99
  return {
103
100
  kind,
104
101
  path: imported.path,
@@ -135,8 +132,7 @@ export function cloneMcpConfig(config) {
135
132
  }
136
133
  export function loadMcpConfig(overridePath, cwd = process.cwd(), options = {}) {
137
134
  const includeProject = options.includeProject !== false;
138
- const sourceSpecs = getConfigSources(overridePath, cwd)
139
- .filter((source) => includeProject || !isProjectConfigSource(source, cwd));
135
+ const sourceSpecs = getConfigSources(overridePath, cwd).filter((source) => includeProject || !isProjectConfigSource(source, cwd));
140
136
  const hostConfigDiscovery = getConfiguredHostConfigDiscovery(overridePath, cwd, includeProject);
141
137
  // Host files are a lower-precedence fallback. This ordering means an opt-in
142
138
  // discovery cannot override a shared or Pi-owned definition, and all normal
@@ -146,8 +142,9 @@ export function loadMcpConfig(overridePath, cwd = process.cwd(), options = {}) {
146
142
  : { mcpServers: {} };
147
143
  for (const source of sourceSpecs) {
148
144
  const loaded = readValidatedConfig(source.readPath, `MCP config from ${source.readPath}`);
149
- if (!loaded)
145
+ if (!loaded) {
150
146
  continue;
147
+ }
151
148
  config = mergeConfigs(config, expandImports(loaded, cwd, includeProject));
152
149
  }
153
150
  return config;
@@ -155,12 +152,14 @@ export function loadMcpConfig(overridePath, cwd = process.cwd(), options = {}) {
155
152
  function getConfiguredHostConfigDiscovery(overridePath, cwd = process.cwd(), includeProject = true) {
156
153
  let configured = "off";
157
154
  for (const source of getConfigSources(overridePath, cwd)) {
158
- if (!includeProject && isProjectConfigSource(source, cwd))
155
+ if (!includeProject && isProjectConfigSource(source, cwd)) {
159
156
  continue;
157
+ }
160
158
  const loaded = readValidatedConfig(source.readPath, `MCP config from ${source.readPath}`);
161
159
  const value = loaded?.settings?.hostConfigDiscovery;
162
- if (value === "off" || value === "on")
160
+ if (value === "off" || value === "on") {
163
161
  configured = value;
162
+ }
164
163
  }
165
164
  return configured;
166
165
  }
@@ -168,8 +167,9 @@ function loadDiscoveredHostConfigs(cwd, includeProject) {
168
167
  let config = { mcpServers: {} };
169
168
  for (const importKind of Object.keys(IMPORT_PATHS)) {
170
169
  const imported = loadImportedConfig(importKind, cwd, `Failed to discover imported MCP config from ${importKind}:`, includeProject);
171
- if (!imported)
170
+ if (!imported) {
172
171
  continue;
172
+ }
173
173
  config = mergeConfigs(config, {
174
174
  mcpServers: extractServers(imported.value, importKind),
175
175
  });
@@ -180,29 +180,33 @@ function getConfigConflicts(sourceSpecs, imports, cwd) {
180
180
  const seen = new Map();
181
181
  const record = (name, source) => {
182
182
  const entries = seen.get(name) ?? [];
183
- if (!entries.some((entry) => entry.kind === source.kind && entry.path === source.path))
183
+ if (!entries.some((entry) => entry.kind === source.kind && entry.path === source.path)) {
184
184
  entries.push(source);
185
+ }
185
186
  seen.set(name, entries);
186
187
  };
187
188
  // Host candidates are listed first because, when enabled, they are the
188
189
  // lowest-precedence fallback. The fixed IMPORT_PATHS order is deterministic.
189
190
  for (const entry of imports) {
190
191
  const imported = loadImportedConfig(entry.kind, cwd, `Failed to inspect imported MCP config from ${entry.kind}:`);
191
- if (!imported)
192
+ if (!imported) {
192
193
  continue;
194
+ }
193
195
  for (const name of Object.keys(extractServers(imported.value, entry.kind))) {
194
196
  record(name, { kind: "host", path: imported.path });
195
197
  }
196
198
  }
197
199
  for (const source of sourceSpecs) {
198
200
  const loaded = readValidatedConfig(source.readPath, `MCP config from ${source.readPath}`);
199
- if (!loaded)
201
+ if (!loaded) {
200
202
  continue;
203
+ }
201
204
  if (loaded.imports?.length) {
202
205
  for (const importKind of loaded.imports) {
203
206
  const imported = loadImportedConfig(importKind, cwd, `Failed to inspect imported MCP config from ${importKind}:`);
204
- if (!imported)
207
+ if (!imported) {
205
208
  continue;
209
+ }
206
210
  for (const name of Object.keys(extractServers(imported.value, importKind))) {
207
211
  record(name, { kind: "host", path: imported.path });
208
212
  }
@@ -225,8 +229,9 @@ function canonicalPath(path) {
225
229
  let existingPath = resolvedPath;
226
230
  while (!existsSync(existingPath)) {
227
231
  const parent = dirname(existingPath);
228
- if (parent === existingPath)
232
+ if (parent === existingPath) {
229
233
  return resolvedPath;
234
+ }
230
235
  existingPath = parent;
231
236
  }
232
237
  try {
@@ -238,16 +243,13 @@ function canonicalPath(path) {
238
243
  }
239
244
  export function isPathInsideProject(path, cwd) {
240
245
  const projectRoots = findProjectRoots(cwd);
241
- const roots = projectRoots.length > 0
242
- ? projectRoots
243
- : isHomePath(cwd) ? [] : [cwd];
246
+ const roots = projectRoots.length > 0 ? projectRoots : isHomePath(cwd) ? [] : [cwd];
244
247
  const contains = (root, candidate) => {
245
248
  const pathFromRoot = relative(root, candidate);
246
- return pathFromRoot === ""
247
- || (pathFromRoot !== ".." && !pathFromRoot.startsWith(`..${sep}`) && !isAbsolute(pathFromRoot));
249
+ return (pathFromRoot === "" ||
250
+ (pathFromRoot !== ".." && !pathFromRoot.startsWith(`..${sep}`) && !isAbsolute(pathFromRoot)));
248
251
  };
249
- return roots.some((root) => contains(resolve(root), resolve(path))
250
- || contains(canonicalPath(root), canonicalPath(path)));
252
+ return roots.some((root) => contains(resolve(root), resolve(path)) || contains(canonicalPath(root), canonicalPath(path)));
251
253
  }
252
254
  function isProjectConfigSource(source, cwd) {
253
255
  return source.scope === "project" || isPathInsideProject(source.readPath, cwd);
@@ -270,8 +272,9 @@ function getConfigSources(overridePath, cwd = process.cwd()) {
270
272
  });
271
273
  }
272
274
  for (const [index, agentsPath] of AGENTS_GLOBAL_CONFIG_PATHS.entries()) {
273
- if (agentsPath === userPath || agentsPath === GENERIC_GLOBAL_CONFIG_PATH)
275
+ if (agentsPath === userPath || agentsPath === GENERIC_GLOBAL_CONFIG_PATH) {
274
276
  continue;
277
+ }
275
278
  sources.push({
276
279
  id: index === 0 ? "agents-global" : "agents-nested-global",
277
280
  label: index === 0 ? "user-global .agents MCP" : "user-global .agents nested MCP",
@@ -349,19 +352,29 @@ function mergeServerMaps(base, next) {
349
352
  if (existing && typeof definition.socket === "string") {
350
353
  baseEntry = { ...existing };
351
354
  for (const field of [
352
- "command", "args", "env", "cwd", "url", "headers", "auth",
353
- "bearerToken", "bearerTokenEnv", "oauth",
355
+ "command",
356
+ "args",
357
+ "env",
358
+ "cwd",
359
+ "url",
360
+ "headers",
361
+ "auth",
362
+ "bearerToken",
363
+ "bearerTokenEnv",
364
+ "oauth",
354
365
  ]) {
355
366
  delete baseEntry[field];
356
367
  }
357
368
  }
358
- else if (existing?.socket && (typeof definition.command === "string" || typeof definition.url === "string")) {
369
+ else if (existing?.socket &&
370
+ (typeof definition.command === "string" || typeof definition.url === "string")) {
359
371
  baseEntry = { ...existing };
360
372
  delete baseEntry.socket;
361
373
  }
362
374
  if (existing && typeof definition.url === "string" && definition.url !== existing.url) {
363
- if (baseEntry === existing)
375
+ if (baseEntry === existing) {
364
376
  baseEntry = { ...existing };
377
+ }
365
378
  for (const field of URL_BOUND_AUTH_FIELDS) {
366
379
  delete baseEntry[field];
367
380
  }
@@ -370,11 +383,14 @@ function mergeServerMaps(base, next) {
370
383
  }
371
384
  }
372
385
  // Stdio env belongs to the program that received it, not just the server name.
373
- if (existing?.command && ((definition.command !== undefined && definition.command !== existing.command)
374
- || (definition.args !== undefined && !isDeepStrictEqual(definition.args, existing.args ?? []))
375
- || (definition.cwd !== undefined && definition.cwd !== existing.cwd))) {
376
- if (baseEntry === existing)
386
+ if (existing?.command &&
387
+ ((definition.command !== undefined && definition.command !== existing.command) ||
388
+ (definition.args !== undefined &&
389
+ !isDeepStrictEqual(definition.args, existing.args ?? [])) ||
390
+ (definition.cwd !== undefined && definition.cwd !== existing.cwd))) {
391
+ if (baseEntry === existing) {
377
392
  baseEntry = { ...existing };
393
+ }
378
394
  delete baseEntry.env;
379
395
  }
380
396
  merged[name] = { ...baseEntry, ...definition };
@@ -383,18 +399,21 @@ function mergeServerMaps(base, next) {
383
399
  }
384
400
  function mergeImports(left, right) {
385
401
  const merged = [...(left ?? []), ...(right ?? [])];
386
- if (merged.length === 0)
402
+ if (merged.length === 0) {
387
403
  return undefined;
404
+ }
388
405
  return [...new Set(merged)];
389
406
  }
390
407
  function expandImports(config, cwd, includeProject) {
391
- if (!config.imports?.length)
408
+ if (!config.imports?.length) {
392
409
  return config;
410
+ }
393
411
  const importedServers = {};
394
412
  for (const importKind of config.imports) {
395
413
  const imported = loadImportedConfig(importKind, cwd, `Failed to import MCP config from ${importKind}:`, includeProject);
396
- if (!imported)
414
+ if (!imported) {
397
415
  continue;
416
+ }
398
417
  const servers = extractServers(imported.value, importKind);
399
418
  for (const [name, definition] of Object.entries(servers)) {
400
419
  if (!importedServers[name]) {
@@ -421,17 +440,20 @@ function resolveImportCandidates(importKind, cwd, includeProject) {
421
440
  break;
422
441
  }
423
442
  const parent = dirname(current);
424
- if (parent === current)
443
+ if (parent === current) {
425
444
  break;
445
+ }
426
446
  current = parent;
427
447
  }
428
- if (!gitRoot)
448
+ if (!gitRoot) {
429
449
  return join(start, "opencode.json");
450
+ }
430
451
  current = start;
431
452
  while (true) {
432
453
  const projectConfig = join(current, "opencode.json");
433
- if (existsSync(projectConfig) || current === gitRoot)
454
+ if (existsSync(projectConfig) || current === gitRoot) {
434
455
  return projectConfig;
456
+ }
435
457
  current = dirname(current);
436
458
  }
437
459
  }
@@ -451,8 +473,9 @@ function loadImportedConfig(importKind, cwd, warningPrefix, includeProject = tru
451
473
  let merged = {};
452
474
  let highestPrecedencePath;
453
475
  for (const path of resolveImportCandidates(importKind, cwd, includeProject)) {
454
- if (!existsSync(path))
476
+ if (!existsSync(path)) {
455
477
  continue;
478
+ }
456
479
  try {
457
480
  const value = readImportedConfig(path);
458
481
  if (value && typeof value === "object" && !Array.isArray(value)) {
@@ -467,8 +490,9 @@ function loadImportedConfig(importKind, cwd, warningPrefix, includeProject = tru
467
490
  return highestPrecedencePath ? { path: highestPrecedencePath, value: merged } : null;
468
491
  }
469
492
  for (const path of resolveImportCandidates(importKind, cwd, includeProject)) {
470
- if (!existsSync(path))
493
+ if (!existsSync(path)) {
471
494
  continue;
495
+ }
472
496
  try {
473
497
  return { path, value: readImportedConfig(path) };
474
498
  }
@@ -479,8 +503,9 @@ function loadImportedConfig(importKind, cwd, warningPrefix, includeProject = tru
479
503
  return null;
480
504
  }
481
505
  function readValidatedConfig(path, label) {
482
- if (!existsSync(path))
506
+ if (!existsSync(path)) {
483
507
  return null;
508
+ }
484
509
  try {
485
510
  return validateConfig(parseJsonConfig(readFileSync(path, "utf-8")));
486
511
  }
@@ -503,8 +528,9 @@ function validateConfig(raw) {
503
528
  return { mcpServers: {} };
504
529
  }
505
530
  for (const entry of Object.values(servers)) {
506
- if (entry && typeof entry === "object")
531
+ if (entry && typeof entry === "object") {
507
532
  validateServerProtocolConfig(entry);
533
+ }
508
534
  }
509
535
  return {
510
536
  mcpServers: servers,
@@ -521,13 +547,18 @@ function mergeOpenCodeConfigs(base, next) {
521
547
  if (nextMcp && typeof nextMcp === "object" && !Array.isArray(nextMcp)) {
522
548
  for (const [name, nextEntry] of Object.entries(nextMcp)) {
523
549
  const baseEntry = mergedMcp[name];
524
- if (baseEntry && typeof baseEntry === "object" && !Array.isArray(baseEntry)
525
- && nextEntry && typeof nextEntry === "object" && !Array.isArray(nextEntry)) {
550
+ if (baseEntry &&
551
+ typeof baseEntry === "object" &&
552
+ !Array.isArray(baseEntry) &&
553
+ nextEntry &&
554
+ typeof nextEntry === "object" &&
555
+ !Array.isArray(nextEntry)) {
526
556
  const safeBase = { ...baseEntry };
527
557
  const override = nextEntry;
528
558
  if (typeof override.type === "string" && override.type !== safeBase.type) {
529
- for (const field of ["command", "environment", "cwd", "url", "headers", "oauth"])
559
+ for (const field of ["command", "environment", "cwd", "url", "headers", "oauth"]) {
530
560
  delete safeBase[field];
561
+ }
531
562
  }
532
563
  if (typeof override.url === "string" && override.url !== safeBase.url) {
533
564
  delete safeBase.headers;
@@ -535,23 +566,31 @@ function mergeOpenCodeConfigs(base, next) {
535
566
  }
536
567
  if (Array.isArray(override.command)) {
537
568
  const baseCommand = safeBase.command;
538
- const commandChanged = !Array.isArray(baseCommand)
539
- || override.command.length !== baseCommand.length
540
- || override.command.some((value, index) => value !== baseCommand[index]);
569
+ const commandChanged = !Array.isArray(baseCommand) ||
570
+ override.command.length !== baseCommand.length ||
571
+ override.command.some((value, index) => value !== baseCommand[index]);
541
572
  if (commandChanged) {
542
573
  delete safeBase.environment;
543
574
  delete safeBase.cwd;
544
575
  }
545
576
  }
546
- if (Object.hasOwn(override, "cwd") && override.cwd !== safeBase.cwd)
577
+ if (Object.hasOwn(override, "cwd") && override.cwd !== safeBase.cwd) {
547
578
  delete safeBase.environment;
579
+ }
548
580
  const mergedEntry = { ...safeBase, ...override };
549
581
  for (const field of ["headers", "oauth"]) {
550
582
  const baseField = safeBase[field];
551
583
  const nextField = override[field];
552
- if (baseField && typeof baseField === "object" && !Array.isArray(baseField)
553
- && nextField && typeof nextField === "object" && !Array.isArray(nextField)) {
554
- mergedEntry[field] = { ...baseField, ...nextField };
584
+ if (baseField &&
585
+ typeof baseField === "object" &&
586
+ !Array.isArray(baseField) &&
587
+ nextField &&
588
+ typeof nextField === "object" &&
589
+ !Array.isArray(nextField)) {
590
+ mergedEntry[field] = {
591
+ ...baseField,
592
+ ...nextField,
593
+ };
555
594
  }
556
595
  }
557
596
  mergedMcp[name] = mergedEntry;
@@ -564,8 +603,9 @@ function mergeOpenCodeConfigs(base, next) {
564
603
  return { ...base, ...next, mcp: mergedMcp };
565
604
  }
566
605
  function extractServers(config, kind) {
567
- if (!config || typeof config !== "object")
606
+ if (!config || typeof config !== "object") {
568
607
  return {};
608
+ }
569
609
  const obj = config;
570
610
  let servers;
571
611
  switch (kind) {
@@ -593,16 +633,22 @@ function extractServers(config, kind) {
593
633
  const mappedServers = {};
594
634
  for (const [name, entry] of Object.entries(servers)) {
595
635
  if (kind === "opencode") {
596
- if (!entry || typeof entry !== "object" || Array.isArray(entry))
636
+ if (!entry || typeof entry !== "object" || Array.isArray(entry)) {
597
637
  continue;
638
+ }
598
639
  const raw = entry;
599
- if (raw.enabled === false)
640
+ if (raw.enabled === false) {
600
641
  continue;
601
- if (raw.type === "local" && Array.isArray(raw.command) && raw.command.length > 0 && raw.command.every((value) => typeof value === "string")) {
642
+ }
643
+ if (raw.type === "local" &&
644
+ Array.isArray(raw.command) &&
645
+ raw.command.length > 0 &&
646
+ raw.command.every((value) => typeof value === "string")) {
602
647
  const env = toStringRecord(raw.environment);
603
648
  const command = raw.command[0];
604
- if (command === undefined)
649
+ if (command === undefined) {
605
650
  continue;
651
+ }
606
652
  const mapped = {
607
653
  command,
608
654
  args: raw.command.slice(1),
@@ -644,17 +690,22 @@ function extractServers(config, kind) {
644
690
  const envHttpHeaders = mapped.env_http_headers;
645
691
  if (typeof bearerTokenEnv === "string") {
646
692
  mapped.bearerTokenEnv = bearerTokenEnv;
647
- if (mapped.auth === undefined)
693
+ if (mapped.auth === undefined) {
648
694
  mapped.auth = "bearer";
695
+ }
649
696
  }
650
697
  if (httpHeaders && typeof httpHeaders === "object" && !Array.isArray(httpHeaders)) {
651
- mapped.headers = { ...mapped.headers, ...httpHeaders };
698
+ mapped.headers = {
699
+ ...mapped.headers,
700
+ ...httpHeaders,
701
+ };
652
702
  }
653
703
  if (envHttpHeaders && typeof envHttpHeaders === "object" && !Array.isArray(envHttpHeaders)) {
654
704
  const headers = { ...mapped.headers };
655
705
  for (const [header, envVar] of Object.entries(envHttpHeaders)) {
656
- if (typeof envVar === "string" && headers[header] === undefined)
706
+ if (typeof envVar === "string" && headers[header] === undefined) {
657
707
  headers[header] = `$env:${envVar}`;
708
+ }
658
709
  }
659
710
  mapped.headers = headers;
660
711
  }
@@ -669,8 +720,9 @@ function serializeRawConfig(raw) {
669
720
  return `${JSON.stringify(raw, null, 2)}\n`;
670
721
  }
671
722
  function buildUnifiedDiff(beforeText, afterText) {
672
- if (beforeText === afterText)
723
+ if (beforeText === afterText) {
673
724
  return "(no changes)";
725
+ }
674
726
  const before = beforeText.split("\n");
675
727
  const after = afterText.split("\n");
676
728
  const rows = before.length;
@@ -680,11 +732,13 @@ function buildUnifiedDiff(beforeText, afterText) {
680
732
  for (let j = cols - 1; j >= 0; j--) {
681
733
  const row = lcs[i];
682
734
  const nextRow = lcs[i + 1];
683
- if (!row || !nextRow)
735
+ if (!row || !nextRow) {
684
736
  continue;
685
- row[j] = before[i] === after[j]
686
- ? (nextRow[j + 1] ?? 0) + 1
687
- : Math.max(nextRow[j] ?? 0, row[j + 1] ?? 0);
737
+ }
738
+ row[j] =
739
+ before[i] === after[j]
740
+ ? (nextRow[j + 1] ?? 0) + 1
741
+ : Math.max(nextRow[j] ?? 0, row[j + 1] ?? 0);
688
742
  }
689
743
  }
690
744
  const lines = ["--- before", "+++ after"];
@@ -724,8 +778,9 @@ function buildConfigWritePreview(filePath, nextRaw) {
724
778
  };
725
779
  }
726
780
  function readRawConfigObject(filePath) {
727
- if (!existsSync(filePath))
781
+ if (!existsSync(filePath)) {
728
782
  return {};
783
+ }
729
784
  let raw;
730
785
  try {
731
786
  raw = parseJsonConfig(readFileSync(filePath, "utf-8"));
@@ -747,13 +802,16 @@ function writeRawConfigObject(filePath, raw) {
747
802
  mode: existingMode ?? 0o666,
748
803
  flag: "wx",
749
804
  });
750
- if (existingMode !== undefined)
805
+ if (existingMode !== undefined) {
751
806
  chmodSync(tmpPath, existingMode);
807
+ }
752
808
  renameSync(tmpPath, filePath);
753
809
  }
754
810
  function getServersObject(raw, filePath) {
755
- const existing = raw.mcpServers !== undefined ? raw.mcpServers
756
- : raw["mcp-servers"] !== undefined ? raw["mcp-servers"]
811
+ const existing = raw.mcpServers !== undefined
812
+ ? raw.mcpServers
813
+ : raw["mcp-servers"] !== undefined
814
+ ? raw["mcp-servers"]
757
815
  : {};
758
816
  if (!existing || typeof existing !== "object" || Array.isArray(existing)) {
759
817
  throw new Error(`Invalid MCP config at ${filePath}: mcpServers must be an object`);
@@ -784,14 +842,20 @@ export function writeProjectServerDisabledOverride(overridePath, cwd, serverName
784
842
  throw new Error(`Failed to read project MCP override at ${filePath}: ${error instanceof Error ? error.message : String(error)}`, { cause: error });
785
843
  }
786
844
  }
787
- const serverKey = raw.mcpServers !== undefined ? "mcpServers" : raw["mcp-servers"] !== undefined ? "mcp-servers" : "mcpServers";
845
+ const serverKey = raw.mcpServers !== undefined
846
+ ? "mcpServers"
847
+ : raw["mcp-servers"] !== undefined
848
+ ? "mcp-servers"
849
+ : "mcpServers";
788
850
  const rawServers = raw[serverKey];
789
- if (rawServers !== undefined && (!rawServers || typeof rawServers !== "object" || Array.isArray(rawServers))) {
851
+ if (rawServers !== undefined &&
852
+ (!rawServers || typeof rawServers !== "object" || Array.isArray(rawServers))) {
790
853
  throw new Error(`Failed to update project MCP override at ${filePath}: ${serverKey} must be an object`);
791
854
  }
792
855
  const servers = (rawServers ?? {});
793
856
  const previous = servers[serverName];
794
- if (previous !== undefined && (!previous || typeof previous !== "object" || Array.isArray(previous))) {
857
+ if (previous !== undefined &&
858
+ (!previous || typeof previous !== "object" || Array.isArray(previous))) {
795
859
  throw new Error(`Failed to update project MCP override at ${filePath}: server "${serverName}" must be an object`);
796
860
  }
797
861
  const existing = previous;
@@ -803,28 +867,35 @@ export function writeProjectServerDisabledOverride(overridePath, cwd, serverName
803
867
  next = Object.fromEntries(Object.entries(existing ?? {}).filter(([key]) => key !== "disabled"));
804
868
  let lowerConfig = { mcpServers: {} };
805
869
  for (const source of getConfigSources(overridePath, cwd)) {
806
- if (source.readPath === filePath)
870
+ if (source.readPath === filePath) {
807
871
  continue;
872
+ }
808
873
  const loaded = readValidatedConfig(source.readPath, `MCP config from ${source.readPath}`);
809
- if (loaded)
874
+ if (loaded) {
810
875
  lowerConfig = mergeConfigs(lowerConfig, expandImports(loaded, cwd, true));
876
+ }
811
877
  }
812
878
  if (raw.imports !== undefined) {
813
- if (!Array.isArray(raw.imports) || raw.imports.some((kind) => typeof kind !== "string" || !Object.hasOwn(IMPORT_PATHS, kind))) {
879
+ if (!Array.isArray(raw.imports) ||
880
+ raw.imports.some((kind) => typeof kind !== "string" || !Object.hasOwn(IMPORT_PATHS, kind))) {
814
881
  throw new Error(`Failed to update project MCP override at ${filePath}: imports contains an unsupported config kind`);
815
882
  }
816
883
  lowerConfig = mergeConfigs(lowerConfig, expandImports({ mcpServers: {}, imports: raw.imports }, cwd, true));
817
884
  }
818
- if (isServerDisabled(lowerConfig.mcpServers[serverName]))
885
+ if (isServerDisabled(lowerConfig.mcpServers[serverName])) {
819
886
  next.disabled = false;
887
+ }
820
888
  }
821
- if ((!existing && Object.keys(next).length === 0) || JSON.stringify(existing) === JSON.stringify(next)) {
889
+ if ((!existing && Object.keys(next).length === 0) ||
890
+ JSON.stringify(existing) === JSON.stringify(next)) {
822
891
  return { path: filePath, changed: false };
823
892
  }
824
- if (Object.keys(next).length === 0)
893
+ if (Object.keys(next).length === 0) {
825
894
  delete servers[serverName];
826
- else
895
+ }
896
+ else {
827
897
  servers[serverName] = next;
898
+ }
828
899
  raw[serverKey] = servers;
829
900
  writeRawConfigObject(filePath, raw);
830
901
  return { path: filePath, changed: true };
@@ -835,7 +906,9 @@ function isRepoPromptServer(name, entry) {
835
906
  return true;
836
907
  }
837
908
  const command = entry.command?.toLowerCase() ?? "";
838
- if (command.includes("repoprompt") || command.includes("rp-mcp") || command.endsWith("repoprompt_cli")) {
909
+ if (command.includes("repoprompt") ||
910
+ command.includes("rp-mcp") ||
911
+ command.endsWith("repoprompt_cli")) {
839
912
  return true;
840
913
  }
841
914
  return (entry.args ?? []).some((arg) => typeof arg === "string" && arg.toLowerCase().includes("repoprompt"));
@@ -851,18 +924,19 @@ function findProjectRoots(cwd = process.cwd()) {
851
924
  for (const startPath of startPaths) {
852
925
  let current = startPath;
853
926
  while (true) {
854
- if (!isHomePath(current)
855
- && (existsSync(join(current, ".git"))
856
- || existsSync(join(current, "package.json"))
857
- || existsSync(join(current, PROJECT_CONFIG_NAME))
858
- || existsSync(join(current, ".pi")))
859
- && !seen.has(current)) {
927
+ if (!isHomePath(current) &&
928
+ (existsSync(join(current, ".git")) ||
929
+ existsSync(join(current, "package.json")) ||
930
+ existsSync(join(current, PROJECT_CONFIG_NAME)) ||
931
+ existsSync(join(current, ".pi"))) &&
932
+ !seen.has(current)) {
860
933
  roots.push(current);
861
934
  seen.add(current);
862
935
  }
863
936
  const parent = dirname(current);
864
- if (parent === current)
937
+ if (parent === current) {
865
938
  break;
939
+ }
866
940
  current = parent;
867
941
  }
868
942
  }
@@ -880,11 +954,13 @@ function buildRepoPromptEntry(executablePath) {
880
954
  }
881
955
  function detectRepoPrompt(summary, cwd = process.cwd()) {
882
956
  for (const source of summary.sources) {
883
- if (source.kind !== "shared" || source.serverCount === 0)
957
+ if (source.kind !== "shared" || source.serverCount === 0) {
884
958
  continue;
959
+ }
885
960
  const config = readValidatedConfig(source.path, `MCP config from ${source.path}`);
886
- if (!config)
961
+ if (!config) {
887
962
  continue;
963
+ }
888
964
  for (const [name, entry] of Object.entries(config.mcpServers)) {
889
965
  if (isRepoPromptServer(name, entry)) {
890
966
  return { configured: true, configuredPath: source.path };
@@ -896,7 +972,9 @@ function detectRepoPrompt(summary, cwd = process.cwd()) {
896
972
  return { configured: false };
897
973
  }
898
974
  const projectRoot = findProjectRoot(cwd);
899
- const targetPath = projectRoot ? join(projectRoot, PROJECT_CONFIG_NAME) : GENERIC_GLOBAL_CONFIG_PATH;
975
+ const targetPath = projectRoot
976
+ ? join(projectRoot, PROJECT_CONFIG_NAME)
977
+ : GENERIC_GLOBAL_CONFIG_PATH;
900
978
  return {
901
979
  configured: false,
902
980
  executablePath,
@@ -908,7 +986,9 @@ function detectRepoPrompt(summary, cwd = process.cwd()) {
908
986
  export function previewCompatibilityImports(importKinds, overridePath) {
909
987
  const targetPath = getPiGlobalConfigPath(overridePath);
910
988
  const raw = readRawConfigObject(targetPath);
911
- const currentImports = Array.isArray(raw.imports) ? raw.imports.filter((value) => typeof value === "string") : [];
989
+ const currentImports = Array.isArray(raw.imports)
990
+ ? raw.imports.filter((value) => typeof value === "string")
991
+ : [];
912
992
  const merged = [...new Set([...currentImports, ...importKinds])];
913
993
  const nextRaw = { ...raw, imports: merged };
914
994
  setServersObject(nextRaw, getServersObject(nextRaw, targetPath));
@@ -917,7 +997,9 @@ export function previewCompatibilityImports(importKinds, overridePath) {
917
997
  export function ensureCompatibilityImports(importKinds, overridePath) {
918
998
  const targetPath = getPiGlobalConfigPath(overridePath);
919
999
  const raw = readRawConfigObject(targetPath);
920
- const currentImports = Array.isArray(raw.imports) ? raw.imports.filter((value) => typeof value === "string") : [];
1000
+ const currentImports = Array.isArray(raw.imports)
1001
+ ? raw.imports.filter((value) => typeof value === "string")
1002
+ : [];
921
1003
  const merged = [...new Set([...currentImports, ...importKinds])];
922
1004
  const added = merged.filter((kind) => !currentImports.includes(kind));
923
1005
  if (added.length === 0) {
@@ -967,8 +1049,9 @@ export function getServerProvenance(overridePath, cwd = process.cwd()) {
967
1049
  if (getConfiguredHostConfigDiscovery(overridePath, cwd) === "on") {
968
1050
  for (const importKind of Object.keys(IMPORT_PATHS)) {
969
1051
  const imported = loadImportedConfig(importKind, cwd, `Failed to inspect imported MCP config from ${importKind}:`);
970
- if (!imported)
1052
+ if (!imported) {
971
1053
  continue;
1054
+ }
972
1055
  for (const name of Object.keys(extractServers(imported.value, importKind))) {
973
1056
  // Keep writes inside Pi-owned storage even though the source is external.
974
1057
  // Later import kinds win in the same deterministic order as loadDiscoveredHostConfigs.
@@ -978,13 +1061,15 @@ export function getServerProvenance(overridePath, cwd = process.cwd()) {
978
1061
  }
979
1062
  for (const source of getConfigSources(overridePath, cwd)) {
980
1063
  const loaded = readValidatedConfig(source.readPath, `MCP config from ${source.readPath}`);
981
- if (!loaded)
1064
+ if (!loaded) {
982
1065
  continue;
1066
+ }
983
1067
  if (loaded.imports?.length) {
984
1068
  for (const importKind of loaded.imports) {
985
1069
  const imported = loadImportedConfig(importKind, cwd, `Failed to inspect imported MCP config from ${importKind}:`);
986
- if (!imported)
1070
+ if (!imported) {
987
1071
  continue;
1072
+ }
988
1073
  const servers = extractServers(imported.value, importKind);
989
1074
  for (const name of Object.keys(servers)) {
990
1075
  if (!provenance.has(name)) {
@@ -1007,11 +1092,13 @@ export function writeDirectToolsConfig(changes, provenance) {
1007
1092
  const byPath = new Map();
1008
1093
  for (const [serverName, value] of changes) {
1009
1094
  const prov = provenance.get(serverName);
1010
- if (!prov)
1095
+ if (!prov) {
1011
1096
  continue;
1097
+ }
1012
1098
  const targetPath = prov.path;
1013
- if (!byPath.has(targetPath))
1099
+ if (!byPath.has(targetPath)) {
1014
1100
  byPath.set(targetPath, []);
1101
+ }
1015
1102
  byPath.get(targetPath).push({ name: serverName, value });
1016
1103
  }
1017
1104
  for (const [filePath, entries] of byPath) {
@@ -1026,13 +1113,15 @@ export function writeDirectToolsConfig(changes, provenance) {
1026
1113
  }
1027
1114
  }
1028
1115
  export function resolveConfiguredOAuthDir(raw, cwd = process.cwd()) {
1029
- if (raw === undefined || raw === null)
1116
+ if (raw === undefined || raw === null) {
1030
1117
  return undefined;
1118
+ }
1031
1119
  if (typeof raw !== "string") {
1032
1120
  throw new Error("settings.oauthDir must be a string");
1033
1121
  }
1034
1122
  const trimmed = raw.trim();
1035
- if (!trimmed)
1123
+ if (!trimmed) {
1036
1124
  return undefined;
1125
+ }
1037
1126
  return resolve(cwd, trimmed);
1038
1127
  }