@gleanwork/mcp-server-tester 0.12.0 → 1.0.0-beta.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.
- package/README.md +120 -337
- package/dist/cli/index.js +468 -176
- package/dist/fixtures/mcp.d.ts +121 -44
- package/dist/fixtures/mcp.js +988 -248
- package/dist/fixtures/mcp.js.map +1 -1
- package/dist/fixtures/mcpAuth.js +6 -2
- package/dist/fixtures/mcpAuth.js.map +1 -1
- package/dist/index.cjs +5034 -1284
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1697 -575
- package/dist/index.d.ts +1697 -575
- package/dist/index.js +5020 -1280
- package/dist/index.js.map +1 -1
- package/dist/reporters/mcpReporter.cjs +35 -16
- package/dist/reporters/mcpReporter.cjs.map +1 -1
- package/dist/reporters/mcpReporter.d.cts +8 -3
- package/dist/reporters/mcpReporter.d.ts +8 -3
- package/dist/reporters/mcpReporter.js +36 -17
- package/dist/reporters/mcpReporter.js.map +1 -1
- package/dist/reporters/ui-dist/app.js +5 -5
- package/dist/reporters/ui-dist/styles.css +1 -1
- package/package.json +64 -8
- package/src/reporters/ui-dist/app.js +5 -5
- package/src/reporters/ui-dist/styles.css +1 -1
package/dist/fixtures/mcpAuth.js
CHANGED
|
@@ -92,6 +92,9 @@ var PlaywrightOAuthClientProvider = class {
|
|
|
92
92
|
}
|
|
93
93
|
/**
|
|
94
94
|
* Stores new OAuth tokens for the current session
|
|
95
|
+
*
|
|
96
|
+
* The code verifier is cleared after a successful token exchange — it is
|
|
97
|
+
* single-use per PKCE spec and must not persist beyond the exchange.
|
|
95
98
|
*/
|
|
96
99
|
async saveTokens(tokens) {
|
|
97
100
|
const state = await this.loadState() ?? this.createEmptyState();
|
|
@@ -101,6 +104,7 @@ var PlaywrightOAuthClientProvider = class {
|
|
|
101
104
|
refreshToken: tokens.refresh_token,
|
|
102
105
|
expiresAt: tokens.expires_in ? Date.now() + tokens.expires_in * 1e3 : void 0
|
|
103
106
|
};
|
|
107
|
+
delete state.codeVerifier;
|
|
104
108
|
await this.saveState(state);
|
|
105
109
|
}
|
|
106
110
|
/**
|
|
@@ -180,11 +184,11 @@ In a testing context, use performOAuthSetup() in your Playwright globalSetup to
|
|
|
180
184
|
state.savedAt = Date.now();
|
|
181
185
|
this.cachedState = state;
|
|
182
186
|
const dir = path.dirname(this.config.storagePath);
|
|
183
|
-
await fs.mkdir(dir, { recursive: true });
|
|
187
|
+
await fs.mkdir(dir, { recursive: true, mode: 448 });
|
|
184
188
|
await fs.writeFile(
|
|
185
189
|
this.config.storagePath,
|
|
186
190
|
JSON.stringify(state, null, 2),
|
|
187
|
-
"utf-8"
|
|
191
|
+
{ encoding: "utf-8", mode: 384 }
|
|
188
192
|
);
|
|
189
193
|
}
|
|
190
194
|
async deleteState() {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/auth/oauthClientProvider.ts","../../src/fixtures/mcpAuth.ts"],"names":["base"],"mappings":"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","file":"mcpAuth.js","sourcesContent":["/**\n * OAuth client provider implementation for MCP SDK\n *\n * Implements the MCP SDK's OAuthClientProvider interface using file-based storage\n * for integration with Playwright's auth state pattern.\n */\n\nimport * as fs from 'node:fs/promises';\nimport * as path from 'node:path';\nimport type { OAuthClientProvider } from '@modelcontextprotocol/sdk/client/auth.js';\nimport type {\n OAuthClientMetadata,\n OAuthClientInformationFull,\n OAuthTokens,\n} from '@modelcontextprotocol/sdk/shared/auth.js';\nimport type { StoredOAuthState } from './types.js';\n\n/**\n * Configuration for the Playwright OAuth client provider\n */\nexport interface PlaywrightOAuthClientProviderConfig {\n /**\n * Path to the auth state file (e.g., playwright/.auth/oauth-state.json)\n */\n storagePath: string;\n\n /**\n * OAuth redirect URI for callback\n */\n redirectUri: string;\n\n /**\n * Client metadata for DCR or display\n */\n clientMetadata?: Partial<OAuthClientMetadata>;\n\n /**\n * Pre-registered client ID (if not using DCR)\n */\n clientId?: string;\n\n /**\n * Pre-registered client secret (if not using DCR)\n */\n clientSecret?: string;\n}\n\n/**\n * OAuth client provider that implements the MCP SDK's OAuthClientProvider interface\n *\n * Uses file-based storage for integration with Playwright's auth state pattern.\n * Auth state is persisted to disk so it can be reused across test runs.\n *\n * @example\n * ```typescript\n * const provider = new PlaywrightOAuthClientProvider({\n * storagePath: 'playwright/.auth/oauth-state.json',\n * redirectUri: 'http://localhost:3000/callback',\n * });\n *\n * const transport = new StreamableHTTPClientTransport(serverUrl, {\n * authProvider: provider,\n * });\n * ```\n */\nexport class PlaywrightOAuthClientProvider implements OAuthClientProvider {\n private readonly config: PlaywrightOAuthClientProviderConfig;\n private cachedState: StoredOAuthState | null = null;\n private stateParam: string | null = null;\n\n constructor(config: PlaywrightOAuthClientProviderConfig) {\n this.config = config;\n }\n\n /**\n * The URL to redirect the user agent to after authorization\n */\n get redirectUrl(): string {\n return this.config.redirectUri;\n }\n\n /**\n * Metadata about this OAuth client\n */\n get clientMetadata(): OAuthClientMetadata {\n return {\n redirect_uris: [this.config.redirectUri],\n token_endpoint_auth_method: this.config.clientSecret\n ? 'client_secret_basic'\n : 'none',\n grant_types: ['authorization_code', 'refresh_token'],\n response_types: ['code'],\n client_name: '@gleanwork/mcp-server-tester',\n ...this.config.clientMetadata,\n };\n }\n\n /**\n * Returns an OAuth2 state parameter\n */\n state(): string {\n if (!this.stateParam) {\n this.stateParam = this.generateRandomString(32);\n }\n return this.stateParam;\n }\n\n /**\n * Loads information about this OAuth client\n */\n async clientInformation(): Promise<OAuthClientInformationFull | undefined> {\n // If we have a pre-registered client, return it\n if (this.config.clientId) {\n return {\n client_id: this.config.clientId,\n client_secret: this.config.clientSecret,\n redirect_uris: [this.config.redirectUri],\n };\n }\n\n // Otherwise, try to load from storage (DCR result)\n const state = await this.loadState();\n if (state?.clientInfo) {\n return {\n client_id: state.clientInfo.clientId,\n client_secret: state.clientInfo.clientSecret,\n client_id_issued_at: state.clientInfo.clientIdIssuedAt,\n client_secret_expires_at: state.clientInfo.clientSecretExpiresAt,\n redirect_uris: [this.config.redirectUri],\n };\n }\n\n return undefined;\n }\n\n /**\n * Saves client information from Dynamic Client Registration\n */\n async saveClientInformation(\n clientInformation: OAuthClientInformationFull\n ): Promise<void> {\n const state = (await this.loadState()) ?? this.createEmptyState();\n state.clientInfo = {\n clientId: clientInformation.client_id,\n clientSecret: clientInformation.client_secret,\n clientIdIssuedAt: clientInformation.client_id_issued_at,\n clientSecretExpiresAt: clientInformation.client_secret_expires_at,\n };\n await this.saveState(state);\n }\n\n /**\n * Loads any existing OAuth tokens for the current session\n */\n async tokens(): Promise<OAuthTokens | undefined> {\n const state = await this.loadState();\n if (state?.tokens) {\n return {\n access_token: state.tokens.accessToken,\n token_type: state.tokens.tokenType,\n refresh_token: state.tokens.refreshToken,\n expires_in: state.tokens.expiresAt\n ? Math.floor((state.tokens.expiresAt - Date.now()) / 1000)\n : undefined,\n };\n }\n return undefined;\n }\n\n /**\n * Stores new OAuth tokens for the current session\n */\n async saveTokens(tokens: OAuthTokens): Promise<void> {\n const state = (await this.loadState()) ?? this.createEmptyState();\n state.tokens = {\n accessToken: tokens.access_token,\n tokenType: tokens.token_type,\n refreshToken: tokens.refresh_token,\n expiresAt: tokens.expires_in\n ? Date.now() + tokens.expires_in * 1000\n : undefined,\n };\n await this.saveState(state);\n }\n\n /**\n * Invoked to redirect the user agent to the given URL\n *\n * In a testing context, this is typically handled by Playwright automation.\n * This implementation throws an error to signal that the caller needs to\n * handle the redirect externally.\n */\n async redirectToAuthorization(authorizationUrl: URL): Promise<void> {\n // In a test context, the authorization flow should be handled externally\n // by Playwright automation (e.g., in globalSetup)\n throw new Error(\n `OAuth authorization required. Redirect to: ${authorizationUrl.toString()}\\n` +\n 'In a testing context, use performOAuthSetup() in your Playwright globalSetup ' +\n 'to complete the OAuth flow before running tests.'\n );\n }\n\n /**\n * Saves a PKCE code verifier for the current session\n */\n async saveCodeVerifier(codeVerifier: string): Promise<void> {\n const state = (await this.loadState()) ?? this.createEmptyState();\n state.codeVerifier = codeVerifier;\n await this.saveState(state);\n }\n\n /**\n * Loads the PKCE code verifier for the current session\n */\n async codeVerifier(): Promise<string> {\n const state = await this.loadState();\n if (!state?.codeVerifier) {\n throw new Error('No code verifier found in auth state');\n }\n return state.codeVerifier;\n }\n\n /**\n * Invalidates the specified credentials\n */\n async invalidateCredentials(\n scope: 'all' | 'client' | 'tokens' | 'verifier'\n ): Promise<void> {\n const state = await this.loadState();\n if (!state) {\n return;\n }\n\n switch (scope) {\n case 'all':\n await this.deleteState();\n break;\n case 'client':\n delete state.clientInfo;\n await this.saveState(state);\n break;\n case 'tokens':\n delete state.tokens;\n await this.saveState(state);\n break;\n case 'verifier':\n delete state.codeVerifier;\n await this.saveState(state);\n break;\n }\n }\n\n // ---- Private helper methods ----\n\n private async loadState(): Promise<StoredOAuthState | null> {\n if (this.cachedState) {\n return this.cachedState;\n }\n\n try {\n const content = await fs.readFile(this.config.storagePath, 'utf-8');\n this.cachedState = JSON.parse(content) as StoredOAuthState;\n return this.cachedState;\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code === 'ENOENT') {\n return null;\n }\n throw error;\n }\n }\n\n private async saveState(state: StoredOAuthState): Promise<void> {\n state.savedAt = Date.now();\n this.cachedState = state;\n\n // Ensure directory exists\n const dir = path.dirname(this.config.storagePath);\n await fs.mkdir(dir, { recursive: true });\n\n await fs.writeFile(\n this.config.storagePath,\n JSON.stringify(state, null, 2),\n 'utf-8'\n );\n }\n\n private async deleteState(): Promise<void> {\n this.cachedState = null;\n try {\n await fs.unlink(this.config.storagePath);\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code !== 'ENOENT') {\n throw error;\n }\n }\n }\n\n private createEmptyState(): StoredOAuthState {\n return {\n savedAt: Date.now(),\n };\n }\n\n private generateRandomString(length: number): string {\n const chars =\n 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';\n let result = '';\n const randomValues = new Uint8Array(length);\n crypto.getRandomValues(randomValues);\n for (let i = 0; i < length; i++) {\n const randomValue = randomValues[i] ?? 0;\n result += chars[randomValue % chars.length];\n }\n return result;\n }\n}\n\n/**\n * Loads OAuth state from a single file (Playwright auth pattern)\n *\n * This function reads from Playwright's single-file auth state format,\n * typically created by `performOAuthSetup` in globalSetup.\n *\n * **Note:** This does NOT work with tokens stored by the CLI (`mcp-server-tester login`).\n * For CLI-stored tokens, use `loadTokens(serverUrl)` instead.\n *\n * @param storagePath - Path to the auth state file (e.g., 'playwright/.auth/oauth-state.json')\n * @returns The stored OAuth state, or null if not found\n *\n * @example\n * ```typescript\n * // Load Playwright auth state\n * const state = await loadOAuthState('playwright/.auth/oauth-state.json');\n * ```\n */\nexport async function loadOAuthState(\n storagePath: string\n): Promise<StoredOAuthState | null> {\n try {\n const content = await fs.readFile(storagePath, 'utf-8');\n return JSON.parse(content) as StoredOAuthState;\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code === 'ENOENT') {\n return null;\n }\n throw error;\n }\n}\n\n/**\n * Saves OAuth state to a single file (Playwright auth pattern)\n *\n * This function writes to Playwright's single-file auth state format.\n * Used by `performOAuthSetup` in globalSetup.\n *\n * **Note:** This does NOT work with the CLI storage format (`mcp-server-tester login`).\n * For programmatic token injection compatible with CLI, use `injectTokens(serverUrl, tokens)`.\n *\n * @param storagePath - Path to the auth state file (e.g., 'playwright/.auth/oauth-state.json')\n * @param state - The OAuth state to save\n *\n * @example\n * ```typescript\n * // Save Playwright auth state\n * await saveOAuthState('playwright/.auth/oauth-state.json', {\n * tokens: { accessToken: '...', tokenType: 'Bearer' },\n * savedAt: Date.now(),\n * });\n * ```\n */\nexport async function saveOAuthState(\n storagePath: string,\n state: StoredOAuthState\n): Promise<void> {\n state.savedAt = Date.now();\n\n // Ensure directory exists\n const dir = path.dirname(storagePath);\n await fs.mkdir(dir, { recursive: true });\n\n await fs.writeFile(storagePath, JSON.stringify(state, null, 2), 'utf-8');\n}\n","/**\n * Playwright fixtures for MCP OAuth authentication\n *\n * Provides worker-scoped OAuth authentication following Playwright's\n * recommended auth state pattern.\n */\n\nimport { test as base } from '@playwright/test';\nimport type { OAuthClientProvider } from '@modelcontextprotocol/sdk/client/auth.js';\nimport type { MCPAuthConfig, MCPOAuthConfig } from '../config/mcpConfig.js';\nimport {\n PlaywrightOAuthClientProvider,\n type PlaywrightOAuthClientProviderConfig,\n} from '../auth/oauthClientProvider.js';\n\n/**\n * Static token auth provider that wraps a pre-acquired token\n *\n * This is a minimal implementation that provides tokens directly\n * without OAuth flow support.\n */\nclass StaticTokenAuthProvider implements OAuthClientProvider {\n private readonly accessToken: string;\n\n constructor(accessToken: string) {\n this.accessToken = accessToken;\n }\n\n get redirectUrl(): string {\n throw new Error('StaticTokenAuthProvider does not support OAuth redirects');\n }\n\n get clientMetadata() {\n return {\n redirect_uris: [],\n token_endpoint_auth_method: 'none' as const,\n grant_types: [],\n response_types: [],\n client_name: '@gleanwork/mcp-server-tester',\n };\n }\n\n async clientInformation() {\n return undefined;\n }\n\n async tokens() {\n return {\n access_token: this.accessToken,\n token_type: 'Bearer',\n };\n }\n\n async saveTokens(): Promise<void> {\n // Static tokens don't need to be saved\n }\n\n async redirectToAuthorization(): Promise<void> {\n throw new Error('StaticTokenAuthProvider does not support OAuth redirects');\n }\n\n async saveCodeVerifier(): Promise<void> {\n throw new Error('StaticTokenAuthProvider does not support PKCE');\n }\n\n async codeVerifier(): Promise<string> {\n throw new Error('StaticTokenAuthProvider does not support PKCE');\n }\n}\n\n/**\n * Test-scoped auth fixtures interface\n */\nexport interface MCPAuthFixtures {\n /**\n * OAuth client provider for MCP authentication\n */\n mcpAuthProvider: OAuthClientProvider | undefined;\n}\n\n/**\n * Extended Playwright test with MCP auth fixtures\n *\n * Use this when you need OAuth authentication for MCP server testing.\n *\n * @example\n * ```typescript\n * // test.ts\n * import { test } from '@gleanwork/mcp-server-tester/fixtures/mcpAuth';\n *\n * test('authenticated MCP call', async ({ mcpAuthProvider }) => {\n * // mcpAuthProvider can be passed to createMCPClientForConfig\n * });\n * ```\n */\nexport const test = base.extend<MCPAuthFixtures>({\n /**\n * Create auth provider based on environment configuration\n */\n // eslint-disable-next-line no-empty-pattern\n mcpAuthProvider: async ({}, use) => {\n const authConfig = getAuthConfigFromEnv();\n\n if (!authConfig) {\n await use(undefined);\n return;\n }\n\n // Static token mode\n if (authConfig.accessToken) {\n const provider = new StaticTokenAuthProvider(authConfig.accessToken);\n await use(provider);\n return;\n }\n\n // OAuth mode\n if (authConfig.oauth) {\n const provider = createOAuthProvider(authConfig.oauth);\n await use(provider);\n return;\n }\n\n await use(undefined);\n },\n});\n\n/**\n * Creates an OAuth provider from configuration\n */\nfunction createOAuthProvider(\n oauthConfig: MCPOAuthConfig\n): PlaywrightOAuthClientProvider {\n if (!oauthConfig.authStatePath) {\n throw new Error(\n 'OAuth configuration requires authStatePath. ' +\n 'Use performOAuthSetup() in globalSetup to create auth state first.'\n );\n }\n\n const providerConfig: PlaywrightOAuthClientProviderConfig = {\n storagePath: oauthConfig.authStatePath,\n redirectUri:\n oauthConfig.redirectUri ?? 'http://localhost:3000/oauth/callback',\n clientId: oauthConfig.clientId,\n clientSecret: oauthConfig.clientSecret,\n };\n\n return new PlaywrightOAuthClientProvider(providerConfig);\n}\n\n/**\n * Gets auth config from environment variables\n *\n * This is a fallback for fixtures that can't access testInfo.project directly.\n */\nfunction getAuthConfigFromEnv(): MCPAuthConfig | undefined {\n // Check for static token\n const accessToken = process.env.MCP_ACCESS_TOKEN;\n if (accessToken) {\n return { accessToken };\n }\n\n // Check for OAuth config\n const oauthServerUrl = process.env.MCP_OAUTH_SERVER_URL;\n const authStatePath = process.env.MCP_AUTH_STATE_PATH;\n\n if (oauthServerUrl || authStatePath) {\n return {\n oauth: {\n serverUrl: oauthServerUrl ?? '',\n authStatePath: authStatePath,\n clientId: process.env.MCP_OAUTH_CLIENT_ID,\n clientSecret: process.env.MCP_OAUTH_CLIENT_SECRET,\n scopes: process.env.MCP_OAUTH_SCOPES?.split(','),\n resource: process.env.MCP_OAUTH_RESOURCE,\n },\n };\n }\n\n return undefined;\n}\n\n/**\n * Re-export expect for convenience\n */\nexport { expect } from '@playwright/test';\n"]}
|
|
1
|
+
{"version":3,"sources":["../../src/auth/oauthClientProvider.ts","../../src/fixtures/mcpAuth.ts"],"names":["base"],"mappings":"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","file":"mcpAuth.js","sourcesContent":["/**\n * OAuth client provider implementation for MCP SDK\n *\n * Implements the MCP SDK's OAuthClientProvider interface using file-based storage\n * for integration with Playwright's auth state pattern.\n */\n\nimport * as fs from 'node:fs/promises';\nimport * as path from 'node:path';\nimport type { OAuthClientProvider } from '@modelcontextprotocol/sdk/client/auth.js';\nimport type {\n OAuthClientMetadata,\n OAuthClientInformationFull,\n OAuthTokens,\n} from '@modelcontextprotocol/sdk/shared/auth.js';\nimport type { StoredOAuthState } from './types.js';\n\n/**\n * Configuration for the Playwright OAuth client provider\n */\nexport interface PlaywrightOAuthClientProviderConfig {\n /**\n * Path to the auth state file (e.g., playwright/.auth/oauth-state.json)\n */\n storagePath: string;\n\n /**\n * OAuth redirect URI for callback\n */\n redirectUri: string;\n\n /**\n * Client metadata for DCR or display\n */\n clientMetadata?: Partial<OAuthClientMetadata>;\n\n /**\n * Pre-registered client ID (if not using DCR)\n */\n clientId?: string;\n\n /**\n * Pre-registered client secret (if not using DCR)\n */\n clientSecret?: string;\n}\n\n/**\n * OAuth client provider that implements the MCP SDK's OAuthClientProvider interface\n *\n * Uses file-based storage for integration with Playwright's auth state pattern.\n * Auth state is persisted to disk so it can be reused across test runs.\n *\n * @example\n * ```typescript\n * const provider = new PlaywrightOAuthClientProvider({\n * storagePath: 'playwright/.auth/oauth-state.json',\n * redirectUri: 'http://localhost:3000/callback',\n * });\n *\n * const transport = new StreamableHTTPClientTransport(serverUrl, {\n * authProvider: provider,\n * });\n * ```\n */\nexport class PlaywrightOAuthClientProvider implements OAuthClientProvider {\n private readonly config: PlaywrightOAuthClientProviderConfig;\n private cachedState: StoredOAuthState | null = null;\n private stateParam: string | null = null;\n\n constructor(config: PlaywrightOAuthClientProviderConfig) {\n this.config = config;\n }\n\n /**\n * The URL to redirect the user agent to after authorization\n */\n get redirectUrl(): string {\n return this.config.redirectUri;\n }\n\n /**\n * Metadata about this OAuth client\n */\n get clientMetadata(): OAuthClientMetadata {\n return {\n redirect_uris: [this.config.redirectUri],\n token_endpoint_auth_method: this.config.clientSecret\n ? 'client_secret_basic'\n : 'none',\n grant_types: ['authorization_code', 'refresh_token'],\n response_types: ['code'],\n client_name: '@gleanwork/mcp-server-tester',\n ...this.config.clientMetadata,\n };\n }\n\n /**\n * Returns an OAuth2 state parameter\n */\n state(): string {\n if (!this.stateParam) {\n this.stateParam = this.generateRandomString(32);\n }\n return this.stateParam;\n }\n\n /**\n * Loads information about this OAuth client\n */\n async clientInformation(): Promise<OAuthClientInformationFull | undefined> {\n // If we have a pre-registered client, return it\n if (this.config.clientId) {\n return {\n client_id: this.config.clientId,\n client_secret: this.config.clientSecret,\n redirect_uris: [this.config.redirectUri],\n };\n }\n\n // Otherwise, try to load from storage (DCR result)\n const state = await this.loadState();\n if (state?.clientInfo) {\n return {\n client_id: state.clientInfo.clientId,\n client_secret: state.clientInfo.clientSecret,\n client_id_issued_at: state.clientInfo.clientIdIssuedAt,\n client_secret_expires_at: state.clientInfo.clientSecretExpiresAt,\n redirect_uris: [this.config.redirectUri],\n };\n }\n\n return undefined;\n }\n\n /**\n * Saves client information from Dynamic Client Registration\n */\n async saveClientInformation(\n clientInformation: OAuthClientInformationFull\n ): Promise<void> {\n const state = (await this.loadState()) ?? this.createEmptyState();\n state.clientInfo = {\n clientId: clientInformation.client_id,\n clientSecret: clientInformation.client_secret,\n clientIdIssuedAt: clientInformation.client_id_issued_at,\n clientSecretExpiresAt: clientInformation.client_secret_expires_at,\n };\n await this.saveState(state);\n }\n\n /**\n * Loads any existing OAuth tokens for the current session\n */\n async tokens(): Promise<OAuthTokens | undefined> {\n const state = await this.loadState();\n if (state?.tokens) {\n return {\n access_token: state.tokens.accessToken,\n token_type: state.tokens.tokenType,\n refresh_token: state.tokens.refreshToken,\n expires_in: state.tokens.expiresAt\n ? Math.floor((state.tokens.expiresAt - Date.now()) / 1000)\n : undefined,\n };\n }\n return undefined;\n }\n\n /**\n * Stores new OAuth tokens for the current session\n *\n * The code verifier is cleared after a successful token exchange — it is\n * single-use per PKCE spec and must not persist beyond the exchange.\n */\n async saveTokens(tokens: OAuthTokens): Promise<void> {\n const state = (await this.loadState()) ?? this.createEmptyState();\n state.tokens = {\n accessToken: tokens.access_token,\n tokenType: tokens.token_type,\n refreshToken: tokens.refresh_token,\n expiresAt: tokens.expires_in\n ? Date.now() + tokens.expires_in * 1000\n : undefined,\n };\n // Clear codeVerifier after successful token exchange — it's single-use per PKCE spec\n delete state.codeVerifier;\n await this.saveState(state);\n }\n\n /**\n * Invoked to redirect the user agent to the given URL\n *\n * In a testing context, this is typically handled by Playwright automation.\n * This implementation throws an error to signal that the caller needs to\n * handle the redirect externally.\n */\n async redirectToAuthorization(authorizationUrl: URL): Promise<void> {\n // In a test context, the authorization flow should be handled externally\n // by Playwright automation (e.g., in globalSetup)\n throw new Error(\n `OAuth authorization required. Redirect to: ${authorizationUrl.toString()}\\n` +\n 'In a testing context, use performOAuthSetup() in your Playwright globalSetup ' +\n 'to complete the OAuth flow before running tests.'\n );\n }\n\n /**\n * Saves a PKCE code verifier for the current session\n */\n async saveCodeVerifier(codeVerifier: string): Promise<void> {\n const state = (await this.loadState()) ?? this.createEmptyState();\n state.codeVerifier = codeVerifier;\n await this.saveState(state);\n }\n\n /**\n * Loads the PKCE code verifier for the current session\n */\n async codeVerifier(): Promise<string> {\n const state = await this.loadState();\n if (!state?.codeVerifier) {\n throw new Error('No code verifier found in auth state');\n }\n return state.codeVerifier;\n }\n\n /**\n * Invalidates the specified credentials\n */\n async invalidateCredentials(\n scope: 'all' | 'client' | 'tokens' | 'verifier'\n ): Promise<void> {\n const state = await this.loadState();\n if (!state) {\n return;\n }\n\n switch (scope) {\n case 'all':\n await this.deleteState();\n break;\n case 'client':\n delete state.clientInfo;\n await this.saveState(state);\n break;\n case 'tokens':\n delete state.tokens;\n await this.saveState(state);\n break;\n case 'verifier':\n delete state.codeVerifier;\n await this.saveState(state);\n break;\n }\n }\n\n // ---- Private helper methods ----\n\n private async loadState(): Promise<StoredOAuthState | null> {\n if (this.cachedState) {\n return this.cachedState;\n }\n\n try {\n const content = await fs.readFile(this.config.storagePath, 'utf-8');\n this.cachedState = JSON.parse(content) as StoredOAuthState;\n return this.cachedState;\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code === 'ENOENT') {\n return null;\n }\n throw error;\n }\n }\n\n private async saveState(state: StoredOAuthState): Promise<void> {\n state.savedAt = Date.now();\n this.cachedState = state;\n\n // Ensure directory exists\n const dir = path.dirname(this.config.storagePath);\n await fs.mkdir(dir, { recursive: true, mode: 0o700 });\n\n await fs.writeFile(\n this.config.storagePath,\n JSON.stringify(state, null, 2),\n { encoding: 'utf-8', mode: 0o600 }\n );\n }\n\n private async deleteState(): Promise<void> {\n this.cachedState = null;\n try {\n await fs.unlink(this.config.storagePath);\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code !== 'ENOENT') {\n throw error;\n }\n }\n }\n\n private createEmptyState(): StoredOAuthState {\n return {\n savedAt: Date.now(),\n };\n }\n\n private generateRandomString(length: number): string {\n const chars =\n 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';\n let result = '';\n const randomValues = new Uint8Array(length);\n crypto.getRandomValues(randomValues);\n for (let i = 0; i < length; i++) {\n const randomValue = randomValues[i] ?? 0;\n result += chars[randomValue % chars.length];\n }\n return result;\n }\n}\n\n/**\n * Loads OAuth state from a single file (Playwright auth pattern)\n *\n * This function reads from Playwright's single-file auth state format,\n * typically created by `performOAuthSetup` in globalSetup.\n *\n * **Note:** This does NOT work with tokens stored by the CLI (`mcp-server-tester login`).\n * For CLI-stored tokens, use `loadTokens(serverUrl)` instead.\n *\n * @param storagePath - Path to the auth state file (e.g., 'playwright/.auth/oauth-state.json')\n * @returns The stored OAuth state, or null if not found\n *\n * @example\n * ```typescript\n * // Load Playwright auth state\n * const state = await loadOAuthState('playwright/.auth/oauth-state.json');\n * ```\n */\nexport async function loadOAuthState(\n storagePath: string\n): Promise<StoredOAuthState | null> {\n try {\n const content = await fs.readFile(storagePath, 'utf-8');\n return JSON.parse(content) as StoredOAuthState;\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code === 'ENOENT') {\n return null;\n }\n throw error;\n }\n}\n\n/**\n * Saves OAuth state to a single file (Playwright auth pattern)\n *\n * This function writes to Playwright's single-file auth state format.\n * Used by `performOAuthSetup` in globalSetup.\n *\n * **Note:** This does NOT work with the CLI storage format (`mcp-server-tester login`).\n * For programmatic token injection compatible with CLI, use `injectTokens(serverUrl, tokens)`.\n *\n * @param storagePath - Path to the auth state file (e.g., 'playwright/.auth/oauth-state.json')\n * @param state - The OAuth state to save\n *\n * @example\n * ```typescript\n * // Save Playwright auth state\n * await saveOAuthState('playwright/.auth/oauth-state.json', {\n * tokens: { accessToken: '...', tokenType: 'Bearer' },\n * savedAt: Date.now(),\n * });\n * ```\n */\nexport async function saveOAuthState(\n storagePath: string,\n state: StoredOAuthState\n): Promise<void> {\n state.savedAt = Date.now();\n\n // Ensure directory exists\n const dir = path.dirname(storagePath);\n await fs.mkdir(dir, { recursive: true, mode: 0o700 });\n\n await fs.writeFile(storagePath, JSON.stringify(state, null, 2), {\n encoding: 'utf-8',\n mode: 0o600,\n });\n}\n","/**\n * Playwright fixtures for MCP OAuth authentication\n *\n * Provides worker-scoped OAuth authentication following Playwright's\n * recommended auth state pattern.\n */\n\nimport { test as base } from '@playwright/test';\nimport type { OAuthClientProvider } from '@modelcontextprotocol/sdk/client/auth.js';\nimport type { MCPAuthConfig, MCPOAuthConfig } from '../config/mcpConfig.js';\nimport {\n PlaywrightOAuthClientProvider,\n type PlaywrightOAuthClientProviderConfig,\n} from '../auth/oauthClientProvider.js';\n\n/**\n * Static token auth provider that wraps a pre-acquired token\n *\n * This is a minimal implementation that provides tokens directly\n * without OAuth flow support.\n */\nclass StaticTokenAuthProvider implements OAuthClientProvider {\n private readonly accessToken: string;\n\n constructor(accessToken: string) {\n this.accessToken = accessToken;\n }\n\n get redirectUrl(): string {\n throw new Error('StaticTokenAuthProvider does not support OAuth redirects');\n }\n\n get clientMetadata() {\n return {\n redirect_uris: [],\n token_endpoint_auth_method: 'none' as const,\n grant_types: [],\n response_types: [],\n client_name: '@gleanwork/mcp-server-tester',\n };\n }\n\n async clientInformation() {\n return undefined;\n }\n\n async tokens() {\n return {\n access_token: this.accessToken,\n token_type: 'Bearer',\n };\n }\n\n async saveTokens(): Promise<void> {\n // Static tokens don't need to be saved\n }\n\n async redirectToAuthorization(): Promise<void> {\n throw new Error('StaticTokenAuthProvider does not support OAuth redirects');\n }\n\n async saveCodeVerifier(): Promise<void> {\n throw new Error('StaticTokenAuthProvider does not support PKCE');\n }\n\n async codeVerifier(): Promise<string> {\n throw new Error('StaticTokenAuthProvider does not support PKCE');\n }\n}\n\n/**\n * Test-scoped auth fixtures interface\n */\nexport interface MCPAuthFixtures {\n /**\n * OAuth client provider for MCP authentication\n */\n mcpAuthProvider: OAuthClientProvider | undefined;\n}\n\n/**\n * Extended Playwright test with MCP auth fixtures\n *\n * Use this when you need OAuth authentication for MCP server testing.\n *\n * @example\n * ```typescript\n * // test.ts\n * import { test } from '@gleanwork/mcp-server-tester/fixtures/mcpAuth';\n *\n * test('authenticated MCP call', async ({ mcpAuthProvider }) => {\n * // mcpAuthProvider can be passed to createMCPClientForConfig\n * });\n * ```\n */\nexport const test = base.extend<MCPAuthFixtures>({\n /**\n * Create auth provider based on environment configuration\n */\n // eslint-disable-next-line no-empty-pattern\n mcpAuthProvider: async ({}, use) => {\n const authConfig = getAuthConfigFromEnv();\n\n if (!authConfig) {\n await use(undefined);\n return;\n }\n\n // Static token mode\n if (authConfig.accessToken) {\n const provider = new StaticTokenAuthProvider(authConfig.accessToken);\n await use(provider);\n return;\n }\n\n // OAuth mode\n if (authConfig.oauth) {\n const provider = createOAuthProvider(authConfig.oauth);\n await use(provider);\n return;\n }\n\n await use(undefined);\n },\n});\n\n/**\n * Creates an OAuth provider from configuration\n */\nfunction createOAuthProvider(\n oauthConfig: MCPOAuthConfig\n): PlaywrightOAuthClientProvider {\n if (!oauthConfig.authStatePath) {\n throw new Error(\n 'OAuth configuration requires authStatePath. ' +\n 'Use performOAuthSetup() in globalSetup to create auth state first.'\n );\n }\n\n const providerConfig: PlaywrightOAuthClientProviderConfig = {\n storagePath: oauthConfig.authStatePath,\n redirectUri:\n oauthConfig.redirectUri ?? 'http://localhost:3000/oauth/callback',\n clientId: oauthConfig.clientId,\n clientSecret: oauthConfig.clientSecret,\n };\n\n return new PlaywrightOAuthClientProvider(providerConfig);\n}\n\n/**\n * Gets auth config from environment variables\n *\n * This is a fallback for fixtures that can't access testInfo.project directly.\n */\nfunction getAuthConfigFromEnv(): MCPAuthConfig | undefined {\n // Check for static token\n const accessToken = process.env.MCP_ACCESS_TOKEN;\n if (accessToken) {\n return { accessToken };\n }\n\n // Check for OAuth config\n const oauthServerUrl = process.env.MCP_OAUTH_SERVER_URL;\n const authStatePath = process.env.MCP_AUTH_STATE_PATH;\n\n if (oauthServerUrl || authStatePath) {\n return {\n oauth: {\n serverUrl: oauthServerUrl ?? '',\n authStatePath: authStatePath,\n clientId: process.env.MCP_OAUTH_CLIENT_ID,\n clientSecret: process.env.MCP_OAUTH_CLIENT_SECRET,\n scopes: process.env.MCP_OAUTH_SCOPES?.split(','),\n resource: process.env.MCP_OAUTH_RESOURCE,\n },\n };\n }\n\n return undefined;\n}\n\n/**\n * Re-export expect for convenience\n */\nexport { expect } from '@playwright/test';\n"]}
|