@fitchmultz/pi-mcp-adapter 8.0.6 → 8.0.7

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 +128 -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 +26 -2
  87. package/skills/mcp-scripting/SKILL.md +6 -6
package/dist/mcp-auth.js CHANGED
@@ -9,46 +9,47 @@
9
9
  * when set, otherwise <Pi agent dir>/fitch-mcp-adapter/mcp-oauth/sha256-<server-hash>/tokens.json,
10
10
  * then the plaintext file is removed.
11
11
  */
12
- import { spawnSync } from 'child_process';
13
- import { createHash } from 'crypto';
14
- import { createRequire } from 'module';
15
- import { readFileSync, existsSync, rmSync } from 'fs';
16
- import { dirname, join } from 'path';
17
- import { fileURLToPath } from 'url';
18
- import { getAdapterPath, getAgentPath } from './agent-dir.js';
19
- import { resolveConfiguredOAuthDir } from './config.js';
12
+ import { spawnSync } from "child_process";
13
+ import { createHash } from "crypto";
14
+ import { createRequire } from "module";
15
+ import { readFileSync, existsSync, rmSync } from "fs";
16
+ import { dirname, join } from "path";
17
+ import { fileURLToPath } from "url";
18
+ import { getAdapterPath, getAgentPath } from "./agent-dir.js";
19
+ import { resolveConfiguredOAuthDir } from "./config.js";
20
20
  const require = createRequire(import.meta.url);
21
- const AUTH_SECRET_SERVICE = 'fitch-mcp-adapter.oauth';
22
- const LEGACY_AUTH_SECRET_SERVICE = 'pi-mcp-adapter.oauth';
23
- const TEST_AUTH_STORE_ENV = 'PI_MCP_ADAPTER_TEST_AUTH_STORE';
21
+ const AUTH_SECRET_SERVICE = "fitch-mcp-adapter.oauth";
22
+ const LEGACY_AUTH_SECRET_SERVICE = "pi-mcp-adapter.oauth";
23
+ const TEST_AUTH_STORE_ENV = "PI_MCP_ADAPTER_TEST_AUTH_STORE";
24
24
  const AUTH_SECRET_CHUNK_SIZE = 1800;
25
- const KEYRING_RECOVERY_DISABLED_ENV = 'PI_MCP_ADAPTER_DISABLE_KEYRING_RECOVERY';
26
- const KEYRING_RECOVERY_KEYCTL_ENV = 'PI_MCP_ADAPTER_KEYRING_RECOVERY_KEYCTL';
27
- const KEYRING_RECOVERY_NODE_ENV = 'PI_MCP_ADAPTER_KEYRING_RECOVERY_NODE';
28
- const KEYRING_RECOVERY_HELPER_ENV = 'PI_MCP_ADAPTER_KEYRING_RECOVERY_HELPER';
29
- const TEST_LINUX_KEYRING_RECOVERY_ENV = 'PI_MCP_ADAPTER_TEST_LINUX_KEYRING_RECOVERY';
25
+ const KEYRING_RECOVERY_DISABLED_ENV = "PI_MCP_ADAPTER_DISABLE_KEYRING_RECOVERY";
26
+ const KEYRING_RECOVERY_KEYCTL_ENV = "PI_MCP_ADAPTER_KEYRING_RECOVERY_KEYCTL";
27
+ const KEYRING_RECOVERY_NODE_ENV = "PI_MCP_ADAPTER_KEYRING_RECOVERY_NODE";
28
+ const KEYRING_RECOVERY_HELPER_ENV = "PI_MCP_ADAPTER_KEYRING_RECOVERY_HELPER";
29
+ const TEST_LINUX_KEYRING_RECOVERY_ENV = "PI_MCP_ADAPTER_TEST_LINUX_KEYRING_RECOVERY";
30
30
  const KEYRING_RECOVERY_TIMEOUT_MS = 10_000;
31
- const AUTH_CHUNK_MANIFEST_KEY = '__piMcpAdapterOAuthChunked';
31
+ const AUTH_CHUNK_MANIFEST_KEY = "__piMcpAdapterOAuthChunked";
32
32
  export class OAuthCredentialStoreError extends Error {
33
- code = 'OAUTH_CREDENTIAL_STORE_UNAVAILABLE';
33
+ code = "OAUTH_CREDENTIAL_STORE_UNAVAILABLE";
34
34
  operation;
35
35
  constructor(message, operation, cause) {
36
36
  super(isAndroidKeyringLoadFailure(cause)
37
37
  ? `${message}. Android/Termux: @napi-rs/keyring could not load a native binding. Persistent OAuth requires a supported platform with an OS credential store; no plaintext fallback is used.`
38
38
  : message, { cause });
39
39
  this.operation = operation;
40
- this.name = 'OAuthCredentialStoreError';
40
+ this.name = "OAuthCredentialStoreError";
41
41
  }
42
42
  }
43
43
  function causeChainContains(error, pattern) {
44
44
  const seen = new Set();
45
45
  let current = error;
46
- while ((typeof current === 'object' && current !== null) || typeof current === 'function') {
47
- if (seen.has(current))
46
+ while ((typeof current === "object" && current !== null) || typeof current === "function") {
47
+ if (seen.has(current)) {
48
48
  break;
49
+ }
49
50
  seen.add(current);
50
51
  const candidate = current;
51
- if ([candidate.name, candidate.message, candidate.code].some(value => typeof value === 'string' && pattern.test(value))) {
52
+ if ([candidate.name, candidate.message, candidate.code].some((value) => typeof value === "string" && pattern.test(value))) {
52
53
  return true;
53
54
  }
54
55
  current = candidate.cause;
@@ -56,15 +57,18 @@ function causeChainContains(error, pattern) {
56
57
  return false;
57
58
  }
58
59
  function isAndroidKeyringLoadFailure(error) {
59
- return process.platform === 'android' && causeChainContains(error, /^Failed to load @napi-rs\/keyring;/);
60
+ return (process.platform === "android" &&
61
+ causeChainContains(error, /^Failed to load @napi-rs\/keyring;/));
60
62
  }
61
63
  export function formatOAuthCredentialStoreUnavailable(error) {
62
- if (isAndroidKeyringLoadFailure(error))
64
+ if (isAndroidKeyringLoadFailure(error)) {
63
65
  return error.message;
64
- if (process.platform === 'linux' && causeChainContains(error, /key\s*(?:has been\s*)?revoked|keyrevoked/i)) {
65
- return 'OAuth credential store unavailable: the Linux session keyring may be revoked. Start Pi from a fresh login/keyring session and retry.';
66
66
  }
67
- return 'OAuth credential store unavailable. Configure or unlock the OS credential store and retry.';
67
+ if (process.platform === "linux" &&
68
+ causeChainContains(error, /key\s*(?:has been\s*)?revoked|keyrevoked/i)) {
69
+ return "OAuth credential store unavailable: the Linux session keyring may be revoked. Start Pi from a fresh login/keyring session and retry.";
70
+ }
71
+ return "OAuth credential store unavailable. Configure or unlock the OS credential store and retry.";
68
72
  }
69
73
  let KeyringEntryClass;
70
74
  const memoryAuthEntries = new Map();
@@ -96,17 +100,19 @@ function keyringAuthSecretStore(service) {
96
100
  }
97
101
  const unavailableAuthSecretStore = {
98
102
  read() {
99
- throw new Error('simulated secure credential store unavailable');
103
+ throw new Error("simulated secure credential store unavailable");
100
104
  },
101
105
  write() {
102
- throw new Error('simulated secure credential store unavailable');
106
+ throw new Error("simulated secure credential store unavailable");
103
107
  },
104
108
  remove() {
105
- throw new Error('simulated secure credential store unavailable');
109
+ throw new Error("simulated secure credential store unavailable");
106
110
  },
107
111
  };
108
112
  function createKeyRevokedTestError() {
109
- return new Error("Couldn't access platform storage: KeyRevoked", { cause: new Error('KeyRevoked') });
113
+ return new Error("Couldn't access platform storage: KeyRevoked", {
114
+ cause: new Error("KeyRevoked"),
115
+ });
110
116
  }
111
117
  const keyRevokedAuthSecretStore = {
112
118
  read() {
@@ -120,12 +126,15 @@ const keyRevokedAuthSecretStore = {
120
126
  },
121
127
  };
122
128
  function getAuthSecretStore(service = AUTH_SECRET_SERVICE) {
123
- if (process.env[TEST_AUTH_STORE_ENV] === 'memory')
129
+ if (process.env[TEST_AUTH_STORE_ENV] === "memory") {
124
130
  return memoryAuthSecretStore(service);
125
- if (process.env[TEST_AUTH_STORE_ENV] === 'unavailable')
131
+ }
132
+ if (process.env[TEST_AUTH_STORE_ENV] === "unavailable") {
126
133
  return unavailableAuthSecretStore;
127
- if (process.env[TEST_AUTH_STORE_ENV] === 'keyrevoked')
134
+ }
135
+ if (process.env[TEST_AUTH_STORE_ENV] === "keyrevoked") {
128
136
  return keyRevokedAuthSecretStore;
137
+ }
129
138
  return keyringAuthSecretStore(service);
130
139
  }
131
140
  function getKeyringEntry(account, service) {
@@ -134,12 +143,12 @@ function getKeyringEntry(account, service) {
134
143
  return new KeyringEntryClass(service, account);
135
144
  }
136
145
  catch (error) {
137
- throw new Error('OAuth secure credential storage is unavailable. Configure the OS credential store and retry authentication.', { cause: error });
146
+ throw new Error("OAuth secure credential storage is unavailable. Configure the OS credential store and retry authentication.", { cause: error });
138
147
  }
139
148
  }
140
149
  function loadKeyringEntryClass(keyringRequire = require, platform = process.platform, arch = process.arch) {
141
150
  try {
142
- return keyringRequire('@napi-rs/keyring').Entry;
151
+ return keyringRequire("@napi-rs/keyring").Entry;
143
152
  }
144
153
  catch (loaderError) {
145
154
  try {
@@ -170,157 +179,178 @@ function loadKeyringNativeBindingFallback(keyringRequire, platform, arch) {
170
179
  throw lastError instanceof Error ? lastError : new Error(String(lastError));
171
180
  }
172
181
  function getKeyringNativeBindingTargets(platform, arch) {
173
- return getKeyringNativeBindingSuffixes(platform, arch).map(suffix => ({
182
+ return getKeyringNativeBindingSuffixes(platform, arch).map((suffix) => ({
174
183
  packageName: `@napi-rs/keyring-${suffix}`,
175
184
  bindingFile: `keyring.${suffix}.node`,
176
185
  }));
177
186
  }
178
187
  function getKeyringNativeBindingSuffixes(platform, arch) {
179
- if (platform === 'darwin') {
180
- if (arch === 'arm64')
181
- return ['darwin-arm64'];
182
- if (arch === 'x64')
183
- return ['darwin-x64'];
184
- }
185
- if (platform === 'win32') {
186
- if (arch === 'arm64')
187
- return ['win32-arm64-msvc'];
188
- if (arch === 'x64')
189
- return ['win32-x64-msvc'];
190
- if (arch === 'ia32')
191
- return ['win32-ia32-msvc'];
192
- }
193
- if (platform === 'linux') {
194
- if (arch === 'arm64')
195
- return ['linux-arm64-gnu', 'linux-arm64-musl'];
196
- if (arch === 'arm')
197
- return ['linux-arm-gnueabihf'];
198
- if (arch === 'riscv64')
199
- return ['linux-riscv64-gnu'];
200
- if (arch === 'x64')
201
- return ['linux-x64-gnu', 'linux-x64-musl'];
202
- }
203
- if (platform === 'freebsd' && arch === 'x64')
204
- return ['freebsd-x64'];
188
+ if (platform === "darwin") {
189
+ if (arch === "arm64") {
190
+ return ["darwin-arm64"];
191
+ }
192
+ if (arch === "x64") {
193
+ return ["darwin-x64"];
194
+ }
195
+ }
196
+ if (platform === "win32") {
197
+ if (arch === "arm64") {
198
+ return ["win32-arm64-msvc"];
199
+ }
200
+ if (arch === "x64") {
201
+ return ["win32-x64-msvc"];
202
+ }
203
+ if (arch === "ia32") {
204
+ return ["win32-ia32-msvc"];
205
+ }
206
+ }
207
+ if (platform === "linux") {
208
+ if (arch === "arm64") {
209
+ return ["linux-arm64-gnu", "linux-arm64-musl"];
210
+ }
211
+ if (arch === "arm") {
212
+ return ["linux-arm-gnueabihf"];
213
+ }
214
+ if (arch === "riscv64") {
215
+ return ["linux-riscv64-gnu"];
216
+ }
217
+ if (arch === "x64") {
218
+ return ["linux-x64-gnu", "linux-x64-musl"];
219
+ }
220
+ }
221
+ if (platform === "freebsd" && arch === "x64") {
222
+ return ["freebsd-x64"];
223
+ }
205
224
  return [];
206
225
  }
207
226
  function formatErrorMessage(error) {
208
227
  return error instanceof Error ? error.message : String(error);
209
228
  }
210
229
  function isLinuxKeyringRecoveryEnabled() {
211
- if (process.env[KEYRING_RECOVERY_DISABLED_ENV] === '1')
230
+ if (process.env[KEYRING_RECOVERY_DISABLED_ENV] === "1") {
212
231
  return false;
213
- return process.platform === 'linux' || process.env[TEST_LINUX_KEYRING_RECOVERY_ENV] === '1';
232
+ }
233
+ return process.platform === "linux" || process.env[TEST_LINUX_KEYRING_RECOVERY_ENV] === "1";
214
234
  }
215
235
  function shouldAttemptLinuxKeyringRecovery(error) {
216
- return isLinuxKeyringRecoveryEnabled()
217
- && causeChainContains(error, /key\s*(?:has been\s*)?revoked|keyrevoked/i);
236
+ return (isLinuxKeyringRecoveryEnabled() &&
237
+ causeChainContains(error, /key\s*(?:has been\s*)?revoked|keyrevoked/i));
218
238
  }
219
239
  function runLinuxKeyringRecoveryOperation(operation, account, payload, service) {
220
- const keyctl = process.env[KEYRING_RECOVERY_KEYCTL_ENV]?.trim() || 'keyctl';
221
- const node = process.env[KEYRING_RECOVERY_NODE_ENV]?.trim() || 'node';
222
- const helper = process.env[KEYRING_RECOVERY_HELPER_ENV]?.trim()
223
- || fileURLToPath(new URL('./mcp-keyring-helper.cjs', import.meta.url));
240
+ const keyctl = process.env[KEYRING_RECOVERY_KEYCTL_ENV]?.trim() || "keyctl";
241
+ const node = process.env[KEYRING_RECOVERY_NODE_ENV]?.trim() || "node";
242
+ const helper = process.env[KEYRING_RECOVERY_HELPER_ENV]?.trim() ||
243
+ fileURLToPath(new URL("./mcp-keyring-helper.cjs", import.meta.url));
224
244
  const request = JSON.stringify({ operation, service, account, payload });
225
- const result = spawnSync(keyctl, ['session', '-', node, helper], {
245
+ const result = spawnSync(keyctl, ["session", "-", node, helper], {
226
246
  input: `${request}\n`,
227
- encoding: 'utf8',
247
+ encoding: "utf8",
228
248
  maxBuffer: 1024 * 1024,
229
249
  timeout: KEYRING_RECOVERY_TIMEOUT_MS,
230
250
  windowsHide: true,
231
251
  });
232
252
  if (result.error) {
233
- throw new Error(`Linux keyring recovery helper could not start: ${result.error.message}`, { cause: result.error });
253
+ throw new Error(`Linux keyring recovery helper could not start: ${result.error.message}`, {
254
+ cause: result.error,
255
+ });
234
256
  }
235
257
  if (result.status !== 0) {
236
- throw new Error(`Linux keyring recovery helper failed with exit code ${result.status ?? 'unknown'}`);
258
+ throw new Error(`Linux keyring recovery helper failed with exit code ${result.status ?? "unknown"}`);
237
259
  }
238
260
  let response;
239
261
  try {
240
262
  response = JSON.parse(result.stdout.trim());
241
263
  }
242
264
  catch (error) {
243
- throw new Error('Linux keyring recovery helper returned invalid JSON', { cause: error });
265
+ throw new Error("Linux keyring recovery helper returned invalid JSON", { cause: error });
244
266
  }
245
- if (typeof response !== 'object' || response === null || typeof response.ok !== 'boolean') {
246
- throw new Error('Linux keyring recovery helper returned an invalid response');
267
+ if (typeof response !== "object" ||
268
+ response === null ||
269
+ typeof response.ok !== "boolean") {
270
+ throw new Error("Linux keyring recovery helper returned an invalid response");
247
271
  }
248
272
  const typedResponse = response;
249
- if (typedResponse.ok === false) {
250
- throw new Error(typedResponse.error || 'Linux keyring recovery helper failed');
273
+ if (!typedResponse.ok) {
274
+ throw new Error(typedResponse.error || "Linux keyring recovery helper failed");
251
275
  }
252
- if (operation === 'read' && typedResponse.found === true && typeof typedResponse.value !== 'string') {
253
- throw new Error('Linux keyring recovery helper returned an invalid read response');
276
+ if (operation === "read" &&
277
+ typedResponse.found === true &&
278
+ typeof typedResponse.value !== "string") {
279
+ throw new Error("Linux keyring recovery helper returned an invalid read response");
254
280
  }
255
281
  return typedResponse;
256
282
  }
257
283
  function linuxKeyringRecoveryAuthSecretStore(service = AUTH_SECRET_SERVICE) {
258
284
  return {
259
285
  read(account) {
260
- const response = runLinuxKeyringRecoveryOperation('read', account, undefined, service);
286
+ const response = runLinuxKeyringRecoveryOperation("read", account, undefined, service);
261
287
  return response.ok && response.found === true ? response.value : undefined;
262
288
  },
263
289
  write(account, payload) {
264
- runLinuxKeyringRecoveryOperation('write', account, payload, service);
290
+ runLinuxKeyringRecoveryOperation("write", account, payload, service);
265
291
  },
266
292
  remove(account) {
267
- runLinuxKeyringRecoveryOperation('remove', account, undefined, service);
293
+ runLinuxKeyringRecoveryOperation("remove", account, undefined, service);
268
294
  },
269
295
  };
270
296
  }
271
297
  export function getAuthStorageOptions(oauthDir, cwd = process.cwd()) {
272
298
  const baseDir = resolveConfiguredOAuthDir(oauthDir, cwd);
273
- return baseDir ? { baseDir: join(baseDir, 'fitch-mcp-adapter') } : {};
299
+ return baseDir ? { baseDir: join(baseDir, "fitch-mcp-adapter") } : {};
274
300
  }
275
301
  export function getAuthBaseDir(options = {}) {
276
302
  const override = process.env.FITCH_MCP_OAUTH_DIR?.trim();
277
- if (override)
303
+ if (override) {
278
304
  return override;
279
- return options.baseDir ?? getAdapterPath('mcp-oauth');
305
+ }
306
+ return options.baseDir ?? getAdapterPath("mcp-oauth");
280
307
  }
281
308
  /**
282
309
  * Get the legacy server-specific directory path.
283
310
  */
284
311
  function getServerDir(serverName, options) {
285
- if (typeof serverName !== 'string') {
312
+ if (typeof serverName !== "string") {
286
313
  throw new Error(`Invalid MCP server name: ${JSON.stringify(serverName)}`);
287
314
  }
288
315
  const storageKey = getAuthEntryAccount(serverName);
289
316
  return join(getAuthBaseDir(options), storageKey);
290
317
  }
291
318
  function getAuthEntryAccount(serverName) {
292
- if (typeof serverName !== 'string') {
319
+ if (typeof serverName !== "string") {
293
320
  throw new Error(`Invalid MCP server name: ${JSON.stringify(serverName)}`);
294
321
  }
295
- return `sha256-${createHash('sha256').update(serverName, 'utf8').digest('hex')}`;
322
+ return `sha256-${createHash("sha256").update(serverName, "utf8").digest("hex")}`;
296
323
  }
297
324
  /**
298
325
  * Get the legacy plaintext tokens file path for a server.
299
326
  */
300
327
  export function getAuthEntryFilePath(serverName, options) {
301
- return join(getServerDir(serverName, options), 'tokens.json');
328
+ return join(getServerDir(serverName, options), "tokens.json");
302
329
  }
303
330
  function parseJsonPayload(serverName, payload, source) {
304
331
  try {
305
332
  return JSON.parse(payload);
306
333
  }
307
334
  catch (error) {
308
- throw new Error(`Failed to parse OAuth credentials for ${serverName} from ${source}`, { cause: error });
335
+ throw new Error(`Failed to parse OAuth credentials for ${serverName} from ${source}`, {
336
+ cause: error,
337
+ });
309
338
  }
310
339
  }
311
340
  function parseAuthEntryPayload(serverName, payload, source) {
312
341
  return parseJsonPayload(serverName, payload, source);
313
342
  }
314
343
  function isAuthEntryChunkManifest(value) {
315
- if (typeof value !== 'object' || value === null)
344
+ if (typeof value !== "object" || value === null) {
316
345
  return false;
346
+ }
317
347
  const manifest = value;
318
- return manifest[AUTH_CHUNK_MANIFEST_KEY] === 1
319
- && typeof manifest.chunkCount === 'number'
320
- && Number.isInteger(manifest.chunkCount)
321
- && manifest.chunkCount > 0
322
- && typeof manifest.chunkDigest === 'string'
323
- && /^[a-f0-9]{16}$/.test(manifest.chunkDigest);
348
+ return (manifest[AUTH_CHUNK_MANIFEST_KEY] === 1 &&
349
+ typeof manifest.chunkCount === "number" &&
350
+ Number.isInteger(manifest.chunkCount) &&
351
+ manifest.chunkCount > 0 &&
352
+ typeof manifest.chunkDigest === "string" &&
353
+ /^[a-f0-9]{16}$/.test(manifest.chunkDigest));
324
354
  }
325
355
  function getAuthEntryChunkAccount(account, manifest, index) {
326
356
  return `${account}.chunk.${manifest.chunkDigest}.${index}`;
@@ -335,7 +365,9 @@ function readChunkManifestFromPayload(serverName, payload, source) {
335
365
  function readExistingChunkManifest(store, serverName, account) {
336
366
  try {
337
367
  const payload = store.read(account);
338
- return payload === undefined ? undefined : readChunkManifestFromPayload(serverName, payload, 'OS secure credential store');
368
+ return payload === undefined
369
+ ? undefined
370
+ : readChunkManifestFromPayload(serverName, payload, "OS secure credential store");
339
371
  }
340
372
  catch {
341
373
  return undefined;
@@ -347,8 +379,9 @@ function removeChunkPayloads(store, account, manifest) {
347
379
  }
348
380
  }
349
381
  function tryRemoveChunkPayloads(store, account, manifest) {
350
- if (!manifest)
382
+ if (!manifest) {
351
383
  return;
384
+ }
352
385
  try {
353
386
  removeChunkPayloads(store, account, manifest);
354
387
  }
@@ -360,7 +393,7 @@ function createChunkManifest(payload) {
360
393
  return {
361
394
  [AUTH_CHUNK_MANIFEST_KEY]: 1,
362
395
  chunkCount: Math.ceil(payload.length / AUTH_SECRET_CHUNK_SIZE),
363
- chunkDigest: createHash('sha256').update(payload, 'utf8').digest('hex').slice(0, 16),
396
+ chunkDigest: createHash("sha256").update(payload, "utf8").digest("hex").slice(0, 16),
364
397
  };
365
398
  }
366
399
  function readChunkedAuthEntry(store, serverName, account, manifest) {
@@ -373,22 +406,24 @@ function readChunkedAuthEntry(store, serverName, account, manifest) {
373
406
  return chunk;
374
407
  }
375
408
  catch (error) {
376
- throw new OAuthCredentialStoreError(`Failed to read OAuth credentials for ${serverName} from the OS secure credential store`, 'read', error);
409
+ throw new OAuthCredentialStoreError(`Failed to read OAuth credentials for ${serverName} from the OS secure credential store`, "read", error);
377
410
  }
378
411
  });
379
- return parseAuthEntryPayload(serverName, chunks.join(''), 'OS secure credential store chunks');
412
+ return parseAuthEntryPayload(serverName, chunks.join(""), "OS secure credential store chunks");
380
413
  }
381
414
  function readLegacyAuthEntry(serverName, options) {
382
415
  const filePath = getAuthEntryFilePath(serverName, options);
383
- if (!existsSync(filePath))
416
+ if (!existsSync(filePath)) {
384
417
  return undefined;
385
- const data = readFileSync(filePath, 'utf-8');
418
+ }
419
+ const data = readFileSync(filePath, "utf-8");
386
420
  return parseAuthEntryPayload(serverName, data, filePath);
387
421
  }
388
422
  function removeLegacyAuthEntry(serverName, options) {
389
423
  const filePath = getAuthEntryFilePath(serverName, options);
390
- if (!existsSync(filePath))
424
+ if (!existsSync(filePath)) {
391
425
  return;
426
+ }
392
427
  try {
393
428
  rmSync(filePath, { force: true });
394
429
  }
@@ -426,7 +461,7 @@ function writeSecureAuthEntryToStore(store, serverName, entry) {
426
461
  }
427
462
  catch (error) {
428
463
  tryRemoveChunkPayloads(store, account, manifest);
429
- throw new OAuthCredentialStoreError(`Failed to write OAuth credentials for ${serverName} to the OS secure credential store`, 'write', error);
464
+ throw new OAuthCredentialStoreError(`Failed to write OAuth credentials for ${serverName} to the OS secure credential store`, "write", error);
430
465
  }
431
466
  }
432
467
  function writeSecureAuthEntry(serverName, entry) {
@@ -434,8 +469,9 @@ function writeSecureAuthEntry(serverName, entry) {
434
469
  writeSecureAuthEntryToStore(getAuthSecretStore(), serverName, entry);
435
470
  }
436
471
  catch (error) {
437
- if (!shouldAttemptLinuxKeyringRecovery(error))
472
+ if (!shouldAttemptLinuxKeyringRecovery(error)) {
438
473
  throw error;
474
+ }
439
475
  writeSecureAuthEntryToStore(linuxKeyringRecoveryAuthSecretStore(), serverName, entry);
440
476
  }
441
477
  }
@@ -446,58 +482,64 @@ function readSecureAuthEntryFromStore(store, serverName) {
446
482
  payload = store.read(account);
447
483
  }
448
484
  catch (error) {
449
- throw new OAuthCredentialStoreError(`Failed to read OAuth credentials for ${serverName} from the OS secure credential store`, 'read', error);
485
+ throw new OAuthCredentialStoreError(`Failed to read OAuth credentials for ${serverName} from the OS secure credential store`, "read", error);
450
486
  }
451
- if (payload === undefined)
487
+ if (payload === undefined) {
452
488
  return undefined;
453
- const manifest = readChunkManifestFromPayload(serverName, payload, 'OS secure credential store');
489
+ }
490
+ const manifest = readChunkManifestFromPayload(serverName, payload, "OS secure credential store");
454
491
  return manifest
455
492
  ? readChunkedAuthEntry(store, serverName, account, manifest)
456
- : parseAuthEntryPayload(serverName, payload, 'OS secure credential store');
493
+ : parseAuthEntryPayload(serverName, payload, "OS secure credential store");
457
494
  }
458
495
  function readSecureAuthEntry(serverName, service = AUTH_SECRET_SERVICE) {
459
496
  try {
460
497
  return readSecureAuthEntryFromStore(getAuthSecretStore(service), serverName);
461
498
  }
462
499
  catch (error) {
463
- if (!shouldAttemptLinuxKeyringRecovery(error))
500
+ if (!shouldAttemptLinuxKeyringRecovery(error)) {
464
501
  throw error;
502
+ }
465
503
  return readSecureAuthEntryFromStore(linuxKeyringRecoveryAuthSecretStore(service), serverName);
466
504
  }
467
505
  }
468
506
  function readAuthEntry(serverName, options, behavior = {}) {
469
507
  const entry = readSecureAuthEntry(serverName);
470
508
  if (entry !== undefined) {
471
- if (behavior.migrateLegacy !== false)
509
+ if (behavior.migrateLegacy !== false) {
472
510
  removeLegacyAuthEntry(serverName, options);
511
+ }
473
512
  return entry;
474
513
  }
475
514
  const legacyEntry = readLegacyAuthEntry(serverName, options);
476
- if (!legacyEntry || behavior.migrateLegacy === false)
515
+ if (!legacyEntry || behavior.migrateLegacy === false) {
477
516
  return legacyEntry;
517
+ }
478
518
  writeSecureAuthEntry(serverName, legacyEntry);
479
519
  removeLegacyAuthEntry(serverName, options);
480
520
  return legacyEntry;
481
521
  }
482
522
  /** Explicit copy-only upgrade. Normal reads and logout never access the old service. */
483
- export function migrateLegacyAuthEntry(serverName, legacyDirectory = process.env.MCP_OAUTH_DIR?.trim() || getAgentPath('mcp-oauth')) {
484
- if (readSecureAuthEntry(serverName) !== undefined)
485
- return 'existing';
523
+ export function migrateLegacyAuthEntry(serverName, legacyDirectory = process.env.MCP_OAUTH_DIR?.trim() || getAgentPath("mcp-oauth")) {
524
+ if (readSecureAuthEntry(serverName) !== undefined) {
525
+ return "existing";
526
+ }
486
527
  let entry = readSecureAuthEntry(serverName, LEGACY_AUTH_SECRET_SERVICE);
487
528
  if (entry === undefined) {
488
- const path = join(legacyDirectory, getAuthEntryAccount(serverName), 'tokens.json');
489
- if (!existsSync(path))
490
- return 'absent';
491
- entry = parseAuthEntryPayload(serverName, readFileSync(path, 'utf8'), path);
529
+ const path = join(legacyDirectory, getAuthEntryAccount(serverName), "tokens.json");
530
+ if (!existsSync(path)) {
531
+ return "absent";
532
+ }
533
+ entry = parseAuthEntryPayload(serverName, readFileSync(path, "utf8"), path);
492
534
  }
493
- if (!entry || typeof entry !== 'object' || Array.isArray(entry)) {
535
+ if (!entry || typeof entry !== "object" || Array.isArray(entry)) {
494
536
  throw new Error(`Invalid legacy OAuth entry for ${serverName}`);
495
537
  }
496
538
  writeSecureAuthEntry(serverName, entry);
497
539
  if (JSON.stringify(readSecureAuthEntry(serverName)) !== JSON.stringify(entry)) {
498
540
  throw new Error(`Failed to verify migrated OAuth credentials for ${serverName}`);
499
541
  }
500
- return 'copied';
542
+ return "copied";
501
543
  }
502
544
  /**
503
545
  * Get auth entry for a server.
@@ -511,14 +553,17 @@ export function getAuthEntry(serverName, options) {
511
553
  */
512
554
  export function getAuthForUrl(serverName, serverUrl, options) {
513
555
  const entry = getAuthEntry(serverName, options);
514
- if (!entry)
556
+ if (!entry) {
515
557
  return undefined;
558
+ }
516
559
  // If no serverUrl is stored, this is from an old version - consider it invalid
517
- if (!entry.serverUrl)
560
+ if (!entry.serverUrl) {
518
561
  return undefined;
562
+ }
519
563
  // If URL has changed, credentials are invalid
520
- if (entry.serverUrl !== serverUrl)
564
+ if (entry.serverUrl !== serverUrl) {
521
565
  return undefined;
566
+ }
522
567
  return entry;
523
568
  }
524
569
  /**
@@ -529,14 +574,16 @@ export function getAuthForUrl(serverName, serverUrl, options) {
529
574
  export function inspectAuthForUrl(serverName, serverUrl, options) {
530
575
  try {
531
576
  const entry = readAuthEntry(serverName, options, { migrateLegacy: false });
532
- if (!entry?.serverUrl || entry.serverUrl !== serverUrl)
533
- return { status: 'absent' };
534
- return { status: 'present', entry };
577
+ if (!entry?.serverUrl || entry.serverUrl !== serverUrl) {
578
+ return { status: "absent" };
579
+ }
580
+ return { status: "present", entry };
535
581
  }
536
582
  catch (error) {
537
- if (!(error instanceof OAuthCredentialStoreError))
583
+ if (!(error instanceof OAuthCredentialStoreError)) {
538
584
  throw error;
539
- return { status: 'unavailable', message: formatOAuthCredentialStoreUnavailable(error) };
585
+ }
586
+ return { status: "unavailable", message: formatOAuthCredentialStoreUnavailable(error) };
540
587
  }
541
588
  }
542
589
  /**
@@ -557,13 +604,16 @@ function removeAuthEntryFromStore(store, serverName) {
557
604
  const account = getAuthEntryAccount(serverName);
558
605
  try {
559
606
  const payload = store.read(account);
560
- const manifest = payload === undefined ? undefined : readChunkManifestFromPayload(serverName, payload, 'OS secure credential store');
561
- if (manifest)
607
+ const manifest = payload === undefined
608
+ ? undefined
609
+ : readChunkManifestFromPayload(serverName, payload, "OS secure credential store");
610
+ if (manifest) {
562
611
  removeChunkPayloads(store, account, manifest);
612
+ }
563
613
  store.remove(account);
564
614
  }
565
615
  catch (error) {
566
- throw new OAuthCredentialStoreError(`Failed to remove OAuth credentials for ${serverName} from the OS secure credential store`, 'remove', error);
616
+ throw new OAuthCredentialStoreError(`Failed to remove OAuth credentials for ${serverName} from the OS secure credential store`, "remove", error);
567
617
  }
568
618
  }
569
619
  export function removeAuthEntry(serverName, options) {
@@ -571,8 +621,9 @@ export function removeAuthEntry(serverName, options) {
571
621
  removeAuthEntryFromStore(getAuthSecretStore(), serverName);
572
622
  }
573
623
  catch (error) {
574
- if (!shouldAttemptLinuxKeyringRecovery(error))
624
+ if (!shouldAttemptLinuxKeyringRecovery(error)) {
575
625
  throw error;
626
+ }
576
627
  removeAuthEntryFromStore(linuxKeyringRecoveryAuthSecretStore(), serverName);
577
628
  }
578
629
  removeLegacyAuthEntry(serverName, options);