@iwas/mcp 0.0.0-stage → 1.0.1

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.
@@ -0,0 +1,631 @@
1
+ /**
2
+ * MCP Authentication & Credential Types
3
+ *
4
+ * Implements StoredMcpCredentials, profile key normalization,
5
+ * and security-hardened credential redaction (DEV-08 / RFC 8628).
6
+ */
7
+ interface McpCredentials {
8
+ accessToken: string;
9
+ refreshToken?: string;
10
+ expiresAt?: number;
11
+ tokenType?: string;
12
+ scope?: string;
13
+ }
14
+ interface StoredMcpCredentials {
15
+ serverUrl: string;
16
+ clientId: string;
17
+ accessToken: string;
18
+ refreshToken?: string;
19
+ tokenType: string;
20
+ scope?: string;
21
+ resource?: string;
22
+ organizationId: string | null;
23
+ expiresAt: number;
24
+ updatedAt?: number;
25
+ }
26
+ type VaultBackendType = 'keychain' | 'file' | 'auto';
27
+ /**
28
+ * Validates that an object conforms to the StoredMcpCredentials contract.
29
+ */
30
+ declare function isValidStoredCredentials(obj: unknown): obj is StoredMcpCredentials;
31
+ /**
32
+ * Normalizes a server URL and client ID into a canonical profile key.
33
+ * Format: `<canonical_origin>::<client_id>`
34
+ * Example: `https://getiwas.com::iwas-cli`
35
+ *
36
+ * Notes:
37
+ * - Scheme check is case-insensitive per RFC 3986 (n1 fix).
38
+ * - Server origin is lowercased and stripped of trailing slashes.
39
+ * - Client ID is case-preserving per OAuth 2.1 (RFC 6749 §2.2).
40
+ * - Never includes tokens or sensitive parameters in the key.
41
+ */
42
+ declare function normalizeProfileKey(serverUrl: string, clientId: string): string;
43
+ /**
44
+ * Redacts secret token material from credential objects for safe logging & diagnostics.
45
+ * Completely replaces access and refresh tokens with '[REDACTED]' to avoid leaking prefixes.
46
+ */
47
+ declare function redactCredentials(creds: StoredMcpCredentials | McpCredentials): Record<string, unknown>;
48
+ /**
49
+ * Strips secret tokens from error messages or arbitrary strings.
50
+ * Requires minimum length >= 4 to avoid corrupting short message substrings (n5 fix).
51
+ */
52
+ declare function sanitizeSecretString(input: string, secrets: (string | undefined)[]): string;
53
+ /**
54
+ * Checks whether credentials have expired or are within a clock-skew window.
55
+ * Supports both epoch milliseconds and epoch seconds.
56
+ */
57
+ declare function isCredentialExpired(creds: StoredMcpCredentials, skewSeconds?: number): boolean;
58
+
59
+ /**
60
+ * OS Keychain Integration
61
+ *
62
+ * Implements native credential storage on macOS (security) and Linux (secret-tool).
63
+ * Windows and headless fallback environments defer to file vault (DEV-08).
64
+ *
65
+ * Security considerations:
66
+ * - Commands executed with `spawn` (shell: false, no string interpolation).
67
+ * - On Linux, secrets are piped via stdin to prevent `ps` argument visibility.
68
+ * - Errors sanitize and redact all secret strings before throwing.
69
+ * - Subprocesses bounded by default 5000ms timeout to prevent hanging CLI.
70
+ */
71
+ interface KeychainRunResult {
72
+ stdout: string;
73
+ stderr: string;
74
+ exitCode: number;
75
+ }
76
+ interface KeychainCommandRunner {
77
+ spawn(command: string, args: string[], options?: {
78
+ input?: string;
79
+ timeoutMs?: number;
80
+ }): Promise<KeychainRunResult>;
81
+ }
82
+ declare class DefaultKeychainRunner implements KeychainCommandRunner {
83
+ private readonly defaultTimeoutMs;
84
+ constructor(defaultTimeoutMs?: number);
85
+ spawn(command: string, args: string[], options?: {
86
+ input?: string;
87
+ timeoutMs?: number;
88
+ }): Promise<KeychainRunResult>;
89
+ }
90
+ interface KeychainOptions {
91
+ serviceName?: string;
92
+ runner?: KeychainCommandRunner;
93
+ platform?: NodeJS.Platform;
94
+ }
95
+ declare const DEFAULT_KEYCHAIN_SERVICE = "iwas-cli";
96
+ declare class KeychainService {
97
+ private readonly service;
98
+ private readonly runner;
99
+ private readonly platform;
100
+ constructor(options?: KeychainOptions);
101
+ private availableCache;
102
+ /**
103
+ * Returns the configured OS platform.
104
+ */
105
+ getPlatform(): NodeJS.Platform;
106
+ /**
107
+ * Probes whether the host environment has an operable keychain utility.
108
+ * Results are cached when available to avoid unnecessary subprocess spawns (n7 fix).
109
+ */
110
+ isAvailable(forceProbe?: boolean): Promise<boolean>;
111
+ /**
112
+ * Clears the cached isAvailable probe result. Useful for testing and environment shifts.
113
+ */
114
+ clearAvailabilityCache(): void;
115
+ /**
116
+ * Retrieves a secret password string by account identifier.
117
+ */
118
+ getSecret(account: string): Promise<string | null>;
119
+ /**
120
+ * Stores a secret password under the given account identifier.
121
+ */
122
+ setSecret(account: string, secret: string, label?: string): Promise<void>;
123
+ /**
124
+ * Deletes a secret from the keychain. Operation is strictly idempotent.
125
+ */
126
+ deleteSecret(account: string): Promise<void>;
127
+ }
128
+
129
+ /**
130
+ * MCP Credential Vault
131
+ *
132
+ * Implements secure storage for RFC 8628 Device Flow credentials with:
133
+ * - Native OS Keychain support (macOS security & Linux secret-tool)
134
+ * - Atomic filesystem fallback (~/.config/iwas/credentials.json, dir 0700, file 0600)
135
+ * - Strict vs Auto backend synchronization (DEV-08, Major 1 & 2 fixes)
136
+ * - Fail-closed corrupt JSON protection & token redaction
137
+ */
138
+
139
+ interface CredentialVaultOptions {
140
+ vaultType?: VaultBackendType;
141
+ serviceName?: string;
142
+ configDir?: string;
143
+ keychainRunner?: KeychainCommandRunner;
144
+ keychainService?: KeychainService;
145
+ }
146
+ declare class CredentialVault {
147
+ private readonly vaultType;
148
+ private readonly configDir;
149
+ private readonly credentialsFilePath;
150
+ private readonly keychain;
151
+ constructor(options?: CredentialVaultOptions);
152
+ /**
153
+ * Returns the configuration directory for this vault (~/.config/iwas).
154
+ */
155
+ getConfigDir(): string;
156
+ /**
157
+ * Returns the credentials file path (~/.config/iwas/credentials.json).
158
+ */
159
+ getCredentialsFilePath(): string;
160
+ /**
161
+ * Returns the resolved vault mode: 'keychain' or 'file'.
162
+ * In strict 'keychain' mode, throws if OS keychain is unavailable.
163
+ */
164
+ getEffectiveBackend(): Promise<'keychain' | 'file'>;
165
+ /**
166
+ * Retrieves stored credentials by profile key.
167
+ * In auto mode, checks keychain first; falls back to file if missing or failed.
168
+ * Compares timestamps if both exist and migrates newer file credentials to keychain (Medium fix).
169
+ */
170
+ load(profileKey: string): Promise<StoredMcpCredentials | null>;
171
+ /**
172
+ * Persists credentials for the given profile key.
173
+ * When saving to keychain, purges any stale copy from the file fallback (Major 1).
174
+ */
175
+ save(profileKey: string, credentials: StoredMcpCredentials): Promise<void>;
176
+ /**
177
+ * Removes credentials for the given profile key.
178
+ * Cleanses BOTH Keychain and File fallback to prevent plaintext leakage (Major 1).
179
+ * In auto mode, throws if keychain is available but secret deletion fails (Major fix).
180
+ */
181
+ delete(profileKey: string): Promise<void>;
182
+ /**
183
+ * Lists all stored profile keys.
184
+ * In auto mode, merges profiles from both Keychain and File.
185
+ */
186
+ listProfiles(): Promise<string[]>;
187
+ private readKeychainIndex;
188
+ private addProfileToKeychainIndex;
189
+ private removeProfileFromKeychainIndex;
190
+ private ensureConfigDirectory;
191
+ private readAllFromFile;
192
+ private writeAllToFile;
193
+ private loadFromFile;
194
+ private saveToFile;
195
+ private deleteFromFile;
196
+ private listProfilesFromFile;
197
+ }
198
+
199
+ /**
200
+ * HTTP Client & RFC 8628 Network Utilities
201
+ *
202
+ * Implements bounded timeouts, exponential backoff with jitter for transient errors,
203
+ * structured JSON/Form serialization, and strict secret redaction (DEV-09).
204
+ */
205
+ interface RequestOptions {
206
+ method?: 'GET' | 'POST' | 'DELETE' | 'PUT';
207
+ headers?: Record<string, string>;
208
+ body?: string | URLSearchParams | Record<string, unknown>;
209
+ timeoutMs?: number;
210
+ retries?: number;
211
+ fetchFn?: typeof fetch;
212
+ }
213
+ interface HttpResponse<T = unknown> {
214
+ status: number;
215
+ ok: boolean;
216
+ headers: Headers;
217
+ data: T;
218
+ }
219
+ declare class HttpError extends Error {
220
+ readonly status: number;
221
+ readonly data: unknown;
222
+ constructor(message: string, status: number, data?: unknown);
223
+ }
224
+ /**
225
+ * Executes an HTTP fetch with timeout and bounded exponential retries for transient 5xx errors.
226
+ */
227
+ declare function sendRequest<T = unknown>(url: string, options?: RequestOptions): Promise<HttpResponse<T>>;
228
+
229
+ /**
230
+ * MCP CLI Logout & Server Grant Revocation (DEV-11)
231
+ *
232
+ * Implements server-side session and OAuth grant revocation for RFC 8628 Device Flow:
233
+ * 1. Resolves stored credentials for the server profile.
234
+ * 2. Calls POST /api/v1/mcp-session/logout with bearer/DPoP credentials to atomically
235
+ * revoke all user consents and live tokens server-side.
236
+ * 3. Calls POST /api/auth/oauth2/revoke with refresh_token (defense-in-depth RFC 7009).
237
+ * 4. Clears local vault credentials only after successful server revocation (or with --force).
238
+ * 5. Strictly protects against token leakage in log or error output.
239
+ */
240
+
241
+ interface LogoutOptions {
242
+ server?: string;
243
+ clientId?: string;
244
+ force?: boolean;
245
+ json?: boolean;
246
+ vault?: CredentialVault;
247
+ fetchFn?: typeof fetch;
248
+ logger?: (msg: string) => void;
249
+ errorLogger?: (msg: string) => void;
250
+ }
251
+ interface LogoutResult {
252
+ success: boolean;
253
+ profileKey: string;
254
+ serverRevoked: boolean;
255
+ refreshTokenRevoked: boolean;
256
+ localCleared: boolean;
257
+ message: string;
258
+ }
259
+ /**
260
+ * Logs out from the specified server profile:
261
+ * - Revokes user session and OAuth consent at /api/v1/mcp-session/logout
262
+ * - Revokes refresh token at /api/auth/oauth2/revoke (RFC 7009)
263
+ * - Removes local credentials from the vault on success or with --force
264
+ */
265
+ declare function executeLogout(options?: LogoutOptions): Promise<LogoutResult>;
266
+
267
+ /**
268
+ * RFC 8628 OAuth 2.0 Device Authorization Grant CLI Client
269
+ *
270
+ * Implements end-to-end device authorization flow:
271
+ * - Safe URL resolution (auth origin vs MCP resource)
272
+ * - Device code acquisition & user code display (device_code never logged)
273
+ * - RFC 8628 polling state machine (slow_down +5s, authorization_pending, expiry, denial)
274
+ * - In-memory JWT payload decoding for metadata and audience/scope verification
275
+ * - Persistence to OS Keychain via CredentialVault (DEV-08)
276
+ */
277
+
278
+ declare const DEFAULT_CLI_CLIENT_ID = "iwas-cli";
279
+ declare const DEFAULT_SERVER_URL = "https://getiwas.com";
280
+ /**
281
+ * Canonical read-only scopes eligible for MCP tool usage.
282
+ * Derived dynamically from MCP_ELIGIBLE_PERMISSIONS to maintain a single source of truth.
283
+ * Mutating/destructive actions (:create, :update, :delete, :control, :manage, :issue, :revoke, :publish, :write)
284
+ * are excluded from default scopes to enforce strict least-privilege.
285
+ * Role-based permission boundaries (such as platform vs tenant scopes) are strictly enforced by the server's RBAC engine.
286
+ */
287
+ declare const DEFAULT_READ_ONLY_SCOPES: readonly string[];
288
+ /**
289
+ * Least-privilege default scopes: all read-only capability scopes plus offline_access for refresh grants.
290
+ */
291
+ declare const DEFAULT_SCOPES: string;
292
+ declare const DEVICE_CODE_GRANT_TYPE = "urn:ietf:params:oauth:grant-type:device_code";
293
+ interface DeviceAuthorizationResponse {
294
+ device_code: string;
295
+ user_code: string;
296
+ verification_uri: string;
297
+ verification_uri_complete: string;
298
+ expires_in: number;
299
+ interval: number;
300
+ }
301
+ interface OAuthTokenSuccessResponse {
302
+ access_token: string;
303
+ token_type: string;
304
+ expires_in: number;
305
+ refresh_token?: string;
306
+ scope?: string;
307
+ }
308
+ interface OAuthTokenErrorResponse {
309
+ error: string;
310
+ error_description?: string;
311
+ }
312
+ interface DeviceLoginOptions {
313
+ server?: string;
314
+ resource?: string;
315
+ clientId?: string;
316
+ scopes?: string;
317
+ noBrowser?: boolean;
318
+ json?: boolean;
319
+ vault?: CredentialVault;
320
+ logger?: (msg: string) => void;
321
+ errorLogger?: (msg: string) => void;
322
+ pollTimeoutMs?: number;
323
+ browserOpener?: (url: string) => boolean;
324
+ spawnFn?: SpawnFunction;
325
+ }
326
+ interface DeviceLoginResult {
327
+ profileKey: string;
328
+ serverUrl: string;
329
+ clientId: string;
330
+ organizationId: string | null;
331
+ scope?: string;
332
+ resource?: string;
333
+ expiresAt: number;
334
+ }
335
+ /**
336
+ * Validates that candidate scopes are all supported in MCP_OAUTH_SCOPES
337
+ * and that at least one capability scope besides offline_access is requested.
338
+ */
339
+ declare function validateScopes(scopesString: string): {
340
+ valid: boolean;
341
+ invalidScopes: string[];
342
+ error?: string;
343
+ };
344
+ /**
345
+ * Checks whether a hostname corresponds to loopback/localhost (IPv4 or IPv6).
346
+ */
347
+ declare function isLocalhost(hostname: string): boolean;
348
+ /**
349
+ * Resolves and validates the Auth Server Origin.
350
+ * Strips trailing slashes, ensures HTTPS outside localhost, rejects non-root paths,
351
+ * and strictly rejects resource URLs ending in /mcp as the server base.
352
+ */
353
+ declare function resolveAuthServerUrl(input?: string): string;
354
+ /**
355
+ * Resolves the target MCP OAuth resource URL (default: `${server}/mcp`).
356
+ * Enforces HTTPS outside localhost matching server origin validation rules.
357
+ */
358
+ declare function resolveMcpResourceUrl(serverUrl: string, input?: string): string;
359
+ /**
360
+ * Decodes a JWT payload locally for display and metadata extraction.
361
+ * Does NOT perform signature validation (that is solely the server's responsibility).
362
+ */
363
+ declare function decodeJwtPayload(token: string): Record<string, unknown>;
364
+ type SpawnFunction = (command: string, args: readonly string[], options: {
365
+ detached?: boolean;
366
+ stdio?: string;
367
+ }) => {
368
+ on: (event: string, listener: (...args: unknown[]) => void) => unknown;
369
+ unref: () => void;
370
+ };
371
+ /**
372
+ * Best-effort cross-platform browser launch.
373
+ * Handles process errors safely to prevent uncaught ENOENT crashes on headless environments.
374
+ */
375
+ declare function openBrowser(url: string, spawnFn?: SpawnFunction, onWarning?: (msg: string) => void): boolean;
376
+ /**
377
+ * Step 1: Obtains a device authorization code from the server.
378
+ */
379
+ declare function requestDeviceCode(serverUrl: string, params: {
380
+ clientId: string;
381
+ scope: string;
382
+ resource: string;
383
+ }): Promise<DeviceAuthorizationResponse>;
384
+ /**
385
+ * Step 2: Polls the OAuth2 token endpoint until user approves or flow terminates.
386
+ */
387
+ declare function pollDeviceToken(serverUrl: string, params: {
388
+ clientId: string;
389
+ deviceCode: string;
390
+ resource: string;
391
+ initialInterval?: number;
392
+ timeoutMs?: number;
393
+ }, onPollTick?: (status: string) => void): Promise<OAuthTokenSuccessResponse>;
394
+ /**
395
+ * Orchestrates the full login flow, saves credentials in vault, and formats output.
396
+ */
397
+ declare function executeLogin(options?: DeviceLoginOptions): Promise<DeviceLoginResult>;
398
+ /**
399
+ * Checks and outputs stored authentication status for the given profile.
400
+ */
401
+ declare function executeStatus(options?: {
402
+ server?: string;
403
+ clientId?: string;
404
+ json?: boolean;
405
+ vault?: CredentialVault;
406
+ logger?: (msg: string) => void;
407
+ }): Promise<void>;
408
+
409
+ /**
410
+ * MCP Vault Cross-Process Lock
411
+ *
412
+ * Implements atomic cross-process file locking for credential operations (DEV-10).
413
+ * Prevents race conditions and refresh-token family destruction when multiple CLI
414
+ * commands or agent processes refresh tokens concurrently.
415
+ *
416
+ * Key guarantees:
417
+ * - Atomic acquisition via 'wx' flag (O_CREAT | O_EXCL) with mode 0600.
418
+ * - Stale lock detection via process liveness probe: Never reclaims a lock held by a live process.
419
+ * - TOCTOU-safe stale reclamation via hardlink + nlink verification (fail-closed without hardlinks).
420
+ * - Ownership & inode verification: Never unlinks or overwrites a lock owned by another process.
421
+ * - Non-intrusive parent directory permissions: Only restricts dedicated app directories, never arbitrary caller paths.
422
+ * - Guaranteed release in finally block on all paths.
423
+ */
424
+ declare class VaultLockError extends Error {
425
+ constructor(message: string);
426
+ }
427
+ declare class VaultLockTimeoutError extends VaultLockError {
428
+ readonly lockPath: string;
429
+ readonly timeoutMs: number;
430
+ constructor(lockPath: string, timeoutMs: number);
431
+ }
432
+ interface VaultLockData {
433
+ pid: number;
434
+ createdAt: number;
435
+ lockId: string;
436
+ hostname?: string;
437
+ }
438
+ interface VaultLockOptions {
439
+ lockPath?: string;
440
+ configDir?: string;
441
+ timeoutMs?: number;
442
+ staleTimeoutMs?: number;
443
+ corruptLockGraceMs?: number;
444
+ retryIntervalMs?: number;
445
+ isProcessAliveFn?: (pid: number) => boolean;
446
+ }
447
+ /**
448
+ * Checks whether a process with the given PID is alive on the current system.
449
+ */
450
+ declare function defaultIsProcessAlive(pid: number): boolean;
451
+ declare class VaultLock {
452
+ readonly lockPath: string;
453
+ readonly isCustomLockPath: boolean;
454
+ readonly timeoutMs: number;
455
+ readonly staleTimeoutMs: number;
456
+ readonly corruptLockGraceMs: number;
457
+ readonly retryIntervalMs: number;
458
+ private readonly isProcessAlive;
459
+ private currentLockId;
460
+ private currentLockIno?;
461
+ constructor(options?: VaultLockOptions);
462
+ /**
463
+ * Returns whether this instance currently holds the active lock.
464
+ */
465
+ isHeld(): boolean;
466
+ /**
467
+ * Returns the current lock ID if held.
468
+ */
469
+ getCurrentLockId(): string | null;
470
+ /**
471
+ * Acquires the lock exclusively.
472
+ * Retries with bounded exponential backoff and jitter if held by another process.
473
+ * Recovers automatically if the lock is determined to be stale.
474
+ */
475
+ acquire(): Promise<void>;
476
+ /**
477
+ * Releases the lock file safely.
478
+ * Strictly enforces ownership and inode integrity: Never deletes a lock file owned by a different process.
479
+ */
480
+ release(): Promise<void>;
481
+ /**
482
+ * Synchronous release helper for process exit handlers.
483
+ *
484
+ * Uses a hardlink guard so the inode we verified cannot be swapped for another
485
+ * process's lock between the check and the unlink. If hardlinks are unavailable,
486
+ * falls back to the stat-verified path (safe for the owner in practice).
487
+ */
488
+ releaseSync(): void;
489
+ /**
490
+ * Fallback release for filesystems without hardlink support.
491
+ *
492
+ * Hardlink creation can fail on filesystems lacking link support (e.g. FAT32, certain network mounts).
493
+ * In this fallback, the owner verifies that:
494
+ * 1. The lock file exists and matches the inode recorded at acquisition time.
495
+ * 2. The JSON payload matches lockIdToRelease and process.pid.
496
+ * 3. A post-read stat confirms the path still points to the same inode before unlinking.
497
+ *
498
+ * Note on TOCTOU: While a theoretical micro-window exists between post-read stat and unlinkSync,
499
+ * in practice only the legitimate owner releases an active, unexpired lock. If the lock was already
500
+ * unlinked or replaced by another inode, unlink is safely skipped.
501
+ */
502
+ releaseSyncLoose(lockIdToRelease: string, lockInoToRelease?: number): void;
503
+ /**
504
+ * Sweeps orphan temporary guard files (.credentials.lock.release.*, .stale.*, .corrupt.*)
505
+ * left behind if a previous process crashed between linkSync and unlinkSync.
506
+ *
507
+ * Hardlink safety:
508
+ * 1. Hardlinks share `mtime` with the primary lock file. If a lock was held for > 60s,
509
+ * a newly created release/quarantine hardlink will have an old `mtime`.
510
+ * Therefore, age is strictly calculated from the timestamp embedded in the guard filename,
511
+ * NOT from `stat.mtimeMs`.
512
+ * 2. A guard still hardlinked to the current `lockPath` inode (nlink > 1 and
513
+ * the same inode) is active and is NEVER deleted.
514
+ * 3. Only timestamped-UUID guard names are considered. Guards from a replaced or
515
+ * removed lock (different inode / missing lockPath) are swept once their embedded
516
+ * timestamp exceeds `staleTimeoutMs`.
517
+ */
518
+ cleanOrphanGuards(): void;
519
+ /**
520
+ * Executes an async operation within an exclusive vault lock.
521
+ * Guarantees that release() is called in finally.
522
+ */
523
+ withLock<T>(fn: () => Promise<T>): Promise<T>;
524
+ /**
525
+ * Inspects existing lock file and safely reclaims it if stale.
526
+ *
527
+ * Hardened rules (M1 & TOCTOU fix):
528
+ * 1. Same-host lock: Only reclaimed if the holding process is confirmed DEAD (!isProcessAlive).
529
+ * If the process is ALIVE, the lock is NEVER deleted regardless of age.
530
+ * 2. Remote-host lock: Reclaimed only if age exceeds staleTimeoutMs (since cross-host PID is untestable).
531
+ * 3. TOCTOU prevention: Uses hardlink + nlink verification and non-overwriting semantics.
532
+ * Never replaces or destroys an active lock file acquired by a racing process.
533
+ */
534
+ private tryCleanStaleLock;
535
+ /**
536
+ * TOCTOU-safe stale lock removal using hardlink + nlink verification.
537
+ * Ensures that if another process acquires this.lockPath before or during cleanup,
538
+ * its active lock is never deleted or overwritten.
539
+ */
540
+ private reclaimStaleLock;
541
+ /**
542
+ * TOCTOU-safe corrupt lock removal with inode, size, and mtime verification.
543
+ */
544
+ private reclaimCorruptLock;
545
+ }
546
+ /**
547
+ * Executes a function within an exclusive vault lock.
548
+ */
549
+ declare function withVaultLock<T>(lockPath: string, fn: () => Promise<T>, options?: Omit<VaultLockOptions, 'lockPath'>): Promise<T>;
550
+
551
+ /**
552
+ * MCP Token Refresh Manager
553
+ *
554
+ * Implements transparent, concurrent-safe token refresh for RFC 8628 (DEV-10).
555
+ *
556
+ * Key guarantees:
557
+ * - In-process single-flight collapsing concurrent refresh calls for the same profile.
558
+ * - Cross-process mutual exclusion via VaultLock (credentials.lock) to protect token family.
559
+ * - Double-checked locking pattern: Avoids redundant server requests if a concurrent
560
+ * process already refreshed the token family.
561
+ * - Terminal error handling: ONLY invalid_grant / invalid_client clears vault and raises
562
+ * ReauthenticationRequiredError. Non-terminal 4xx errors preserve vault credentials.
563
+ * - Transient error handling: 5xx and network failures retry with bounded backoff and
564
+ * never wipe local credentials. Single retry on POST is protected by the provider's
565
+ * 30s reuse grace window (MCP-OAUTH-04).
566
+ * - Persistence failure treated as terminal: Clears vault and raises ReauthenticationRequiredError
567
+ * to avoid reusing now-rotated refresh tokens.
568
+ * - Atomic persistence before lock release.
569
+ * - Zero token leakage in logs or error messages.
570
+ */
571
+
572
+ declare class ReauthenticationRequiredError extends Error {
573
+ readonly profileKey: string;
574
+ readonly reason: string;
575
+ readonly terminalError?: string;
576
+ constructor(profileKey: string, reason: string, terminalError?: string);
577
+ }
578
+ interface TokenRefreshManagerOptions {
579
+ vault?: CredentialVault;
580
+ safetyMarginSeconds?: number;
581
+ lockTimeoutMs?: number;
582
+ staleLockTimeoutMs?: number;
583
+ staleTimeoutMs?: number;
584
+ lockPath?: string;
585
+ fetchFn?: typeof fetch;
586
+ logger?: (msg: string) => void;
587
+ errorLogger?: (msg: string) => void;
588
+ }
589
+ declare class TokenRefreshManager {
590
+ private readonly vault;
591
+ private readonly safetyMarginSeconds;
592
+ private readonly lockTimeoutMs;
593
+ private readonly staleLockTimeoutMs;
594
+ private readonly lockPath?;
595
+ private readonly fetchFn?;
596
+ private readonly logger?;
597
+ private readonly errorLogger?;
598
+ private readonly inFlightRefreshes;
599
+ constructor(options?: TokenRefreshManagerOptions);
600
+ /**
601
+ * Returns a valid access token for the given profile key.
602
+ * If the current access token is within the safety margin or expired, refreshes
603
+ * transparently using the stored refresh token behind a cross-process lock.
604
+ */
605
+ getValidAccessToken(profileKey: string): Promise<string>;
606
+ /**
607
+ * Returns valid stored credentials for the profile key, refreshing if necessary.
608
+ */
609
+ getValidCredentials(profileKey: string): Promise<StoredMcpCredentials>;
610
+ /**
611
+ * Explicitly forces a token refresh regardless of current expiry time.
612
+ * Reuses an in-flight refresh promise if one is already active (m7 fix).
613
+ */
614
+ refresh(profileKey: string): Promise<StoredMcpCredentials>;
615
+ /**
616
+ * Collapses concurrent in-process calls for the same profileKey to a single execution.
617
+ */
618
+ private refreshWithSingleFlight;
619
+ /**
620
+ * Acquires the cross-process lock, double-checks if another process already refreshed,
621
+ * and invokes the server refresh endpoint if still needed.
622
+ */
623
+ private executeRefreshWithLock;
624
+ /**
625
+ * Performs the HTTP refresh exchange with the OAuth2 token endpoint.
626
+ */
627
+ private performTokenExchange;
628
+ }
629
+
630
+ export { CredentialVault, DEFAULT_CLI_CLIENT_ID, DEFAULT_KEYCHAIN_SERVICE, DEFAULT_READ_ONLY_SCOPES, DEFAULT_SCOPES, DEFAULT_SERVER_URL, DEVICE_CODE_GRANT_TYPE, DefaultKeychainRunner, HttpError, KeychainService, ReauthenticationRequiredError, TokenRefreshManager, VaultLock, VaultLockError, VaultLockTimeoutError, decodeJwtPayload, defaultIsProcessAlive, executeLogin, executeLogout, executeStatus, isCredentialExpired, isLocalhost, isValidStoredCredentials, normalizeProfileKey, openBrowser, pollDeviceToken, redactCredentials, requestDeviceCode, resolveAuthServerUrl, resolveMcpResourceUrl, sanitizeSecretString, sendRequest, validateScopes, withVaultLock };
631
+ export type { CredentialVaultOptions, DeviceAuthorizationResponse, DeviceLoginOptions, DeviceLoginResult, HttpResponse, KeychainCommandRunner, KeychainOptions, KeychainRunResult, LogoutOptions, LogoutResult, McpCredentials, OAuthTokenErrorResponse, OAuthTokenSuccessResponse, RequestOptions, SpawnFunction, StoredMcpCredentials, TokenRefreshManagerOptions, VaultBackendType, VaultLockData, VaultLockOptions };