inspector/core/auth/node/storage-node.ts
cliffhall 32a3610737 Merge remote-tracking branch 'origin/v2/main' into 1579-wave-1
# Conflicts:
#	clients/cli/package-lock.json
#	clients/cli/package.json
#	clients/web/src/App.tsx
#	clients/web/src/lib/environmentFactory.test.ts
#	clients/web/src/lib/environmentFactory.ts
#	clients/web/src/lib/remoteOAuthStorage.test.ts
#	clients/web/src/lib/remoteOAuthStorage.ts
#	clients/web/src/test/core/auth/connection-state.test.ts
#	clients/web/src/test/core/auth/providers.test.ts
#	clients/web/src/test/core/auth/storage-browser.test.ts
#	clients/web/src/test/core/auth/storage-remote.test.ts
#	clients/web/src/test/core/mcp/oauthManager.test.ts
#	clients/web/src/test/integration/auth/node/storage.test.ts
#	clients/web/src/test/integration/storage/adapters.test.ts
#	clients/web/src/utils/clearServerOAuthState.test.ts
#	clients/web/src/utils/clearServerOAuthState.ts
#	core/auth/connection-state.ts
#	core/auth/oauth-storage.ts
#	core/auth/providers.ts
#	core/auth/storage.ts
#	core/auth/store.ts
#	core/storage/adapters/remote-storage.ts
2026-07-09 09:41:08 -04:00

90 lines
2.9 KiB
TypeScript

import { OAuthStorageBase } from "../oauth-storage.js";
import { OAuthMemoryStore } from "../store.js";
import { createFileOAuthPersistBackend } from "./oauth-persist-file.js";
import {
getDefaultStorageDir,
getStoreFilePath,
} from "../../storage/store-io.js";
/** Default path: ~/.mcp-inspector/storage/oauth.json */
const DEFAULT_STATE_PATH = getStoreFilePath(getDefaultStorageDir(), "oauth");
/**
* Get path to OAuth state file. Resolution order: explicit `customPath`, then
* the `MCP_INSPECTOR_OAUTH_STATE_PATH` environment variable (so tests and
* scripted runs can point at an isolated fixture without touching
* `~/.mcp-inspector`), then the default `~/.mcp-inspector/storage/oauth.json`.
*/
export function getStateFilePath(customPath?: string): string {
return (
customPath ??
process.env.MCP_INSPECTOR_OAUTH_STATE_PATH ??
DEFAULT_STATE_PATH
);
}
const memoryCache = new Map<string, OAuthMemoryStore>();
const storageCache = new Map<string, NodeOAuthStorage>();
function getSharedMemory(stateFilePath?: string): OAuthMemoryStore {
const key = getStateFilePath(stateFilePath);
let memory = memoryCache.get(key);
if (!memory) {
memory = new OAuthMemoryStore();
memoryCache.set(key, memory);
}
return memory;
}
/**
* Drop cached in-memory and {@link NodeOAuthStorage} instances for a path.
* @internal Test isolation only.
*/
export function resetNodeOAuthStorageCache(stateFilePath?: string): void {
const key = getStateFilePath(stateFilePath);
memoryCache.delete(key);
storageCache.delete(key);
}
/**
* Clear all OAuth client state (all servers) in the default store.
* Useful for test isolation in E2E OAuth tests.
* Use a custom-path store and clear per serverUrl if you need to clear non-default storage.
*/
export async function clearAllOAuthClientState(): Promise<void> {
const storage = getNodeOAuthStorage();
const filePath = getStateFilePath();
const snapshot = await createFileOAuthPersistBackend({ filePath }).read();
const urls = Object.keys(snapshot?.servers ?? {});
for (const url of urls) {
await storage.clear(url);
}
}
/**
* Node.js storage implementation with file-based persistence.
* For InspectorClient, CLI, and TUI.
*/
export class NodeOAuthStorage extends OAuthStorageBase {
/**
* @param storagePath - Optional path to state file. Default: ~/.mcp-inspector/storage/oauth.json
*/
constructor(storagePath?: string) {
const filePath = getStateFilePath(storagePath);
super(
getSharedMemory(storagePath),
createFileOAuthPersistBackend({ filePath }),
);
}
}
/** Cached NodeOAuthStorage instances keyed by resolved file path. */
function getNodeOAuthStorage(storagePath?: string): NodeOAuthStorage {
const key = getStateFilePath(storagePath);
let storage = storageCache.get(key);
if (!storage) {
storage = new NodeOAuthStorage(storagePath);
storageCache.set(key, storage);
}
return storage;
}