@mcp-z/oauth-google 1.0.1 → 1.0.2

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.
@@ -1 +1 @@
1
- {"version":3,"sources":["/Users/kevin/Dev/Projects/mcp-z/oauth-google/src/providers/loopback-oauth.ts"],"sourcesContent":["/**\n * Loopback OAuth Implementation (RFC 8252)\n *\n * Implements OAuth 2.0 Authorization Code Flow with PKCE using loopback interface redirection.\n * Uses ephemeral local server with OS-assigned port (RFC 8252 Section 8.3).\n */\n\nimport { addAccount, generatePKCE, getActiveAccount, getErrorTemplate, getSuccessTemplate, getToken, type OAuth2TokenStorageProvider, setAccountInfo, setActiveAccount, setToken } from '@mcp-z/oauth';\nimport { randomUUID } from 'crypto';\nimport { OAuth2Client } from 'google-auth-library';\nimport * as http from 'http';\nimport open from 'open';\nimport { type AuthContext, AuthRequiredError, type CachedToken, type EnrichedExtra, type LoopbackOAuthConfig } from '../types.ts';\n\ninterface TokenResponse {\n access_token: string;\n refresh_token?: string;\n expires_in?: number;\n scope?: string;\n token_type?: string;\n}\n\n/**\n * Loopback OAuth Client (RFC 8252 Section 7.3)\n *\n * Implements OAuth 2.0 Authorization Code Flow with PKCE for native applications\n * using loopback interface redirection. Manages ephemeral OAuth flows and token persistence\n * with Keyv for key-based token storage using compound keys.\n *\n * Token key format: {accountId}:{service}:token (e.g., \"user@example.com:gmail:token\")\n */\nexport class LoopbackOAuthProvider implements OAuth2TokenStorageProvider {\n private config: LoopbackOAuthConfig;\n\n constructor(config: LoopbackOAuthConfig) {\n this.config = config;\n }\n\n /**\n * Get access token from Keyv using compound key\n *\n * @param accountId - Account identifier (email address). Required for loopback OAuth.\n * @returns Access token for API requests\n */\n async getAccessToken(accountId?: string): Promise<string> {\n const { logger, service, tokenStore } = this.config;\n\n // Use active account if no accountId specified\n const effectiveAccountId = accountId ?? (await getActiveAccount(tokenStore, { service }));\n\n // If we have an accountId, try to use existing token\n if (effectiveAccountId) {\n logger.debug('Getting access token', { service, accountId: effectiveAccountId });\n\n // Check Keyv for token using new key format\n const storedToken = await getToken<CachedToken>(tokenStore, { accountId: effectiveAccountId, service });\n\n if (storedToken && this.isTokenValid(storedToken)) {\n logger.debug('Using stored access token', { accountId: effectiveAccountId });\n return storedToken.accessToken;\n }\n\n // If stored token expired but has refresh token, try refresh\n if (storedToken?.refreshToken) {\n try {\n logger.info('Refreshing expired access token', { accountId: effectiveAccountId });\n const refreshedToken = await this.refreshAccessToken(storedToken.refreshToken);\n await setToken(tokenStore, { accountId: effectiveAccountId, service }, refreshedToken);\n return refreshedToken.accessToken;\n } catch (error) {\n logger.info('Token refresh failed, starting new OAuth flow', {\n accountId: effectiveAccountId,\n error: error instanceof Error ? error.message : String(error),\n });\n // Fall through to new OAuth flow\n }\n }\n }\n\n // No valid token or no account - need OAuth authentication\n const { clientId, scope, redirectUri } = this.config;\n\n if (redirectUri) {\n // Persistent callback mode (cloud deployment with configured redirect_uri)\n const { verifier: codeVerifier, challenge: codeChallenge } = generatePKCE();\n const stateId = randomUUID();\n\n // Store PKCE verifier for callback (5 minute TTL)\n await tokenStore.set(`${service}:pending:${stateId}`, { codeVerifier, createdAt: Date.now() }, 5 * 60 * 1000);\n\n // Build auth URL with configured redirect_uri\n const authUrl = new URL('https://accounts.google.com/o/oauth2/v2/auth');\n authUrl.searchParams.set('client_id', clientId);\n authUrl.searchParams.set('redirect_uri', redirectUri);\n authUrl.searchParams.set('response_type', 'code');\n authUrl.searchParams.set('scope', scope);\n authUrl.searchParams.set('access_type', 'offline');\n authUrl.searchParams.set('code_challenge', codeChallenge);\n authUrl.searchParams.set('code_challenge_method', 'S256');\n authUrl.searchParams.set('state', stateId);\n authUrl.searchParams.set('prompt', 'consent');\n\n logger.info('OAuth required - persistent callback mode', { service, redirectUri });\n throw new AuthRequiredError({\n kind: 'auth_url',\n provider: service,\n url: authUrl.toString(),\n });\n }\n // Ephemeral callback mode (local development)\n logger.info('Starting ephemeral OAuth flow', { service, headless: this.config.headless });\n const { token, email } = await this.performEphemeralOAuthFlow();\n\n await setToken(tokenStore, { accountId: email, service }, token);\n await addAccount(tokenStore, { service, accountId: email });\n await setActiveAccount(tokenStore, { service, accountId: email });\n await setAccountInfo(tokenStore, { service, accountId: email }, { email, addedAt: new Date().toISOString() });\n\n logger.info('OAuth flow completed', { service, accountId: email });\n return token.accessToken;\n }\n\n /**\n * Convert to googleapis-compatible OAuth2Client\n *\n * @param accountId - Account identifier for multi-account support (e.g., 'user@example.com')\n * @returns OAuth2Client configured for the specified account\n */\n toAuth(accountId?: string): OAuth2Client {\n const { clientId, clientSecret } = this.config;\n const client = new OAuth2Client({\n clientId,\n ...(clientSecret && { clientSecret }),\n });\n\n // @ts-expect-error - Override protected method to inject fresh token\n client.getRequestMetadataAsync = async (_url?: string) => {\n // Get token from FileAuthAdapter (not from client to avoid recursion)\n const token = await this.getAccessToken(accountId);\n\n // Update client credentials for googleapis compatibility\n client.credentials = {\n access_token: token,\n token_type: 'Bearer',\n };\n\n // Return headers as Map (required by authclient.js addUserProjectAndAuthHeaders)\n const headers = new Map<string, string>();\n headers.set('authorization', `Bearer ${token}`);\n return { headers };\n };\n\n return client;\n }\n\n /**\n * Get user email from Google's userinfo endpoint (pure query)\n * Used to query email for existing authenticated account\n *\n * @param accountId - Account identifier to get email for\n * @returns User's email address\n */\n async getUserEmail(accountId?: string): Promise<string> {\n // Get token for existing account\n const token = await this.getAccessToken(accountId);\n\n // Fetch email from Google userinfo\n const response = await fetch('https://www.googleapis.com/oauth2/v2/userinfo', {\n headers: {\n Authorization: `Bearer ${token}`,\n },\n });\n\n if (!response.ok) {\n throw new Error(`Failed to get user info: ${response.status} ${await response.text()}`);\n }\n\n const userInfo = (await response.json()) as { email: string };\n return userInfo.email;\n }\n\n private isTokenValid(token: CachedToken): boolean {\n if (!token.expiresAt) return true; // No expiry = assume valid\n return Date.now() < token.expiresAt - 60000; // 1 minute buffer\n }\n\n /**\n * Fetch user email from Google OAuth2 userinfo endpoint\n * Called during OAuth flow to get email for accountId\n *\n * @param accessToken - Fresh access token from OAuth exchange\n * @returns User's email address\n */\n private async fetchUserEmailFromToken(accessToken: string): Promise<string> {\n const { logger } = this.config;\n\n const response = await fetch('https://www.googleapis.com/oauth2/v2/userinfo', {\n headers: {\n Authorization: `Bearer ${accessToken}`,\n },\n });\n\n if (!response.ok) {\n const errorText = await response.text();\n throw new Error(`Failed to fetch user email: HTTP ${response.status} - ${errorText}`);\n }\n\n const userInfo = (await response.json()) as { email: string };\n const email = userInfo.email;\n\n logger.debug('Fetched user email from Google userinfo API', { email });\n return email;\n }\n\n private async performEphemeralOAuthFlow(): Promise<{ token: CachedToken; email: string }> {\n const { clientId, scope, headless, logger, redirectUri: configRedirectUri } = this.config;\n\n // Server listen configuration (where ephemeral server binds)\n let listenHost = 'localhost'; // Default: localhost for ephemeral loopback\n let listenPort = 0; // Default: OS-assigned ephemeral port\n\n // Redirect URI configuration (what goes in auth URL and token exchange)\n let callbackPath = '/callback'; // Default callback path\n let useConfiguredUri = false;\n\n if (configRedirectUri) {\n try {\n const parsed = new URL(configRedirectUri);\n const isLoopback = parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1';\n\n if (isLoopback) {\n // Local development: Listen on specific loopback address/port\n listenHost = parsed.hostname;\n listenPort = parsed.port ? Number.parseInt(parsed.port, 10) : 0;\n } else {\n // Cloud deployment: Listen on 0.0.0.0 with PORT from environment\n // The redirectUri is the PUBLIC URL (e.g., https://example.com/oauth/callback)\n // The server listens on 0.0.0.0:PORT and the load balancer routes to it\n listenHost = '0.0.0.0';\n const envPort = process.env.PORT ? Number.parseInt(process.env.PORT, 10) : undefined;\n listenPort = envPort && Number.isFinite(envPort) ? envPort : 8080;\n }\n\n // Extract callback path from URL\n if (parsed.pathname && parsed.pathname !== '/') {\n callbackPath = parsed.pathname;\n }\n\n useConfiguredUri = true;\n\n logger.debug('Using configured redirect URI', {\n listenHost,\n listenPort,\n callbackPath,\n redirectUri: configRedirectUri,\n isLoopback,\n });\n } catch (error) {\n logger.warn('Failed to parse redirectUri, using ephemeral defaults', {\n redirectUri: configRedirectUri,\n error: error instanceof Error ? error.message : String(error),\n });\n // Continue with defaults (localhost, port 0, http, /callback)\n }\n }\n\n return new Promise((resolve, reject) => {\n // Generate PKCE challenge\n const { verifier: codeVerifier, challenge: codeChallenge } = generatePKCE();\n\n let server: http.Server | null = null;\n let serverPort: number;\n let finalRedirectUri: string; // Will be set in server.listen callback\n\n // Create ephemeral server with OS-assigned port (RFC 8252)\n server = http.createServer(async (req, res) => {\n if (!req.url) {\n res.writeHead(400, { 'Content-Type': 'text/html' });\n res.end(getErrorTemplate('Invalid request'));\n server?.close();\n reject(new Error('Invalid request: missing URL'));\n return;\n }\n const url = new URL(req.url, `http://127.0.0.1:${serverPort}`);\n\n if (url.pathname === callbackPath) {\n const code = url.searchParams.get('code');\n const error = url.searchParams.get('error');\n\n if (error) {\n res.writeHead(400, { 'Content-Type': 'text/html' });\n res.end(getErrorTemplate(error));\n server?.close();\n reject(new Error(`OAuth error: ${error}`));\n return;\n }\n\n if (!code) {\n res.writeHead(400, { 'Content-Type': 'text/html' });\n res.end(getErrorTemplate('No authorization code received'));\n server?.close();\n reject(new Error('No authorization code received'));\n return;\n }\n\n try {\n // Exchange code for token (must use same redirect_uri as in authorization request)\n const tokenResponse = await this.exchangeCodeForToken(code, codeVerifier, finalRedirectUri);\n\n // Build cached token\n const cachedToken: CachedToken = {\n accessToken: tokenResponse.access_token,\n ...(tokenResponse.refresh_token !== undefined && { refreshToken: tokenResponse.refresh_token }),\n ...(tokenResponse.expires_in !== undefined && { expiresAt: Date.now() + tokenResponse.expires_in * 1000 }),\n ...(tokenResponse.scope !== undefined && { scope: tokenResponse.scope }),\n };\n\n // Fetch user email immediately using the new access token\n const email = await this.fetchUserEmailFromToken(tokenResponse.access_token);\n\n res.writeHead(200, { 'Content-Type': 'text/html' });\n res.end(getSuccessTemplate());\n server?.close();\n resolve({ token: cachedToken, email });\n } catch (exchangeError) {\n logger.error('Token exchange failed', { error: exchangeError instanceof Error ? exchangeError.message : String(exchangeError) });\n res.writeHead(500, { 'Content-Type': 'text/html' });\n res.end(getErrorTemplate('Token exchange failed'));\n server?.close();\n reject(exchangeError);\n }\n } else {\n res.writeHead(404, { 'Content-Type': 'text/plain' });\n res.end('Not Found');\n }\n });\n\n // Listen on configured host/port\n server.listen(listenPort, listenHost, () => {\n const address = server?.address();\n if (!address || typeof address === 'string') {\n server?.close();\n reject(new Error('Failed to start ephemeral server'));\n return;\n }\n\n serverPort = address.port;\n\n // Construct final redirect URI\n if (useConfiguredUri && configRedirectUri) {\n // Use configured redirect URI as-is (public URL for cloud, or specific local URL)\n finalRedirectUri = configRedirectUri;\n } else {\n // Construct ephemeral redirect URI with actual server port (default local behavior)\n finalRedirectUri = `http://localhost:${serverPort}${callbackPath}`;\n }\n\n // Build auth URL\n const authUrl = new URL('https://accounts.google.com/o/oauth2/v2/auth');\n authUrl.searchParams.set('client_id', clientId);\n authUrl.searchParams.set('redirect_uri', finalRedirectUri);\n authUrl.searchParams.set('response_type', 'code');\n authUrl.searchParams.set('scope', scope);\n authUrl.searchParams.set('access_type', 'offline');\n authUrl.searchParams.set('prompt', 'consent');\n authUrl.searchParams.set('code_challenge', codeChallenge);\n authUrl.searchParams.set('code_challenge_method', 'S256');\n\n logger.info('Ephemeral OAuth server started', { port: serverPort, headless });\n\n if (headless) {\n // Headless mode: Print auth URL to stderr (stdout is MCP protocol)\n console.error('\\nšŸ” OAuth Authorization Required');\n console.error('šŸ“‹ Please visit this URL in your browser:\\n');\n console.error(` ${authUrl.toString()}\\n`);\n console.error('ā³ Waiting for authorization...\\n');\n } else {\n // Interactive mode: Open browser automatically\n logger.info('Opening browser for OAuth authorization');\n open(authUrl.toString()).catch((error: Error) => {\n logger.info('Failed to open browser automatically', { error: error.message });\n console.error('\\nšŸ” OAuth Authorization Required');\n console.error(` ${authUrl.toString()}\\n`);\n });\n }\n });\n\n // Timeout after 5 minutes\n setTimeout(\n () => {\n if (server) {\n server.close();\n reject(new Error('OAuth flow timed out after 5 minutes'));\n }\n },\n 5 * 60 * 1000\n );\n });\n }\n\n private async exchangeCodeForToken(code: string, codeVerifier: string, redirectUri: string): Promise<TokenResponse> {\n const { clientId, clientSecret } = this.config;\n\n const tokenUrl = 'https://oauth2.googleapis.com/token';\n const params: Record<string, string> = {\n code,\n client_id: clientId,\n redirect_uri: redirectUri,\n grant_type: 'authorization_code',\n code_verifier: codeVerifier,\n };\n if (clientSecret) {\n params.client_secret = clientSecret;\n }\n const body = new URLSearchParams(params);\n\n const response = await fetch(tokenUrl, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/x-www-form-urlencoded',\n },\n body: body.toString(),\n });\n\n if (!response.ok) {\n const errorText = await response.text();\n throw new Error(`Token exchange failed: ${response.status} ${errorText}`);\n }\n\n return (await response.json()) as TokenResponse;\n }\n\n private async refreshAccessToken(refreshToken: string): Promise<CachedToken> {\n const { clientId, clientSecret } = this.config;\n\n const tokenUrl = 'https://oauth2.googleapis.com/token';\n const params: Record<string, string> = {\n refresh_token: refreshToken,\n client_id: clientId,\n grant_type: 'refresh_token',\n };\n if (clientSecret) {\n params.client_secret = clientSecret;\n }\n const body = new URLSearchParams(params);\n\n const response = await fetch(tokenUrl, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/x-www-form-urlencoded',\n },\n body: body.toString(),\n });\n\n if (!response.ok) {\n const errorText = await response.text();\n throw new Error(`Token refresh failed: ${response.status} ${errorText}`);\n }\n\n const tokenResponse = (await response.json()) as TokenResponse;\n\n return {\n accessToken: tokenResponse.access_token,\n refreshToken: refreshToken, // Keep original refresh token\n ...(tokenResponse.expires_in !== undefined && { expiresAt: Date.now() + tokenResponse.expires_in * 1000 }),\n ...(tokenResponse.scope !== undefined && { scope: tokenResponse.scope }),\n };\n }\n\n /**\n * Handle OAuth callback from persistent endpoint.\n * Used by HTTP servers with configured redirectUri.\n *\n * @param params - OAuth callback parameters\n * @returns Email and cached token\n */\n async handleOAuthCallback(params: { code: string; state?: string }): Promise<{ email: string; token: CachedToken }> {\n const { code, state } = params;\n const { logger, service, tokenStore, clientId, clientSecret, redirectUri } = this.config;\n\n if (!state) {\n throw new Error('Missing state parameter in OAuth callback');\n }\n\n if (!redirectUri) {\n throw new Error('handleOAuthCallback requires configured redirectUri');\n }\n\n // Load pending auth (includes PKCE verifier)\n const pendingKey = `${service}:pending:${state}`;\n const pendingAuth = await tokenStore.get<{ codeVerifier: string; createdAt: number }>(pendingKey);\n\n if (!pendingAuth) {\n throw new Error('Invalid or expired OAuth state. Please try again.');\n }\n\n // Check TTL (5 minutes)\n if (Date.now() - pendingAuth.createdAt > 5 * 60 * 1000) {\n await tokenStore.delete(pendingKey);\n throw new Error('OAuth state expired. Please try again.');\n }\n\n logger.info('Processing OAuth callback', { service, state });\n\n // Exchange code for token\n const body = new URLSearchParams({\n code,\n client_id: clientId,\n ...(clientSecret && { client_secret: clientSecret }),\n redirect_uri: redirectUri,\n grant_type: 'authorization_code',\n code_verifier: pendingAuth.codeVerifier,\n });\n\n const response = await fetch('https://oauth2.googleapis.com/token', {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/x-www-form-urlencoded',\n },\n body: body.toString(),\n });\n\n if (!response.ok) {\n const errorText = await response.text();\n throw new Error(`Token exchange failed: ${response.status} ${errorText}`);\n }\n\n const tokenResponse = (await response.json()) as TokenResponse;\n\n // Fetch user email\n const email = await this.fetchUserEmailFromToken(tokenResponse.access_token);\n\n // Create cached token\n const cachedToken: CachedToken = {\n accessToken: tokenResponse.access_token,\n refreshToken: tokenResponse.refresh_token,\n expiresAt: tokenResponse.expires_in ? Date.now() + tokenResponse.expires_in * 1000 : undefined,\n ...(tokenResponse.scope !== undefined && { scope: tokenResponse.scope }),\n };\n\n // Store token\n await setToken(tokenStore, { accountId: email, service }, cachedToken);\n\n // Add account and set as active\n await addAccount(tokenStore, { service, accountId: email });\n await setActiveAccount(tokenStore, { service, accountId: email });\n\n // Store account metadata\n await setAccountInfo(\n tokenStore,\n { service, accountId: email },\n {\n email,\n addedAt: new Date().toISOString(),\n }\n );\n\n // Clean up pending auth\n await tokenStore.delete(pendingKey);\n\n logger.info('OAuth callback completed', { service, email });\n\n return { email, token: cachedToken };\n }\n\n /**\n * Create authentication middleware for MCP tools, resources, and prompts\n *\n * Returns position-aware middleware wrappers that enrich handlers with authentication context.\n * The middleware handles token retrieval, refresh, and AuthRequiredError automatically.\n *\n * Single-user middleware for desktop/CLI apps where ONE user runs the entire process:\n * - Desktop applications (Claude Desktop)\n * - CLI tools (Gmail CLI)\n * - Personal automation scripts\n *\n * All requests use token lookups based on the active account or account override.\n *\n * @returns Object with withToolAuth, withResourceAuth, withPromptAuth methods\n *\n * @example\n * ```typescript\n * const loopback = new LoopbackOAuthProvider({ service: 'gmail', ... });\n * const authMiddleware = loopback.authMiddleware();\n * const tools = toolFactories.map(f => f()).map(authMiddleware.withToolAuth);\n * const resources = resourceFactories.map(f => f()).map(authMiddleware.withResourceAuth);\n * const prompts = promptFactories.map(f => f()).map(authMiddleware.withPromptAuth);\n * ```\n */\n authMiddleware() {\n const { service, tokenStore, logger } = this.config;\n\n // Shared wrapper logic - extracts extra parameter from specified position\n // Generic T captures the actual module type; handler is cast from unknown to callable\n const wrapAtPosition = <T extends { name: string; handler: unknown; [key: string]: unknown }>(module: T, extraPosition: number): T => {\n const operation = module.name;\n const originalHandler = module.handler as (...args: unknown[]) => Promise<unknown>;\n\n const wrappedHandler = async (...allArgs: unknown[]) => {\n // Extract extra from the correct position\n const extra = allArgs[extraPosition] as EnrichedExtra;\n\n try {\n // Check for backchannel override via _meta.accountId\n let accountId: string | undefined;\n try {\n accountId = (extra as { _meta?: { accountId?: string } })._meta?.accountId ?? (await getActiveAccount(tokenStore, { service }));\n } catch (error) {\n if (error instanceof Error && ((error as { code?: string }).code === 'REQUIRES_AUTHENTICATION' || error.name === 'AccountManagerError')) {\n accountId = undefined;\n } else {\n throw error;\n }\n }\n\n // Eagerly validate token exists or trigger OAuth flow\n await this.getAccessToken(accountId);\n\n // After OAuth flow completes, get the actual accountId (email) that was set\n const effectiveAccountId = accountId ?? (await getActiveAccount(tokenStore, { service }));\n if (!effectiveAccountId) {\n throw new Error(`No account found after OAuth flow for service ${service}`);\n }\n\n const auth = this.toAuth(effectiveAccountId);\n\n // Inject authContext and logger into extra\n (extra as { authContext?: AuthContext }).authContext = {\n auth,\n accountId: effectiveAccountId,\n };\n (extra as { logger?: unknown }).logger = logger;\n\n // Call original handler with all args\n return await originalHandler(...allArgs);\n } catch (error) {\n if (error instanceof AuthRequiredError) {\n logger.info('Authentication required', {\n service,\n tool: operation,\n descriptor: error.descriptor,\n });\n // Return auth_required response wrapped in { result } to match tool outputSchema pattern\n // Tools define outputSchema: z.object({ result: discriminatedUnion(...) }) where auth_required is a branch\n const authRequiredResponse = {\n type: 'auth_required' as const,\n provider: service,\n message: `Authentication required for ${operation}. Please authenticate with ${service}.`,\n url: error.descriptor.kind === 'auth_url' ? error.descriptor.url : undefined,\n };\n\n return {\n content: [\n {\n type: 'text' as const,\n text: JSON.stringify({ result: authRequiredResponse }),\n },\n ],\n structuredContent: { result: authRequiredResponse },\n };\n }\n throw error;\n }\n };\n\n return {\n ...module,\n handler: wrappedHandler,\n } as T;\n };\n\n return {\n withToolAuth: <T extends { name: string; config: unknown; handler: unknown }>(module: T) => wrapAtPosition(module, 1),\n withResourceAuth: <T extends { name: string; template?: unknown; config?: unknown; handler: unknown }>(module: T) => wrapAtPosition(module, 2),\n withPromptAuth: <T extends { name: string; config: unknown; handler: unknown }>(module: T) => wrapAtPosition(module, 0),\n };\n }\n}\n\n/**\n * Create a loopback OAuth client for Google services\n * Works for both stdio and HTTP transports\n */\nexport function createGoogleFileAuth(config: LoopbackOAuthConfig): OAuth2TokenStorageProvider {\n return new LoopbackOAuthProvider(config);\n}\n"],"names":["addAccount","generatePKCE","getActiveAccount","getErrorTemplate","getSuccessTemplate","getToken","setAccountInfo","setActiveAccount","setToken","randomUUID","OAuth2Client","http","open","AuthRequiredError","LoopbackOAuthProvider","getAccessToken","accountId","logger","service","tokenStore","config","effectiveAccountId","debug","storedToken","isTokenValid","accessToken","refreshToken","info","refreshedToken","refreshAccessToken","error","Error","message","String","clientId","scope","redirectUri","verifier","codeVerifier","challenge","codeChallenge","stateId","set","createdAt","Date","now","authUrl","URL","searchParams","kind","provider","url","toString","headless","token","email","performEphemeralOAuthFlow","addedAt","toISOString","toAuth","clientSecret","client","getRequestMetadataAsync","_url","credentials","access_token","token_type","headers","Map","getUserEmail","response","fetch","Authorization","ok","status","text","userInfo","json","expiresAt","fetchUserEmailFromToken","errorText","configRedirectUri","listenHost","listenPort","callbackPath","useConfiguredUri","parsed","isLoopback","hostname","port","Number","parseInt","envPort","process","env","PORT","undefined","isFinite","pathname","warn","Promise","resolve","reject","server","serverPort","finalRedirectUri","createServer","req","res","writeHead","end","close","code","get","tokenResponse","exchangeCodeForToken","cachedToken","refresh_token","expires_in","exchangeError","listen","address","console","catch","setTimeout","tokenUrl","params","client_id","redirect_uri","grant_type","code_verifier","client_secret","body","URLSearchParams","method","handleOAuthCallback","state","pendingKey","pendingAuth","delete","authMiddleware","wrapAtPosition","module","extraPosition","operation","name","originalHandler","handler","wrappedHandler","allArgs","extra","_meta","auth","authContext","tool","descriptor","authRequiredResponse","type","content","JSON","stringify","result","structuredContent","withToolAuth","withResourceAuth","withPromptAuth","createGoogleFileAuth"],"mappings":"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"}
1
+ {"version":3,"sources":["/Users/kevin/Dev/Projects/mcp-z/oauth-google/src/providers/loopback-oauth.ts"],"sourcesContent":["/**\n * Loopback OAuth Implementation (RFC 8252)\n *\n * Implements OAuth 2.0 Authorization Code Flow with PKCE using loopback interface redirection.\n * Uses ephemeral local server with OS-assigned port (RFC 8252 Section 8.3).\n *\n * CHANGE (2026-01-03):\n * - Non-headless mode now opens the auth URL AND blocks (polls) until tokens are available,\n * for BOTH redirectUri (persistent) and ephemeral (loopback) modes.\n * - Ephemeral flow no longer calls `open()` itself. Instead it:\n * 1) starts the loopback callback server\n * 2) throws AuthRequiredError(auth_url)\n * - Middleware catches AuthRequiredError(auth_url):\n * - if not headless: open(url) once + poll pending state until callback completes (or timeout)\n * - then retries token acquisition and injects authContext in the SAME tool call.\n */\n\nimport { addAccount, generatePKCE, getActiveAccount, getErrorTemplate, getSuccessTemplate, getToken, type OAuth2TokenStorageProvider, setAccountInfo, setActiveAccount, setToken } from '@mcp-z/oauth';\nimport { randomUUID } from 'crypto';\nimport { OAuth2Client } from 'google-auth-library';\nimport * as http from 'http';\nimport open from 'open';\nimport { type AuthContext, type AuthFlowDescriptor, AuthRequiredError, type CachedToken, type EnrichedExtra, type LoopbackOAuthConfig } from '../types.ts';\n\ninterface TokenResponse {\n access_token: string;\n refresh_token?: string;\n expires_in?: number;\n scope?: string;\n token_type?: string;\n}\n\ntype PendingAuth = {\n codeVerifier: string;\n createdAt: number;\n // populated when callback completes successfully\n completedAt?: number;\n email?: string;\n};\n\nconst OAUTH_TIMEOUT_MS = 5 * 60 * 1000;\nconst OAUTH_POLL_MS = 500;\n\n/**\n * Loopback OAuth Client (RFC 8252 Section 7.3)\n *\n * Implements OAuth 2.0 Authorization Code Flow with PKCE for native applications\n * using loopback interface redirection. Manages ephemeral OAuth flows and token persistence\n * with Keyv for key-based token storage using compound keys.\n *\n * Token key format: {accountId}:{service}:token (e.g., \"user@example.com:gmail:token\")\n */\nexport class LoopbackOAuthProvider implements OAuth2TokenStorageProvider {\n private config: LoopbackOAuthConfig;\n\n // Track URLs we've already opened for a given state within this process (prevents tab spam).\n private openedStates = new Set<string>();\n\n constructor(config: LoopbackOAuthConfig) {\n this.config = config;\n }\n\n /**\n * Get access token from Keyv using compound key\n *\n * @param accountId - Account identifier (email address). Required for loopback OAuth.\n * @returns Access token for API requests\n */\n async getAccessToken(accountId?: string): Promise<string> {\n const { logger, service, tokenStore } = this.config;\n\n // Use active account if no accountId specified\n const effectiveAccountId = accountId ?? (await getActiveAccount(tokenStore, { service }));\n\n // If we have an accountId, try to use existing token\n if (effectiveAccountId) {\n logger.debug('Getting access token', { service, accountId: effectiveAccountId });\n\n // Check Keyv for token using new key format\n const storedToken = await getToken<CachedToken>(tokenStore, { accountId: effectiveAccountId, service });\n\n if (storedToken && this.isTokenValid(storedToken)) {\n logger.debug('Using stored access token', { accountId: effectiveAccountId });\n return storedToken.accessToken;\n }\n\n // If stored token expired but has refresh token, try refresh\n if (storedToken?.refreshToken) {\n try {\n logger.info('Refreshing expired access token', { accountId: effectiveAccountId });\n const refreshedToken = await this.refreshAccessToken(storedToken.refreshToken);\n await setToken(tokenStore, { accountId: effectiveAccountId, service }, refreshedToken);\n return refreshedToken.accessToken;\n } catch (error) {\n logger.info('Token refresh failed, starting new OAuth flow', {\n accountId: effectiveAccountId,\n error: error instanceof Error ? error.message : String(error),\n });\n // Fall through to new OAuth flow\n }\n }\n }\n\n // No valid token or no account - need OAuth authentication\n const { clientId, scope, redirectUri } = this.config;\n\n if (redirectUri) {\n // Persistent callback mode (cloud deployment with configured redirect_uri)\n const { verifier: codeVerifier, challenge: codeChallenge } = generatePKCE();\n const stateId = randomUUID();\n\n // Store PKCE verifier for callback (5 minute TTL)\n await this.createPendingAuth({ state: stateId, codeVerifier });\n\n // Build auth URL with configured redirect_uri\n const authUrl = this.buildAuthUrl({\n redirectUri,\n codeChallenge,\n state: stateId,\n });\n\n logger.info('OAuth required - persistent callback mode', { service, redirectUri, clientId, scope });\n throw new AuthRequiredError({\n kind: 'auth_url',\n provider: service,\n url: authUrl,\n });\n }\n\n // Ephemeral callback mode (local development)\n // IMPORTANT: do NOT open here anymore; we throw auth_url and the middleware will open+poll.\n logger.info('Starting ephemeral OAuth flow', { service, headless: this.config.headless });\n const descriptor = await this.startEphemeralOAuthFlow();\n throw new AuthRequiredError(descriptor);\n }\n\n /**\n * Convert to googleapis-compatible OAuth2Client\n *\n * @param accountId - Account identifier for multi-account support (e.g., 'user@example.com')\n * @returns OAuth2Client configured for the specified account\n */\n toAuth(accountId?: string): OAuth2Client {\n const { clientId, clientSecret } = this.config;\n const client = new OAuth2Client({\n clientId,\n ...(clientSecret && { clientSecret }),\n });\n\n // @ts-expect-error - Override protected method to inject fresh token\n client.getRequestMetadataAsync = async (_url?: string) => {\n // Get token from FileAuthAdapter (not from client to avoid recursion)\n const token = await this.getAccessToken(accountId);\n\n // Update client credentials for googleapis compatibility\n client.credentials = {\n access_token: token,\n token_type: 'Bearer',\n };\n\n // Return headers as Map (required by authclient.js addUserProjectAndAuthHeaders)\n const headers = new Map<string, string>();\n headers.set('authorization', `Bearer ${token}`);\n return { headers };\n };\n\n return client;\n }\n\n /**\n * Get user email from Google's userinfo endpoint (pure query)\n * Used to query email for existing authenticated account\n *\n * @param accountId - Account identifier to get email for\n * @returns User's email address\n */\n async getUserEmail(accountId?: string): Promise<string> {\n // Get token for existing account\n const token = await this.getAccessToken(accountId);\n\n // Fetch email from Google userinfo\n const response = await fetch('https://www.googleapis.com/oauth2/v2/userinfo', {\n headers: {\n Authorization: `Bearer ${token}`,\n },\n });\n\n if (!response.ok) {\n throw new Error(`Failed to get user info: ${response.status} ${await response.text()}`);\n }\n\n const userInfo = (await response.json()) as { email: string };\n return userInfo.email;\n }\n\n private isTokenValid(token: CachedToken): boolean {\n if (!token.expiresAt) return true; // No expiry = assume valid\n return Date.now() < token.expiresAt - 60000; // 1 minute buffer\n }\n\n /**\n * Fetch user email from Google OAuth2 userinfo endpoint\n * Called during OAuth flow to get email for accountId\n *\n * @param accessToken - Fresh access token from OAuth exchange\n * @returns User's email address\n */\n private async fetchUserEmailFromToken(accessToken: string): Promise<string> {\n const { logger } = this.config;\n\n const response = await fetch('https://www.googleapis.com/oauth2/v2/userinfo', {\n headers: {\n Authorization: `Bearer ${accessToken}`,\n },\n });\n\n if (!response.ok) {\n const errorText = await response.text();\n throw new Error(`Failed to fetch user email: HTTP ${response.status} - ${errorText}`);\n }\n\n const userInfo = (await response.json()) as { email: string };\n const email = userInfo.email;\n\n logger.debug('Fetched user email from Google userinfo API', { email });\n return email;\n }\n\n // ---------------------------------------------------------------------------\n // Shared OAuth helpers\n // ---------------------------------------------------------------------------\n\n /**\n * Build OAuth authorization URL with the \"most parameters\" baseline.\n * This is shared by BOTH persistent (redirectUri) and ephemeral (loopback) modes.\n */\n private buildAuthUrl(args: { redirectUri: string; codeChallenge: string; state: string }): string {\n const { clientId, scope } = this.config;\n\n const authUrl = new URL('https://accounts.google.com/o/oauth2/v2/auth');\n authUrl.searchParams.set('client_id', clientId);\n authUrl.searchParams.set('redirect_uri', args.redirectUri);\n authUrl.searchParams.set('response_type', 'code');\n authUrl.searchParams.set('scope', scope);\n authUrl.searchParams.set('access_type', 'offline'); // always\n authUrl.searchParams.set('prompt', 'consent'); // always\n authUrl.searchParams.set('code_challenge', args.codeChallenge);\n authUrl.searchParams.set('code_challenge_method', 'S256');\n authUrl.searchParams.set('state', args.state);\n\n return authUrl.toString();\n }\n\n /**\n * Create a cached token + email from an authorization code.\n * This is the shared callback handler for BOTH persistent and ephemeral modes.\n */\n private async handleAuthorizationCode(args: { code: string; codeVerifier: string; redirectUri: string }): Promise<{ email: string; token: CachedToken }> {\n // Exchange code for token (must use same redirect_uri as in authorization request)\n const tokenResponse = await this.exchangeCodeForToken(args.code, args.codeVerifier, args.redirectUri);\n\n // Build cached token\n const cachedToken: CachedToken = {\n accessToken: tokenResponse.access_token,\n ...(tokenResponse.refresh_token !== undefined && { refreshToken: tokenResponse.refresh_token }),\n ...(tokenResponse.expires_in !== undefined && { expiresAt: Date.now() + tokenResponse.expires_in * 1000 }),\n ...(tokenResponse.scope !== undefined && { scope: tokenResponse.scope }),\n };\n\n // Fetch user email immediately using the new access token\n const email = await this.fetchUserEmailFromToken(tokenResponse.access_token);\n\n return { email, token: cachedToken };\n }\n\n /**\n * Store token + account metadata. Shared by BOTH persistent and ephemeral modes.\n */\n private async persistAuthResult(args: { email: string; token: CachedToken }): Promise<void> {\n const { tokenStore, service } = this.config;\n\n await setToken(tokenStore, { accountId: args.email, service }, args.token);\n await addAccount(tokenStore, { service, accountId: args.email });\n await setActiveAccount(tokenStore, { service, accountId: args.email });\n await setAccountInfo(tokenStore, { service, accountId: args.email }, { email: args.email, addedAt: new Date().toISOString() });\n }\n\n /**\n * Pending auth (PKCE verifier) key format.\n * Keep as-is (confirmed), even though it's not a public contract.\n */\n private pendingKey(state: string): string {\n return `${this.config.service}:pending:${state}`;\n }\n\n /**\n * Store PKCE verifier for callback (5 minute TTL).\n * Shared by BOTH persistent and ephemeral modes.\n */\n private async createPendingAuth(args: { state: string; codeVerifier: string }): Promise<void> {\n const { tokenStore } = this.config;\n const record: PendingAuth = { codeVerifier: args.codeVerifier, createdAt: Date.now() };\n await tokenStore.set(this.pendingKey(args.state), record, OAUTH_TIMEOUT_MS);\n }\n\n /**\n * Load and validate pending auth state (5 minute TTL).\n * Shared by BOTH persistent and ephemeral modes.\n */\n private async readAndValidatePendingAuth(state: string): Promise<PendingAuth> {\n const { tokenStore } = this.config;\n\n const pendingAuth = await tokenStore.get<PendingAuth>(this.pendingKey(state));\n if (!pendingAuth) {\n throw new Error('Invalid or expired OAuth state. Please try again.');\n }\n\n // Check TTL (5 minutes)\n if (Date.now() - pendingAuth.createdAt > OAUTH_TIMEOUT_MS) {\n await tokenStore.delete(this.pendingKey(state));\n throw new Error('OAuth state expired. Please try again.');\n }\n\n return pendingAuth;\n }\n\n /**\n * Mark pending auth as completed (used by middleware polling).\n */\n private async markPendingComplete(args: { state: string; email: string; pending: PendingAuth }): Promise<void> {\n const { tokenStore } = this.config;\n const updated: PendingAuth = {\n ...args.pending,\n completedAt: Date.now(),\n email: args.email,\n };\n await tokenStore.set(this.pendingKey(args.state), updated, OAUTH_TIMEOUT_MS);\n }\n\n /**\n * Clean up pending auth state.\n */\n private async deletePendingAuth(state: string): Promise<void> {\n const { tokenStore } = this.config;\n await tokenStore.delete(this.pendingKey(state));\n }\n\n /**\n * Wait until pending auth is marked completed (or timeout).\n * Used by middleware after opening auth URL in non-headless mode.\n */\n private async waitForOAuthCompletion(state: string): Promise<{ email?: string }> {\n const { tokenStore } = this.config;\n const key = this.pendingKey(state);\n const start = Date.now();\n\n while (Date.now() - start < OAUTH_TIMEOUT_MS) {\n const pending = await tokenStore.get<PendingAuth>(key);\n if (pending?.completedAt) {\n return { email: pending.email };\n }\n await new Promise((r) => setTimeout(r, OAUTH_POLL_MS));\n }\n\n throw new Error('OAuth flow timed out after 5 minutes');\n }\n\n /**\n * Process an OAuth callback using shared state validation + token exchange + persistence.\n * Used by BOTH:\n * - ephemeral loopback server callback handler\n * - persistent redirectUri callback handler\n *\n * IMPORTANT CHANGE:\n * - We do NOT delete pending state here anymore.\n * - We mark it completed so middleware can poll and then clean it up.\n */\n private async processOAuthCallback(args: { code: string; state: string; redirectUri: string }): Promise<{ email: string; token: CachedToken }> {\n const { logger, service } = this.config;\n\n const pending = await this.readAndValidatePendingAuth(args.state);\n\n logger.info('Processing OAuth callback', { service, state: args.state });\n\n const result = await this.handleAuthorizationCode({\n code: args.code,\n codeVerifier: pending.codeVerifier,\n redirectUri: args.redirectUri,\n });\n\n await this.persistAuthResult(result);\n await this.markPendingComplete({ state: args.state, email: result.email, pending });\n\n logger.info('OAuth callback completed', { service, email: result.email });\n return result;\n }\n\n // ---------------------------------------------------------------------------\n // Ephemeral loopback server + flow\n // ---------------------------------------------------------------------------\n\n /**\n * Loopback OAuth server helper (RFC 8252 Section 7.3)\n *\n * Implements ephemeral local server with OS-assigned port (RFC 8252 Section 8.3).\n * Shared callback handling uses:\n * - the same authUrl builder as redirectUri mode\n * - the same pending PKCE verifier storage as redirectUri mode\n * - the same callback processor as redirectUri mode\n */\n private createOAuthCallbackServer(args: {\n callbackPath: string;\n finalRedirectUri: () => string; // resolved after listen\n onDone: () => void;\n onError: (err: unknown) => void;\n }): http.Server {\n const { logger } = this.config;\n\n // Create ephemeral server with OS-assigned port (RFC 8252)\n return http.createServer(async (req, res) => {\n try {\n if (!req.url) {\n res.writeHead(400, { 'Content-Type': 'text/html' });\n res.end(getErrorTemplate('Invalid request'));\n args.onError(new Error('Invalid request: missing URL'));\n return;\n }\n\n // Use loopback base for URL parsing (port is not important for parsing path/query)\n const url = new URL(req.url, 'http://127.0.0.1');\n\n if (url.pathname !== args.callbackPath) {\n res.writeHead(404, { 'Content-Type': 'text/plain' });\n res.end('Not Found');\n return;\n }\n\n const code = url.searchParams.get('code');\n const error = url.searchParams.get('error');\n const state = url.searchParams.get('state');\n\n if (error) {\n res.writeHead(400, { 'Content-Type': 'text/html' });\n res.end(getErrorTemplate(error));\n args.onError(new Error(`OAuth error: ${error}`));\n return;\n }\n\n if (!code) {\n res.writeHead(400, { 'Content-Type': 'text/html' });\n res.end(getErrorTemplate('No authorization code received'));\n args.onError(new Error('No authorization code received'));\n return;\n }\n\n if (!state) {\n res.writeHead(400, { 'Content-Type': 'text/html' });\n res.end(getErrorTemplate('Missing state parameter in OAuth callback'));\n args.onError(new Error('Missing state parameter in OAuth callback'));\n return;\n }\n\n try {\n await this.processOAuthCallback({\n code,\n state,\n redirectUri: args.finalRedirectUri(),\n });\n\n res.writeHead(200, { 'Content-Type': 'text/html' });\n res.end(getSuccessTemplate());\n\n args.onDone();\n } catch (exchangeError) {\n logger.error('Token exchange failed', { error: exchangeError instanceof Error ? exchangeError.message : String(exchangeError) });\n res.writeHead(500, { 'Content-Type': 'text/html' });\n res.end(getErrorTemplate('Token exchange failed'));\n args.onError(exchangeError);\n }\n } catch (outerError) {\n logger.error('OAuth callback server error', { error: outerError instanceof Error ? outerError.message : String(outerError) });\n res.writeHead(500, { 'Content-Type': 'text/html' });\n res.end(getErrorTemplate('Internal server error'));\n args.onError(outerError);\n }\n });\n }\n\n /**\n * Starts the ephemeral loopback server and returns an AuthRequiredError(auth_url).\n * Middleware will open+poll and then retry in the same call.\n */\n private async startEphemeralOAuthFlow(): Promise<AuthFlowDescriptor> {\n const { headless, logger, redirectUri: configRedirectUri, service, tokenStore } = this.config;\n\n // Server listen configuration (where ephemeral server binds)\n let listenHost = 'localhost'; // Default: localhost for ephemeral loopback\n let listenPort = 0; // Default: OS-assigned ephemeral port\n\n // Redirect URI configuration (what goes in auth URL and token exchange)\n let callbackPath = '/callback'; // Default callback path\n let useConfiguredUri = false;\n\n if (configRedirectUri) {\n try {\n const parsed = new URL(configRedirectUri);\n const isLoopback = parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1';\n\n if (isLoopback) {\n // Local development: Listen on specific loopback address/port\n listenHost = parsed.hostname;\n listenPort = parsed.port ? Number.parseInt(parsed.port, 10) : 0;\n } else {\n // Cloud deployment: Listen on 0.0.0.0 with PORT from environment\n // The redirectUri is the PUBLIC URL (e.g., https://example.com/oauth/callback)\n // The server listens on 0.0.0.0:PORT and the load balancer routes to it\n listenHost = '0.0.0.0';\n const envPort = process.env.PORT ? Number.parseInt(process.env.PORT, 10) : undefined;\n listenPort = envPort && Number.isFinite(envPort) ? envPort : 8080;\n }\n\n // Extract callback path from URL\n if (parsed.pathname && parsed.pathname !== '/') {\n callbackPath = parsed.pathname;\n }\n\n useConfiguredUri = true;\n\n logger.debug('Using configured redirect URI', {\n listenHost,\n listenPort,\n callbackPath,\n redirectUri: configRedirectUri,\n isLoopback,\n });\n } catch (error) {\n logger.warn('Failed to parse redirectUri, using ephemeral defaults', {\n redirectUri: configRedirectUri,\n error: error instanceof Error ? error.message : String(error),\n });\n // Continue with defaults (localhost, port 0, http, /callback)\n }\n }\n\n // Generate PKCE challenge + state\n const { verifier: codeVerifier, challenge: codeChallenge } = generatePKCE();\n const stateId = randomUUID();\n\n // Store PKCE verifier for callback (5 minute TTL)\n await this.createPendingAuth({ state: stateId, codeVerifier });\n\n let server: http.Server | null = null;\n let serverPort: number;\n let finalRedirectUri: string; // set after listen\n\n // Create ephemeral server with OS-assigned port (RFC 8252)\n server = this.createOAuthCallbackServer({\n callbackPath,\n finalRedirectUri: () => finalRedirectUri,\n onDone: () => {\n server?.close();\n },\n onError: (err) => {\n logger.error('Ephemeral OAuth server error', { error: err instanceof Error ? err.message : String(err) });\n server?.close();\n },\n });\n\n // Start listening\n await new Promise<void>((resolve, reject) => {\n server?.listen(listenPort, listenHost, () => {\n const address = server?.address();\n if (!address || typeof address === 'string') {\n server?.close();\n reject(new Error('Failed to start ephemeral server'));\n return;\n }\n\n serverPort = address.port;\n\n // Construct final redirect URI\n if (useConfiguredUri && configRedirectUri) {\n finalRedirectUri = configRedirectUri;\n } else {\n finalRedirectUri = `http://localhost:${serverPort}${callbackPath}`;\n }\n\n logger.info('Ephemeral OAuth server started', { port: serverPort, headless, service });\n\n resolve();\n });\n });\n\n // Timeout after 5 minutes (match middleware polling timeout)\n setTimeout(() => {\n if (server) {\n server.close();\n // Best-effort cleanup if user never completes flow:\n // delete pending so a future attempt can restart cleanly.\n void tokenStore.delete(this.pendingKey(stateId));\n }\n }, OAUTH_TIMEOUT_MS);\n\n // Build auth URL - SAME helper as persistent mode\n const authUrl = this.buildAuthUrl({\n redirectUri: finalRedirectUri,\n codeChallenge,\n state: stateId,\n });\n\n return {\n kind: 'auth_url',\n provider: service,\n url: authUrl,\n };\n }\n\n private async exchangeCodeForToken(code: string, codeVerifier: string, redirectUri: string): Promise<TokenResponse> {\n const { clientId, clientSecret } = this.config;\n\n const tokenUrl = 'https://oauth2.googleapis.com/token';\n const params: Record<string, string> = {\n code,\n client_id: clientId,\n redirect_uri: redirectUri,\n grant_type: 'authorization_code',\n code_verifier: codeVerifier,\n };\n if (clientSecret) {\n params.client_secret = clientSecret;\n }\n const body = new URLSearchParams(params);\n\n const response = await fetch(tokenUrl, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/x-www-form-urlencoded',\n },\n body: body.toString(),\n });\n\n if (!response.ok) {\n const errorText = await response.text();\n throw new Error(`Token exchange failed: ${response.status} ${errorText}`);\n }\n\n return (await response.json()) as TokenResponse;\n }\n\n private async refreshAccessToken(refreshToken: string): Promise<CachedToken> {\n const { clientId, clientSecret } = this.config;\n\n const tokenUrl = 'https://oauth2.googleapis.com/token';\n const params: Record<string, string> = {\n refresh_token: refreshToken,\n client_id: clientId,\n grant_type: 'refresh_token',\n };\n if (clientSecret) {\n params.client_secret = clientSecret;\n }\n const body = new URLSearchParams(params);\n\n const response = await fetch(tokenUrl, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/x-www-form-urlencoded',\n },\n body: body.toString(),\n });\n\n if (!response.ok) {\n const errorText = await response.text();\n throw new Error(`Token refresh failed: ${response.status} ${errorText}`);\n }\n\n const tokenResponse = (await response.json()) as TokenResponse;\n\n return {\n accessToken: tokenResponse.access_token,\n refreshToken: refreshToken, // Keep original refresh token\n ...(tokenResponse.expires_in !== undefined && { expiresAt: Date.now() + tokenResponse.expires_in * 1000 }),\n ...(tokenResponse.scope !== undefined && { scope: tokenResponse.scope }),\n };\n }\n\n /**\n * Handle OAuth callback from persistent endpoint.\n * Used by HTTP servers with configured redirectUri.\n *\n * @param params - OAuth callback parameters\n * @returns Email and cached token\n */\n async handleOAuthCallback(params: { code: string; state?: string }): Promise<{ email: string; token: CachedToken }> {\n const { code, state } = params;\n const { redirectUri } = this.config;\n\n if (!state) {\n throw new Error('Missing state parameter in OAuth callback');\n }\n\n if (!redirectUri) {\n throw new Error('handleOAuthCallback requires configured redirectUri');\n }\n\n // Shared callback processor (same code path as ephemeral)\n return await this.processOAuthCallback({ code, state, redirectUri });\n }\n\n /**\n * Create authentication middleware for MCP tools, resources, and prompts\n *\n * Returns position-aware middleware wrappers that enrich handlers with authentication context.\n * The middleware handles token retrieval, refresh, and AuthRequiredError automatically.\n *\n * Single-user middleware for desktop/CLI apps where ONE user runs the entire process:\n * - Desktop applications (Claude Desktop)\n * - CLI tools (Gmail CLI)\n * - Personal automation scripts\n *\n * All requests use token lookups based on the active account or account override.\n *\n * @returns Object with withToolAuth, withResourceAuth, withPromptAuth methods\n */\n authMiddleware() {\n const { service, tokenStore, logger } = this.config;\n\n // Shared wrapper logic - extracts extra parameter from specified position\n // Generic T captures the actual module type; handler is cast from unknown to callable\n const wrapAtPosition = <T extends { name: string; handler: unknown; [key: string]: unknown }>(module: T, extraPosition: number): T => {\n const operation = module.name;\n const originalHandler = module.handler as (...args: unknown[]) => Promise<unknown>;\n\n const wrappedHandler = async (...allArgs: unknown[]) => {\n // Extract extra from the correct position\n const extra = allArgs[extraPosition] as EnrichedExtra;\n\n // Helper: retry once after open+poll completes\n const ensureAuthenticatedOrThrow = async () => {\n try {\n // Check for backchannel override via _meta.accountId\n let accountId: string | undefined;\n try {\n accountId = (extra as { _meta?: { accountId?: string } })._meta?.accountId ?? (await getActiveAccount(tokenStore, { service }));\n } catch (error) {\n if (error instanceof Error && (((error as { code?: string }).code === 'REQUIRES_AUTHENTICATION' || error.name === 'AccountManagerError') as boolean)) {\n accountId = undefined;\n } else {\n throw error;\n }\n }\n\n await this.getAccessToken(accountId);\n\n const effectiveAccountId = accountId ?? (await getActiveAccount(tokenStore, { service }));\n if (!effectiveAccountId) {\n throw new Error(`No account found after OAuth flow for service ${service}`);\n }\n\n return effectiveAccountId;\n } catch (error) {\n if (error instanceof AuthRequiredError && error.descriptor.kind === 'auth_url') {\n // Headless: don't open/poll; just propagate to outer handler to return auth_required.\n if (this.config.headless) throw error;\n\n // Non-headless: open once + poll until callback completes, then retry token acquisition.\n const authUrl = new URL(error.descriptor.url);\n const state = authUrl.searchParams.get('state');\n if (!state) throw new Error('Auth URL missing state parameter');\n\n if (!this.openedStates.has(state)) {\n this.openedStates.add(state);\n open(error.descriptor.url).catch((e: unknown) => {\n logger.info('Failed to open browser automatically', { error: e instanceof Error ? e.message : String(e) });\n });\n }\n\n // Block until callback completes (or timeout)\n await this.waitForOAuthCompletion(state);\n\n // Cleanup pending state after we observe completion\n await this.deletePendingAuth(state);\n\n // Retry after completion\n return await ensureAuthenticatedOrThrow();\n }\n\n throw error;\n }\n };\n\n try {\n const effectiveAccountId = await ensureAuthenticatedOrThrow();\n const auth = this.toAuth(effectiveAccountId);\n\n // Inject authContext and logger into extra\n (extra as { authContext?: AuthContext }).authContext = {\n auth,\n accountId: effectiveAccountId,\n };\n (extra as { logger?: unknown }).logger = logger;\n\n // Call original handler with all args\n return await originalHandler(...allArgs);\n } catch (error) {\n if (error instanceof AuthRequiredError) {\n logger.info('Authentication required', {\n service,\n tool: operation,\n descriptor: error.descriptor,\n });\n\n const authRequiredResponse = {\n type: 'auth_required' as const,\n provider: service,\n message: `Authentication required for ${operation}. Please authenticate with ${service}.`,\n url: error.descriptor.kind === 'auth_url' ? error.descriptor.url : undefined,\n };\n\n return {\n content: [\n {\n type: 'text' as const,\n text: JSON.stringify({ result: authRequiredResponse }),\n },\n ],\n structuredContent: { result: authRequiredResponse },\n };\n }\n throw error;\n }\n };\n\n return {\n ...module,\n handler: wrappedHandler,\n } as T;\n };\n\n return {\n withToolAuth: <T extends { name: string; config: unknown; handler: unknown }>(module: T) => wrapAtPosition(module, 1),\n withResourceAuth: <T extends { name: string; template?: unknown; config?: unknown; handler: unknown }>(module: T) => wrapAtPosition(module, 2),\n withPromptAuth: <T extends { name: string; config: unknown; handler: unknown }>(module: T) => wrapAtPosition(module, 0),\n };\n }\n}\n\n/**\n * Create a loopback OAuth client for Google services\n * Works for both stdio and HTTP transports\n */\nexport function createGoogleFileAuth(config: LoopbackOAuthConfig): OAuth2TokenStorageProvider {\n return new LoopbackOAuthProvider(config);\n}\n"],"names":["addAccount","generatePKCE","getActiveAccount","getErrorTemplate","getSuccessTemplate","getToken","setAccountInfo","setActiveAccount","setToken","randomUUID","OAuth2Client","http","open","AuthRequiredError","OAUTH_TIMEOUT_MS","OAUTH_POLL_MS","LoopbackOAuthProvider","getAccessToken","accountId","logger","service","tokenStore","config","effectiveAccountId","debug","storedToken","isTokenValid","accessToken","refreshToken","info","refreshedToken","refreshAccessToken","error","Error","message","String","clientId","scope","redirectUri","verifier","codeVerifier","challenge","codeChallenge","stateId","createPendingAuth","state","authUrl","buildAuthUrl","kind","provider","url","headless","descriptor","startEphemeralOAuthFlow","toAuth","clientSecret","client","getRequestMetadataAsync","_url","token","credentials","access_token","token_type","headers","Map","set","getUserEmail","response","fetch","Authorization","ok","status","text","userInfo","json","email","expiresAt","Date","now","fetchUserEmailFromToken","errorText","args","URL","searchParams","toString","handleAuthorizationCode","tokenResponse","exchangeCodeForToken","code","cachedToken","refresh_token","undefined","expires_in","persistAuthResult","addedAt","toISOString","pendingKey","record","createdAt","readAndValidatePendingAuth","pendingAuth","get","delete","markPendingComplete","updated","pending","completedAt","deletePendingAuth","waitForOAuthCompletion","key","start","Promise","r","setTimeout","processOAuthCallback","result","createOAuthCallbackServer","createServer","req","res","writeHead","end","onError","pathname","callbackPath","finalRedirectUri","onDone","exchangeError","outerError","configRedirectUri","listenHost","listenPort","useConfiguredUri","parsed","isLoopback","hostname","port","Number","parseInt","envPort","process","env","PORT","isFinite","warn","server","serverPort","close","err","resolve","reject","listen","address","tokenUrl","params","client_id","redirect_uri","grant_type","code_verifier","client_secret","body","URLSearchParams","method","handleOAuthCallback","authMiddleware","wrapAtPosition","module","extraPosition","operation","name","originalHandler","handler","wrappedHandler","allArgs","extra","ensureAuthenticatedOrThrow","_meta","openedStates","has","add","catch","e","auth","authContext","tool","authRequiredResponse","type","content","JSON","stringify","structuredContent","withToolAuth","withResourceAuth","withPromptAuth","Set","createGoogleFileAuth"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GAED,SAASA,UAAU,EAAEC,YAAY,EAAEC,gBAAgB,EAAEC,gBAAgB,EAAEC,kBAAkB,EAAEC,QAAQ,EAAmCC,cAAc,EAAEC,gBAAgB,EAAEC,QAAQ,QAAQ,eAAe;AACvM,SAASC,UAAU,QAAQ,SAAS;AACpC,SAASC,YAAY,QAAQ,sBAAsB;AACnD,YAAYC,UAAU,OAAO;AAC7B,OAAOC,UAAU,OAAO;AACxB,SAAoDC,iBAAiB,QAAwE,cAAc;AAkB3J,MAAMC,mBAAmB,IAAI,KAAK;AAClC,MAAMC,gBAAgB;AAEtB;;;;;;;;CAQC,GACD,OAAO,MAAMC;IAUX;;;;;GAKC,GACD,MAAMC,eAAeC,SAAkB,EAAmB;QACxD,MAAM,EAAEC,MAAM,EAAEC,OAAO,EAAEC,UAAU,EAAE,GAAG,IAAI,CAACC,MAAM;QAEnD,+CAA+C;QAC/C,MAAMC,qBAAqBL,sBAAAA,uBAAAA,YAAc,MAAMhB,iBAAiBmB,YAAY;YAAED;QAAQ;QAEtF,qDAAqD;QACrD,IAAIG,oBAAoB;YACtBJ,OAAOK,KAAK,CAAC,wBAAwB;gBAAEJ;gBAASF,WAAWK;YAAmB;YAE9E,4CAA4C;YAC5C,MAAME,cAAc,MAAMpB,SAAsBgB,YAAY;gBAAEH,WAAWK;gBAAoBH;YAAQ;YAErG,IAAIK,eAAe,IAAI,CAACC,YAAY,CAACD,cAAc;gBACjDN,OAAOK,KAAK,CAAC,6BAA6B;oBAAEN,WAAWK;gBAAmB;gBAC1E,OAAOE,YAAYE,WAAW;YAChC;YAEA,6DAA6D;YAC7D,IAAIF,wBAAAA,kCAAAA,YAAaG,YAAY,EAAE;gBAC7B,IAAI;oBACFT,OAAOU,IAAI,CAAC,mCAAmC;wBAAEX,WAAWK;oBAAmB;oBAC/E,MAAMO,iBAAiB,MAAM,IAAI,CAACC,kBAAkB,CAACN,YAAYG,YAAY;oBAC7E,MAAMpB,SAASa,YAAY;wBAAEH,WAAWK;wBAAoBH;oBAAQ,GAAGU;oBACvE,OAAOA,eAAeH,WAAW;gBACnC,EAAE,OAAOK,OAAO;oBACdb,OAAOU,IAAI,CAAC,iDAAiD;wBAC3DX,WAAWK;wBACXS,OAAOA,iBAAiBC,QAAQD,MAAME,OAAO,GAAGC,OAAOH;oBACzD;gBACA,iCAAiC;gBACnC;YACF;QACF;QAEA,2DAA2D;QAC3D,MAAM,EAAEI,QAAQ,EAAEC,KAAK,EAAEC,WAAW,EAAE,GAAG,IAAI,CAAChB,MAAM;QAEpD,IAAIgB,aAAa;YACf,2EAA2E;YAC3E,MAAM,EAAEC,UAAUC,YAAY,EAAEC,WAAWC,aAAa,EAAE,GAAGzC;YAC7D,MAAM0C,UAAUlC;YAEhB,kDAAkD;YAClD,MAAM,IAAI,CAACmC,iBAAiB,CAAC;gBAAEC,OAAOF;gBAASH;YAAa;YAE5D,8CAA8C;YAC9C,MAAMM,UAAU,IAAI,CAACC,YAAY,CAAC;gBAChCT;gBACAI;gBACAG,OAAOF;YACT;YAEAxB,OAAOU,IAAI,CAAC,6CAA6C;gBAAET;gBAASkB;gBAAaF;gBAAUC;YAAM;YACjG,MAAM,IAAIxB,kBAAkB;gBAC1BmC,MAAM;gBACNC,UAAU7B;gBACV8B,KAAKJ;YACP;QACF;QAEA,8CAA8C;QAC9C,4FAA4F;QAC5F3B,OAAOU,IAAI,CAAC,iCAAiC;YAAET;YAAS+B,UAAU,IAAI,CAAC7B,MAAM,CAAC6B,QAAQ;QAAC;QACvF,MAAMC,aAAa,MAAM,IAAI,CAACC,uBAAuB;QACrD,MAAM,IAAIxC,kBAAkBuC;IAC9B;IAEA;;;;;GAKC,GACDE,OAAOpC,SAAkB,EAAgB;QACvC,MAAM,EAAEkB,QAAQ,EAAEmB,YAAY,EAAE,GAAG,IAAI,CAACjC,MAAM;QAC9C,MAAMkC,SAAS,IAAI9C,aAAa;YAC9B0B;YACA,GAAImB,gBAAgB;gBAAEA;YAAa,CAAC;QACtC;QAEA,qEAAqE;QACrEC,OAAOC,uBAAuB,GAAG,OAAOC;YACtC,sEAAsE;YACtE,MAAMC,QAAQ,MAAM,IAAI,CAAC1C,cAAc,CAACC;YAExC,yDAAyD;YACzDsC,OAAOI,WAAW,GAAG;gBACnBC,cAAcF;gBACdG,YAAY;YACd;YAEA,iFAAiF;YACjF,MAAMC,UAAU,IAAIC;YACpBD,QAAQE,GAAG,CAAC,iBAAiB,CAAC,OAAO,EAAEN,OAAO;YAC9C,OAAO;gBAAEI;YAAQ;QACnB;QAEA,OAAOP;IACT;IAEA;;;;;;GAMC,GACD,MAAMU,aAAahD,SAAkB,EAAmB;QACtD,iCAAiC;QACjC,MAAMyC,QAAQ,MAAM,IAAI,CAAC1C,cAAc,CAACC;QAExC,mCAAmC;QACnC,MAAMiD,WAAW,MAAMC,MAAM,iDAAiD;YAC5EL,SAAS;gBACPM,eAAe,CAAC,OAAO,EAAEV,OAAO;YAClC;QACF;QAEA,IAAI,CAACQ,SAASG,EAAE,EAAE;YAChB,MAAM,IAAIrC,MAAM,CAAC,yBAAyB,EAAEkC,SAASI,MAAM,CAAC,CAAC,EAAE,MAAMJ,SAASK,IAAI,IAAI;QACxF;QAEA,MAAMC,WAAY,MAAMN,SAASO,IAAI;QACrC,OAAOD,SAASE,KAAK;IACvB;IAEQjD,aAAaiC,KAAkB,EAAW;QAChD,IAAI,CAACA,MAAMiB,SAAS,EAAE,OAAO,MAAM,2BAA2B;QAC9D,OAAOC,KAAKC,GAAG,KAAKnB,MAAMiB,SAAS,GAAG,OAAO,kBAAkB;IACjE;IAEA;;;;;;GAMC,GACD,MAAcG,wBAAwBpD,WAAmB,EAAmB;QAC1E,MAAM,EAAER,MAAM,EAAE,GAAG,IAAI,CAACG,MAAM;QAE9B,MAAM6C,WAAW,MAAMC,MAAM,iDAAiD;YAC5EL,SAAS;gBACPM,eAAe,CAAC,OAAO,EAAE1C,aAAa;YACxC;QACF;QAEA,IAAI,CAACwC,SAASG,EAAE,EAAE;YAChB,MAAMU,YAAY,MAAMb,SAASK,IAAI;YACrC,MAAM,IAAIvC,MAAM,CAAC,iCAAiC,EAAEkC,SAASI,MAAM,CAAC,GAAG,EAAES,WAAW;QACtF;QAEA,MAAMP,WAAY,MAAMN,SAASO,IAAI;QACrC,MAAMC,QAAQF,SAASE,KAAK;QAE5BxD,OAAOK,KAAK,CAAC,+CAA+C;YAAEmD;QAAM;QACpE,OAAOA;IACT;IAEA,8EAA8E;IAC9E,uBAAuB;IACvB,8EAA8E;IAE9E;;;GAGC,GACD,AAAQ5B,aAAakC,IAAmE,EAAU;QAChG,MAAM,EAAE7C,QAAQ,EAAEC,KAAK,EAAE,GAAG,IAAI,CAACf,MAAM;QAEvC,MAAMwB,UAAU,IAAIoC,IAAI;QACxBpC,QAAQqC,YAAY,CAAClB,GAAG,CAAC,aAAa7B;QACtCU,QAAQqC,YAAY,CAAClB,GAAG,CAAC,gBAAgBgB,KAAK3C,WAAW;QACzDQ,QAAQqC,YAAY,CAAClB,GAAG,CAAC,iBAAiB;QAC1CnB,QAAQqC,YAAY,CAAClB,GAAG,CAAC,SAAS5B;QAClCS,QAAQqC,YAAY,CAAClB,GAAG,CAAC,eAAe,YAAY,SAAS;QAC7DnB,QAAQqC,YAAY,CAAClB,GAAG,CAAC,UAAU,YAAY,SAAS;QACxDnB,QAAQqC,YAAY,CAAClB,GAAG,CAAC,kBAAkBgB,KAAKvC,aAAa;QAC7DI,QAAQqC,YAAY,CAAClB,GAAG,CAAC,yBAAyB;QAClDnB,QAAQqC,YAAY,CAAClB,GAAG,CAAC,SAASgB,KAAKpC,KAAK;QAE5C,OAAOC,QAAQsC,QAAQ;IACzB;IAEA;;;GAGC,GACD,MAAcC,wBAAwBJ,IAAiE,EAAkD;QACvJ,mFAAmF;QACnF,MAAMK,gBAAgB,MAAM,IAAI,CAACC,oBAAoB,CAACN,KAAKO,IAAI,EAAEP,KAAKzC,YAAY,EAAEyC,KAAK3C,WAAW;QAEpG,qBAAqB;QACrB,MAAMmD,cAA2B;YAC/B9D,aAAa2D,cAAczB,YAAY;YACvC,GAAIyB,cAAcI,aAAa,KAAKC,aAAa;gBAAE/D,cAAc0D,cAAcI,aAAa;YAAC,CAAC;YAC9F,GAAIJ,cAAcM,UAAU,KAAKD,aAAa;gBAAEf,WAAWC,KAAKC,GAAG,KAAKQ,cAAcM,UAAU,GAAG;YAAK,CAAC;YACzG,GAAIN,cAAcjD,KAAK,KAAKsD,aAAa;gBAAEtD,OAAOiD,cAAcjD,KAAK;YAAC,CAAC;QACzE;QAEA,0DAA0D;QAC1D,MAAMsC,QAAQ,MAAM,IAAI,CAACI,uBAAuB,CAACO,cAAczB,YAAY;QAE3E,OAAO;YAAEc;YAAOhB,OAAO8B;QAAY;IACrC;IAEA;;GAEC,GACD,MAAcI,kBAAkBZ,IAA2C,EAAiB;QAC1F,MAAM,EAAE5D,UAAU,EAAED,OAAO,EAAE,GAAG,IAAI,CAACE,MAAM;QAE3C,MAAMd,SAASa,YAAY;YAAEH,WAAW+D,KAAKN,KAAK;YAAEvD;QAAQ,GAAG6D,KAAKtB,KAAK;QACzE,MAAM3D,WAAWqB,YAAY;YAAED;YAASF,WAAW+D,KAAKN,KAAK;QAAC;QAC9D,MAAMpE,iBAAiBc,YAAY;YAAED;YAASF,WAAW+D,KAAKN,KAAK;QAAC;QACpE,MAAMrE,eAAee,YAAY;YAAED;YAASF,WAAW+D,KAAKN,KAAK;QAAC,GAAG;YAAEA,OAAOM,KAAKN,KAAK;YAAEmB,SAAS,IAAIjB,OAAOkB,WAAW;QAAG;IAC9H;IAEA;;;GAGC,GACD,AAAQC,WAAWnD,KAAa,EAAU;QACxC,OAAO,GAAG,IAAI,CAACvB,MAAM,CAACF,OAAO,CAAC,SAAS,EAAEyB,OAAO;IAClD;IAEA;;;GAGC,GACD,MAAcD,kBAAkBqC,IAA6C,EAAiB;QAC5F,MAAM,EAAE5D,UAAU,EAAE,GAAG,IAAI,CAACC,MAAM;QAClC,MAAM2E,SAAsB;YAAEzD,cAAcyC,KAAKzC,YAAY;YAAE0D,WAAWrB,KAAKC,GAAG;QAAG;QACrF,MAAMzD,WAAW4C,GAAG,CAAC,IAAI,CAAC+B,UAAU,CAACf,KAAKpC,KAAK,GAAGoD,QAAQnF;IAC5D;IAEA;;;GAGC,GACD,MAAcqF,2BAA2BtD,KAAa,EAAwB;QAC5E,MAAM,EAAExB,UAAU,EAAE,GAAG,IAAI,CAACC,MAAM;QAElC,MAAM8E,cAAc,MAAM/E,WAAWgF,GAAG,CAAc,IAAI,CAACL,UAAU,CAACnD;QACtE,IAAI,CAACuD,aAAa;YAChB,MAAM,IAAInE,MAAM;QAClB;QAEA,wBAAwB;QACxB,IAAI4C,KAAKC,GAAG,KAAKsB,YAAYF,SAAS,GAAGpF,kBAAkB;YACzD,MAAMO,WAAWiF,MAAM,CAAC,IAAI,CAACN,UAAU,CAACnD;YACxC,MAAM,IAAIZ,MAAM;QAClB;QAEA,OAAOmE;IACT;IAEA;;GAEC,GACD,MAAcG,oBAAoBtB,IAA4D,EAAiB;QAC7G,MAAM,EAAE5D,UAAU,EAAE,GAAG,IAAI,CAACC,MAAM;QAClC,MAAMkF,UAAuB;YAC3B,GAAGvB,KAAKwB,OAAO;YACfC,aAAa7B,KAAKC,GAAG;YACrBH,OAAOM,KAAKN,KAAK;QACnB;QACA,MAAMtD,WAAW4C,GAAG,CAAC,IAAI,CAAC+B,UAAU,CAACf,KAAKpC,KAAK,GAAG2D,SAAS1F;IAC7D;IAEA;;GAEC,GACD,MAAc6F,kBAAkB9D,KAAa,EAAiB;QAC5D,MAAM,EAAExB,UAAU,EAAE,GAAG,IAAI,CAACC,MAAM;QAClC,MAAMD,WAAWiF,MAAM,CAAC,IAAI,CAACN,UAAU,CAACnD;IAC1C;IAEA;;;GAGC,GACD,MAAc+D,uBAAuB/D,KAAa,EAA+B;QAC/E,MAAM,EAAExB,UAAU,EAAE,GAAG,IAAI,CAACC,MAAM;QAClC,MAAMuF,MAAM,IAAI,CAACb,UAAU,CAACnD;QAC5B,MAAMiE,QAAQjC,KAAKC,GAAG;QAEtB,MAAOD,KAAKC,GAAG,KAAKgC,QAAQhG,iBAAkB;YAC5C,MAAM2F,UAAU,MAAMpF,WAAWgF,GAAG,CAAcQ;YAClD,IAAIJ,oBAAAA,8BAAAA,QAASC,WAAW,EAAE;gBACxB,OAAO;oBAAE/B,OAAO8B,QAAQ9B,KAAK;gBAAC;YAChC;YACA,MAAM,IAAIoC,QAAQ,CAACC,IAAMC,WAAWD,GAAGjG;QACzC;QAEA,MAAM,IAAIkB,MAAM;IAClB;IAEA;;;;;;;;;GASC,GACD,MAAciF,qBAAqBjC,IAA0D,EAAkD;QAC7I,MAAM,EAAE9D,MAAM,EAAEC,OAAO,EAAE,GAAG,IAAI,CAACE,MAAM;QAEvC,MAAMmF,UAAU,MAAM,IAAI,CAACN,0BAA0B,CAAClB,KAAKpC,KAAK;QAEhE1B,OAAOU,IAAI,CAAC,6BAA6B;YAAET;YAASyB,OAAOoC,KAAKpC,KAAK;QAAC;QAEtE,MAAMsE,SAAS,MAAM,IAAI,CAAC9B,uBAAuB,CAAC;YAChDG,MAAMP,KAAKO,IAAI;YACfhD,cAAciE,QAAQjE,YAAY;YAClCF,aAAa2C,KAAK3C,WAAW;QAC/B;QAEA,MAAM,IAAI,CAACuD,iBAAiB,CAACsB;QAC7B,MAAM,IAAI,CAACZ,mBAAmB,CAAC;YAAE1D,OAAOoC,KAAKpC,KAAK;YAAE8B,OAAOwC,OAAOxC,KAAK;YAAE8B;QAAQ;QAEjFtF,OAAOU,IAAI,CAAC,4BAA4B;YAAET;YAASuD,OAAOwC,OAAOxC,KAAK;QAAC;QACvE,OAAOwC;IACT;IAEA,8EAA8E;IAC9E,mCAAmC;IACnC,8EAA8E;IAE9E;;;;;;;;GAQC,GACD,AAAQC,0BAA0BnC,IAKjC,EAAe;QACd,MAAM,EAAE9D,MAAM,EAAE,GAAG,IAAI,CAACG,MAAM;QAE9B,2DAA2D;QAC3D,OAAOX,KAAK0G,YAAY,CAAC,OAAOC,KAAKC;YACnC,IAAI;gBACF,IAAI,CAACD,IAAIpE,GAAG,EAAE;oBACZqE,IAAIC,SAAS,CAAC,KAAK;wBAAE,gBAAgB;oBAAY;oBACjDD,IAAIE,GAAG,CAACtH,iBAAiB;oBACzB8E,KAAKyC,OAAO,CAAC,IAAIzF,MAAM;oBACvB;gBACF;gBAEA,mFAAmF;gBACnF,MAAMiB,MAAM,IAAIgC,IAAIoC,IAAIpE,GAAG,EAAE;gBAE7B,IAAIA,IAAIyE,QAAQ,KAAK1C,KAAK2C,YAAY,EAAE;oBACtCL,IAAIC,SAAS,CAAC,KAAK;wBAAE,gBAAgB;oBAAa;oBAClDD,IAAIE,GAAG,CAAC;oBACR;gBACF;gBAEA,MAAMjC,OAAOtC,IAAIiC,YAAY,CAACkB,GAAG,CAAC;gBAClC,MAAMrE,QAAQkB,IAAIiC,YAAY,CAACkB,GAAG,CAAC;gBACnC,MAAMxD,QAAQK,IAAIiC,YAAY,CAACkB,GAAG,CAAC;gBAEnC,IAAIrE,OAAO;oBACTuF,IAAIC,SAAS,CAAC,KAAK;wBAAE,gBAAgB;oBAAY;oBACjDD,IAAIE,GAAG,CAACtH,iBAAiB6B;oBACzBiD,KAAKyC,OAAO,CAAC,IAAIzF,MAAM,CAAC,aAAa,EAAED,OAAO;oBAC9C;gBACF;gBAEA,IAAI,CAACwD,MAAM;oBACT+B,IAAIC,SAAS,CAAC,KAAK;wBAAE,gBAAgB;oBAAY;oBACjDD,IAAIE,GAAG,CAACtH,iBAAiB;oBACzB8E,KAAKyC,OAAO,CAAC,IAAIzF,MAAM;oBACvB;gBACF;gBAEA,IAAI,CAACY,OAAO;oBACV0E,IAAIC,SAAS,CAAC,KAAK;wBAAE,gBAAgB;oBAAY;oBACjDD,IAAIE,GAAG,CAACtH,iBAAiB;oBACzB8E,KAAKyC,OAAO,CAAC,IAAIzF,MAAM;oBACvB;gBACF;gBAEA,IAAI;oBACF,MAAM,IAAI,CAACiF,oBAAoB,CAAC;wBAC9B1B;wBACA3C;wBACAP,aAAa2C,KAAK4C,gBAAgB;oBACpC;oBAEAN,IAAIC,SAAS,CAAC,KAAK;wBAAE,gBAAgB;oBAAY;oBACjDD,IAAIE,GAAG,CAACrH;oBAER6E,KAAK6C,MAAM;gBACb,EAAE,OAAOC,eAAe;oBACtB5G,OAAOa,KAAK,CAAC,yBAAyB;wBAAEA,OAAO+F,yBAAyB9F,QAAQ8F,cAAc7F,OAAO,GAAGC,OAAO4F;oBAAe;oBAC9HR,IAAIC,SAAS,CAAC,KAAK;wBAAE,gBAAgB;oBAAY;oBACjDD,IAAIE,GAAG,CAACtH,iBAAiB;oBACzB8E,KAAKyC,OAAO,CAACK;gBACf;YACF,EAAE,OAAOC,YAAY;gBACnB7G,OAAOa,KAAK,CAAC,+BAA+B;oBAAEA,OAAOgG,sBAAsB/F,QAAQ+F,WAAW9F,OAAO,GAAGC,OAAO6F;gBAAY;gBAC3HT,IAAIC,SAAS,CAAC,KAAK;oBAAE,gBAAgB;gBAAY;gBACjDD,IAAIE,GAAG,CAACtH,iBAAiB;gBACzB8E,KAAKyC,OAAO,CAACM;YACf;QACF;IACF;IAEA;;;GAGC,GACD,MAAc3E,0BAAuD;QACnE,MAAM,EAAEF,QAAQ,EAAEhC,MAAM,EAAEmB,aAAa2F,iBAAiB,EAAE7G,OAAO,EAAEC,UAAU,EAAE,GAAG,IAAI,CAACC,MAAM;QAE7F,6DAA6D;QAC7D,IAAI4G,aAAa,aAAa,4CAA4C;QAC1E,IAAIC,aAAa,GAAG,sCAAsC;QAE1D,wEAAwE;QACxE,IAAIP,eAAe,aAAa,wBAAwB;QACxD,IAAIQ,mBAAmB;QAEvB,IAAIH,mBAAmB;YACrB,IAAI;gBACF,MAAMI,SAAS,IAAInD,IAAI+C;gBACvB,MAAMK,aAAaD,OAAOE,QAAQ,KAAK,eAAeF,OAAOE,QAAQ,KAAK;gBAE1E,IAAID,YAAY;oBACd,8DAA8D;oBAC9DJ,aAAaG,OAAOE,QAAQ;oBAC5BJ,aAAaE,OAAOG,IAAI,GAAGC,OAAOC,QAAQ,CAACL,OAAOG,IAAI,EAAE,MAAM;gBAChE,OAAO;oBACL,iEAAiE;oBACjE,+EAA+E;oBAC/E,wEAAwE;oBACxEN,aAAa;oBACb,MAAMS,UAAUC,QAAQC,GAAG,CAACC,IAAI,GAAGL,OAAOC,QAAQ,CAACE,QAAQC,GAAG,CAACC,IAAI,EAAE,MAAMnD;oBAC3EwC,aAAaQ,WAAWF,OAAOM,QAAQ,CAACJ,WAAWA,UAAU;gBAC/D;gBAEA,iCAAiC;gBACjC,IAAIN,OAAOV,QAAQ,IAAIU,OAAOV,QAAQ,KAAK,KAAK;oBAC9CC,eAAeS,OAAOV,QAAQ;gBAChC;gBAEAS,mBAAmB;gBAEnBjH,OAAOK,KAAK,CAAC,iCAAiC;oBAC5C0G;oBACAC;oBACAP;oBACAtF,aAAa2F;oBACbK;gBACF;YACF,EAAE,OAAOtG,OAAO;gBACdb,OAAO6H,IAAI,CAAC,yDAAyD;oBACnE1G,aAAa2F;oBACbjG,OAAOA,iBAAiBC,QAAQD,MAAME,OAAO,GAAGC,OAAOH;gBACzD;YACA,8DAA8D;YAChE;QACF;QAEA,kCAAkC;QAClC,MAAM,EAAEO,UAAUC,YAAY,EAAEC,WAAWC,aAAa,EAAE,GAAGzC;QAC7D,MAAM0C,UAAUlC;QAEhB,kDAAkD;QAClD,MAAM,IAAI,CAACmC,iBAAiB,CAAC;YAAEC,OAAOF;YAASH;QAAa;QAE5D,IAAIyG,SAA6B;QACjC,IAAIC;QACJ,IAAIrB,kBAA0B,mBAAmB;QAEjD,2DAA2D;QAC3DoB,SAAS,IAAI,CAAC7B,yBAAyB,CAAC;YACtCQ;YACAC,kBAAkB,IAAMA;YACxBC,QAAQ;gBACNmB,mBAAAA,6BAAAA,OAAQE,KAAK;YACf;YACAzB,SAAS,CAAC0B;gBACRjI,OAAOa,KAAK,CAAC,gCAAgC;oBAAEA,OAAOoH,eAAenH,QAAQmH,IAAIlH,OAAO,GAAGC,OAAOiH;gBAAK;gBACvGH,mBAAAA,6BAAAA,OAAQE,KAAK;YACf;QACF;QAEA,kBAAkB;QAClB,MAAM,IAAIpC,QAAc,CAACsC,SAASC;YAChCL,mBAAAA,6BAAAA,OAAQM,MAAM,CAACpB,YAAYD,YAAY;gBACrC,MAAMsB,UAAUP,mBAAAA,6BAAAA,OAAQO,OAAO;gBAC/B,IAAI,CAACA,WAAW,OAAOA,YAAY,UAAU;oBAC3CP,mBAAAA,6BAAAA,OAAQE,KAAK;oBACbG,OAAO,IAAIrH,MAAM;oBACjB;gBACF;gBAEAiH,aAAaM,QAAQhB,IAAI;gBAEzB,+BAA+B;gBAC/B,IAAIJ,oBAAoBH,mBAAmB;oBACzCJ,mBAAmBI;gBACrB,OAAO;oBACLJ,mBAAmB,CAAC,iBAAiB,EAAEqB,aAAatB,cAAc;gBACpE;gBAEAzG,OAAOU,IAAI,CAAC,kCAAkC;oBAAE2G,MAAMU;oBAAY/F;oBAAU/B;gBAAQ;gBAEpFiI;YACF;QACF;QAEA,6DAA6D;QAC7DpC,WAAW;YACT,IAAIgC,QAAQ;gBACVA,OAAOE,KAAK;gBACZ,oDAAoD;gBACpD,0DAA0D;gBAC1D,KAAK9H,WAAWiF,MAAM,CAAC,IAAI,CAACN,UAAU,CAACrD;YACzC;QACF,GAAG7B;QAEH,kDAAkD;QAClD,MAAMgC,UAAU,IAAI,CAACC,YAAY,CAAC;YAChCT,aAAauF;YACbnF;YACAG,OAAOF;QACT;QAEA,OAAO;YACLK,MAAM;YACNC,UAAU7B;YACV8B,KAAKJ;QACP;IACF;IAEA,MAAcyC,qBAAqBC,IAAY,EAAEhD,YAAoB,EAAEF,WAAmB,EAA0B;QAClH,MAAM,EAAEF,QAAQ,EAAEmB,YAAY,EAAE,GAAG,IAAI,CAACjC,MAAM;QAE9C,MAAMmI,WAAW;QACjB,MAAMC,SAAiC;YACrClE;YACAmE,WAAWvH;YACXwH,cAActH;YACduH,YAAY;YACZC,eAAetH;QACjB;QACA,IAAIe,cAAc;YAChBmG,OAAOK,aAAa,GAAGxG;QACzB;QACA,MAAMyG,OAAO,IAAIC,gBAAgBP;QAEjC,MAAMvF,WAAW,MAAMC,MAAMqF,UAAU;YACrCS,QAAQ;YACRnG,SAAS;gBACP,gBAAgB;YAClB;YACAiG,MAAMA,KAAK5E,QAAQ;QACrB;QAEA,IAAI,CAACjB,SAASG,EAAE,EAAE;YAChB,MAAMU,YAAY,MAAMb,SAASK,IAAI;YACrC,MAAM,IAAIvC,MAAM,CAAC,uBAAuB,EAAEkC,SAASI,MAAM,CAAC,CAAC,EAAES,WAAW;QAC1E;QAEA,OAAQ,MAAMb,SAASO,IAAI;IAC7B;IAEA,MAAc3C,mBAAmBH,YAAoB,EAAwB;QAC3E,MAAM,EAAEQ,QAAQ,EAAEmB,YAAY,EAAE,GAAG,IAAI,CAACjC,MAAM;QAE9C,MAAMmI,WAAW;QACjB,MAAMC,SAAiC;YACrChE,eAAe9D;YACf+H,WAAWvH;YACXyH,YAAY;QACd;QACA,IAAItG,cAAc;YAChBmG,OAAOK,aAAa,GAAGxG;QACzB;QACA,MAAMyG,OAAO,IAAIC,gBAAgBP;QAEjC,MAAMvF,WAAW,MAAMC,MAAMqF,UAAU;YACrCS,QAAQ;YACRnG,SAAS;gBACP,gBAAgB;YAClB;YACAiG,MAAMA,KAAK5E,QAAQ;QACrB;QAEA,IAAI,CAACjB,SAASG,EAAE,EAAE;YAChB,MAAMU,YAAY,MAAMb,SAASK,IAAI;YACrC,MAAM,IAAIvC,MAAM,CAAC,sBAAsB,EAAEkC,SAASI,MAAM,CAAC,CAAC,EAAES,WAAW;QACzE;QAEA,MAAMM,gBAAiB,MAAMnB,SAASO,IAAI;QAE1C,OAAO;YACL/C,aAAa2D,cAAczB,YAAY;YACvCjC,cAAcA;YACd,GAAI0D,cAAcM,UAAU,KAAKD,aAAa;gBAAEf,WAAWC,KAAKC,GAAG,KAAKQ,cAAcM,UAAU,GAAG;YAAK,CAAC;YACzG,GAAIN,cAAcjD,KAAK,KAAKsD,aAAa;gBAAEtD,OAAOiD,cAAcjD,KAAK;YAAC,CAAC;QACzE;IACF;IAEA;;;;;;GAMC,GACD,MAAM8H,oBAAoBT,MAAwC,EAAkD;QAClH,MAAM,EAAElE,IAAI,EAAE3C,KAAK,EAAE,GAAG6G;QACxB,MAAM,EAAEpH,WAAW,EAAE,GAAG,IAAI,CAAChB,MAAM;QAEnC,IAAI,CAACuB,OAAO;YACV,MAAM,IAAIZ,MAAM;QAClB;QAEA,IAAI,CAACK,aAAa;YAChB,MAAM,IAAIL,MAAM;QAClB;QAEA,0DAA0D;QAC1D,OAAO,MAAM,IAAI,CAACiF,oBAAoB,CAAC;YAAE1B;YAAM3C;YAAOP;QAAY;IACpE;IAEA;;;;;;;;;;;;;;GAcC,GACD8H,iBAAiB;QACf,MAAM,EAAEhJ,OAAO,EAAEC,UAAU,EAAEF,MAAM,EAAE,GAAG,IAAI,CAACG,MAAM;QAEnD,0EAA0E;QAC1E,sFAAsF;QACtF,MAAM+I,iBAAiB,CAAuEC,QAAWC;YACvG,MAAMC,YAAYF,OAAOG,IAAI;YAC7B,MAAMC,kBAAkBJ,OAAOK,OAAO;YAEtC,MAAMC,iBAAiB,OAAO,GAAGC;gBAC/B,0CAA0C;gBAC1C,MAAMC,QAAQD,OAAO,CAACN,cAAc;gBAEpC,+CAA+C;gBAC/C,MAAMQ,6BAA6B;oBACjC,IAAI;wBACF,qDAAqD;wBACrD,IAAI7J;wBACJ,IAAI;;gCACU;4BAAZA,qBAAY,eAAA,AAAC4J,MAA6CE,KAAK,cAAnD,mCAAA,aAAqD9J,SAAS,uCAAK,MAAMhB,iBAAiBmB,YAAY;gCAAED;4BAAQ;wBAC9H,EAAE,OAAOY,OAAO;4BACd,IAAIA,iBAAiBC,SAAW,CAAA,AAACD,MAA4BwD,IAAI,KAAK,6BAA6BxD,MAAMyI,IAAI,KAAK,qBAAoB,GAAgB;gCACpJvJ,YAAYyE;4BACd,OAAO;gCACL,MAAM3D;4BACR;wBACF;wBAEA,MAAM,IAAI,CAACf,cAAc,CAACC;wBAE1B,MAAMK,qBAAqBL,sBAAAA,uBAAAA,YAAc,MAAMhB,iBAAiBmB,YAAY;4BAAED;wBAAQ;wBACtF,IAAI,CAACG,oBAAoB;4BACvB,MAAM,IAAIU,MAAM,CAAC,8CAA8C,EAAEb,SAAS;wBAC5E;wBAEA,OAAOG;oBACT,EAAE,OAAOS,OAAO;wBACd,IAAIA,iBAAiBnB,qBAAqBmB,MAAMoB,UAAU,CAACJ,IAAI,KAAK,YAAY;4BAC9E,sFAAsF;4BACtF,IAAI,IAAI,CAAC1B,MAAM,CAAC6B,QAAQ,EAAE,MAAMnB;4BAEhC,yFAAyF;4BACzF,MAAMc,UAAU,IAAIoC,IAAIlD,MAAMoB,UAAU,CAACF,GAAG;4BAC5C,MAAML,QAAQC,QAAQqC,YAAY,CAACkB,GAAG,CAAC;4BACvC,IAAI,CAACxD,OAAO,MAAM,IAAIZ,MAAM;4BAE5B,IAAI,CAAC,IAAI,CAACgJ,YAAY,CAACC,GAAG,CAACrI,QAAQ;gCACjC,IAAI,CAACoI,YAAY,CAACE,GAAG,CAACtI;gCACtBjC,KAAKoB,MAAMoB,UAAU,CAACF,GAAG,EAAEkI,KAAK,CAAC,CAACC;oCAChClK,OAAOU,IAAI,CAAC,wCAAwC;wCAAEG,OAAOqJ,aAAapJ,QAAQoJ,EAAEnJ,OAAO,GAAGC,OAAOkJ;oCAAG;gCAC1G;4BACF;4BAEA,8CAA8C;4BAC9C,MAAM,IAAI,CAACzE,sBAAsB,CAAC/D;4BAElC,oDAAoD;4BACpD,MAAM,IAAI,CAAC8D,iBAAiB,CAAC9D;4BAE7B,yBAAyB;4BACzB,OAAO,MAAMkI;wBACf;wBAEA,MAAM/I;oBACR;gBACF;gBAEA,IAAI;oBACF,MAAMT,qBAAqB,MAAMwJ;oBACjC,MAAMO,OAAO,IAAI,CAAChI,MAAM,CAAC/B;oBAEzB,2CAA2C;oBAC1CuJ,MAAwCS,WAAW,GAAG;wBACrDD;wBACApK,WAAWK;oBACb;oBACCuJ,MAA+B3J,MAAM,GAAGA;oBAEzC,sCAAsC;oBACtC,OAAO,MAAMuJ,mBAAmBG;gBAClC,EAAE,OAAO7I,OAAO;oBACd,IAAIA,iBAAiBnB,mBAAmB;wBACtCM,OAAOU,IAAI,CAAC,2BAA2B;4BACrCT;4BACAoK,MAAMhB;4BACNpH,YAAYpB,MAAMoB,UAAU;wBAC9B;wBAEA,MAAMqI,uBAAuB;4BAC3BC,MAAM;4BACNzI,UAAU7B;4BACVc,SAAS,CAAC,4BAA4B,EAAEsI,UAAU,2BAA2B,EAAEpJ,QAAQ,CAAC,CAAC;4BACzF8B,KAAKlB,MAAMoB,UAAU,CAACJ,IAAI,KAAK,aAAahB,MAAMoB,UAAU,CAACF,GAAG,GAAGyC;wBACrE;wBAEA,OAAO;4BACLgG,SAAS;gCACP;oCACED,MAAM;oCACNlH,MAAMoH,KAAKC,SAAS,CAAC;wCAAE1E,QAAQsE;oCAAqB;gCACtD;6BACD;4BACDK,mBAAmB;gCAAE3E,QAAQsE;4BAAqB;wBACpD;oBACF;oBACA,MAAMzJ;gBACR;YACF;YAEA,OAAO;gBACL,GAAGsI,MAAM;gBACTK,SAASC;YACX;QACF;QAEA,OAAO;YACLmB,cAAc,CAAgEzB,SAAcD,eAAeC,QAAQ;YACnH0B,kBAAkB,CAAqF1B,SAAcD,eAAeC,QAAQ;YAC5I2B,gBAAgB,CAAgE3B,SAAcD,eAAeC,QAAQ;QACvH;IACF;IAlxBA,YAAYhJ,MAA2B,CAAE;QAHzC,6FAA6F;aACrF2J,eAAe,IAAIiB;QAGzB,IAAI,CAAC5K,MAAM,GAAGA;IAChB;AAixBF;AAEA;;;CAGC,GACD,OAAO,SAAS6K,qBAAqB7K,MAA2B;IAC9D,OAAO,IAAIN,sBAAsBM;AACnC"}
@@ -121,15 +121,15 @@ import { parseArgs } from 'util';
121
121
  if (auth === 'dcr' && transport === 'stdio') {
122
122
  throw new Error('DCR authentication mode requires HTTP transport. DCR is not supported with stdio transport.');
123
123
  }
124
- const cliHeadless = typeof values.headless === 'boolean' ? values.headless : undefined;
125
- const envHeadless = env.HEADLESS === 'true' ? true : env.HEADLESS === 'false' ? false : undefined;
126
- const headless = (_ref = cliHeadless !== null && cliHeadless !== void 0 ? cliHeadless : envHeadless) !== null && _ref !== void 0 ? _ref : false;
127
124
  const cliRedirectUri = typeof values['redirect-uri'] === 'string' ? values['redirect-uri'] : undefined;
128
125
  const envRedirectUri = env.REDIRECT_URI;
129
126
  const redirectUri = cliRedirectUri !== null && cliRedirectUri !== void 0 ? cliRedirectUri : envRedirectUri;
130
127
  if (redirectUri && transport === 'stdio') {
131
128
  throw new Error('REDIRECT_URI requires HTTP transport. The OAuth callback must be served over HTTP.');
132
129
  }
130
+ const cliHeadless = typeof values.headless === 'boolean' ? values.headless : undefined;
131
+ const envHeadless = env.HEADLESS === 'true' ? true : env.HEADLESS === 'false' ? false : undefined;
132
+ const headless = (_ref = cliHeadless !== null && cliHeadless !== void 0 ? cliHeadless : envHeadless) !== null && _ref !== void 0 ? _ref : redirectUri !== undefined; // default for redirectUri is headless (assume server http deployment); otherwise assume local and non-headless
133
133
  const clientId = requiredEnv('GOOGLE_CLIENT_ID');
134
134
  const clientSecret = env.GOOGLE_CLIENT_SECRET;
135
135
  let serviceAccountKeyFile;
@@ -1 +1 @@
1
- {"version":3,"sources":["/Users/kevin/Dev/Projects/mcp-z/oauth-google/src/setup/config.ts"],"sourcesContent":["/**\n * Google OAuth configuration parsing from CLI arguments and environment variables.\n *\n * This module provides utilities to parse Google OAuth configuration from\n * CLI arguments and environment variables, following the same pattern as @mcp-z/server's\n * parseConfig().\n */\n\nimport { resolve } from 'path';\nimport { parseArgs } from 'util';\nimport type { DcrConfig, OAuthConfig } from '../types.ts';\n\n// Re-export for direct imports from config.ts\nexport type { DcrConfig, OAuthConfig };\n\n/**\n * auth mode type (from OAuthConfig)\n */\ntype AuthMode = 'loopback-oauth' | 'service-account' | 'dcr';\n\n/**\n * Parse auth mode string into auth mode.\n *\n * @param value - Auth mode string ('loopback-oauth', 'service-account', or 'dcr')\n * @returns Parsed auth mode\n * @throws Error if value is invalid\n *\n * @example Valid formats\n * ```typescript\n * parseAuthMode('loopback-oauth') // { auth: 'loopback-oauth' }\n * parseAuthMode('service-account') // { auth: 'service-account' }\n * parseAuthMode('dcr') // { auth: 'dcr' }\n * ```\n */\nfunction parseAuthMode(value: string): {\n auth: AuthMode;\n} {\n if (value !== 'loopback-oauth' && value !== 'service-account' && value !== 'dcr') {\n throw new Error(`Invalid --auth value: \"${value}\". Valid values: loopback-oauth, service-account, dcr`);\n }\n\n return {\n auth: value as AuthMode,\n };\n}\n\n/**\n * Transport type for MCP servers\n *\n * @typedef {('stdio' | 'http')} TransportType\n * @property {'stdio'} stdio - Standard input/output transport for CLI applications\n * @property {'http'} http - HTTP transport for web-based applications\n */\nexport type TransportType = 'stdio' | 'http';\n\n/**\n * Parse Google OAuth configuration from CLI arguments and environment variables.\n *\n * CLI Arguments:\n * - --auth: Auth mode ('loopback-oauth' | 'service-account' | 'dcr')\n * - Default: 'loopback-oauth' (if flag is omitted)\n * - --headless: Disable browser opening for OAuth flow (default: false, true in test env)\n * - --redirect-uri: Override OAuth redirect URI (default: ephemeral loopback)\n * - --service-account-key-file: Service account key file path (required for service-account mode)\n *\n * Required environment variables:\n * - GOOGLE_CLIENT_ID: OAuth 2.0 client ID from Google Cloud Console\n *\n * Optional environment variables:\n * - GOOGLE_CLIENT_SECRET: OAuth 2.0 client secret (optional for public clients)\n * - AUTH_MODE: Auth mode (same format as --auth flag)\n * - HEADLESS: Headless mode flag ('true' to enable)\n * - REDIRECT_URI: OAuth redirect URI (overridden by --redirect-uri CLI flag)\n * - GOOGLE_SERVICE_ACCOUNT_KEY_FILE: Service account key file (for service-account mode)\n *\n * @param args - CLI arguments array (typically process.argv)\n * @param env - Environment variables object (typically process.env)\n * @param transport - Optional transport type. If 'stdio' and auth mode is 'dcr', throws an error.\n * See {@link TransportType} for valid values.\n * @returns Parsed Google OAuth configuration\n * @throws Error if required environment variables are missing, values are invalid, or DCR is used with stdio transport\n *\n * @example Default mode (no flags)\n * ```typescript\n * const config = parseConfig(process.argv, process.env);\n * // { auth: 'loopback-oauth' }\n * ```\n *\n * @example Override auth mode\n * ```typescript\n * parseConfig(['--auth=loopback-oauth'], process.env);\n * parseConfig(['--auth=service-account'], process.env);\n * parseConfig(['--auth=dcr'], process.env);\n * ```\n *\n * @example With transport validation\n * ```typescript\n * parseConfig(['--auth=dcr'], process.env, 'http'); // OK\n * parseConfig(['--auth=dcr'], process.env, 'stdio'); // Throws error\n * ```\n *\n * Valid auth modes:\n * - loopback-oauth (default)\n * - service-account\n * - dcr (HTTP transport only)\n */\nexport function parseConfig(args: string[], env: Record<string, string | undefined>, transport?: TransportType): OAuthConfig {\n function requiredEnv(key: string): string {\n const value = env[key];\n if (!value) {\n throw new Error(`Environment variable ${key} is required for Google OAuth`);\n }\n return value;\n }\n\n // Parse CLI arguments\n const { values } = parseArgs({\n args,\n options: {\n auth: { type: 'string' },\n headless: { type: 'boolean' },\n 'redirect-uri': { type: 'string' },\n 'service-account-key-file': { type: 'string' },\n },\n strict: false, // Allow other arguments\n allowPositionals: true,\n });\n\n const authArg = typeof values.auth === 'string' ? values.auth : undefined;\n const envAuthMode = env.AUTH_MODE;\n const mode = authArg || envAuthMode;\n\n let auth: AuthMode;\n\n if (mode) {\n const parsed = parseAuthMode(mode);\n auth = parsed.auth;\n } else {\n // DEFAULT: No flags provided, use loopback-oauth\n auth = 'loopback-oauth';\n }\n\n // Validate: DCR only works with HTTP transport\n if (auth === 'dcr' && transport === 'stdio') {\n throw new Error('DCR authentication mode requires HTTP transport. DCR is not supported with stdio transport.');\n }\n\n const cliHeadless = typeof values.headless === 'boolean' ? values.headless : undefined;\n const envHeadless = env.HEADLESS === 'true' ? true : env.HEADLESS === 'false' ? false : undefined;\n const headless = cliHeadless ?? envHeadless ?? false;\n\n const cliRedirectUri = typeof values['redirect-uri'] === 'string' ? values['redirect-uri'] : undefined;\n const envRedirectUri = env.REDIRECT_URI;\n const redirectUri = cliRedirectUri ?? envRedirectUri;\n if (redirectUri && transport === 'stdio') {\n throw new Error('REDIRECT_URI requires HTTP transport. The OAuth callback must be served over HTTP.');\n }\n\n const clientId = requiredEnv('GOOGLE_CLIENT_ID');\n const clientSecret = env.GOOGLE_CLIENT_SECRET;\n\n let serviceAccountKeyFile: string | undefined;\n if (auth === 'service-account') {\n const cliKeyFile = typeof values['service-account-key-file'] === 'string' ? values['service-account-key-file'] : undefined;\n serviceAccountKeyFile = cliKeyFile ?? env.GOOGLE_SERVICE_ACCOUNT_KEY_FILE;\n\n if (!serviceAccountKeyFile) {\n throw new Error('GOOGLE_SERVICE_ACCOUNT_KEY_FILE environment variable is required when using service account authentication. ' + 'Example: export GOOGLE_SERVICE_ACCOUNT_KEY_FILE=./service-account.json');\n }\n\n // Resolve relative paths now since cwd can change during execution\n serviceAccountKeyFile = resolve(serviceAccountKeyFile);\n }\n\n return {\n clientId,\n ...(clientSecret && { clientSecret }),\n auth,\n headless,\n ...(redirectUri && { redirectUri }),\n ...(serviceAccountKeyFile && { serviceAccountKeyFile }),\n };\n}\n\n/**\n * Build production configuration from process globals.\n * Entry point for production server.\n */\nexport function createConfig(): OAuthConfig {\n return parseConfig(process.argv, process.env);\n}\n\n/**\n * Parse DCR configuration from CLI arguments and environment variables.\n *\n * CLI Arguments:\n * - --dcr-mode: DCR mode ('self-hosted' | 'external')\n * - Default: 'self-hosted' (if flag is omitted)\n * - --dcr-verify-url: External verification endpoint URL (required for external mode)\n * - --dcr-store-uri: DCR client storage URI (required for self-hosted mode)\n *\n * Required environment variables:\n * - GOOGLE_CLIENT_ID: OAuth 2.0 client ID from Google Cloud Console\n *\n * Optional environment variables:\n * - GOOGLE_CLIENT_SECRET: OAuth 2.0 client secret (optional for public clients)\n * - DCR_MODE: DCR mode (same format as --dcr-mode flag)\n * - DCR_VERIFY_URL: External verification URL (same as --dcr-verify-url flag)\n * - DCR_STORE_URI: DCR storage URI (same as --dcr-store-uri flag)\n *\n * @param args - CLI arguments array (typically process.argv)\n * @param env - Environment variables object (typically process.env)\n * @param scope - OAuth scopes to request (space-separated)\n * @returns Parsed DCR configuration\n * @throws Error if required environment variables are missing or validation fails\n *\n * @example Self-hosted mode\n * ```typescript\n * const config = parseDcrConfig(\n * ['--dcr-mode=self-hosted', '--dcr-store-uri=file:///path/to/store.json'],\n * process.env,\n * 'https://www.googleapis.com/auth/drive.readonly'\n * );\n * ```\n *\n * @example External mode\n * ```typescript\n * const config = parseDcrConfig(\n * ['--dcr-mode=external', '--dcr-verify-url=https://auth0.example.com/verify'],\n * process.env,\n * 'https://www.googleapis.com/auth/drive.readonly'\n * );\n * ```\n */\nexport function parseDcrConfig(args: string[], env: Record<string, string | undefined>, scope: string): DcrConfig {\n function requiredEnv(key: string): string {\n const value = env[key];\n if (!value) {\n throw new Error(`Environment variable ${key} is required for DCR configuration`);\n }\n return value;\n }\n\n const { values } = parseArgs({\n args,\n options: {\n 'dcr-mode': { type: 'string' },\n 'dcr-verify-url': { type: 'string' },\n 'dcr-store-uri': { type: 'string' },\n },\n strict: false,\n allowPositionals: true,\n });\n\n const cliMode = typeof values['dcr-mode'] === 'string' ? values['dcr-mode'] : undefined;\n const envMode = env.DCR_MODE;\n const mode = cliMode || envMode || 'self-hosted';\n\n if (mode !== 'self-hosted' && mode !== 'external') {\n throw new Error(`Invalid --dcr-mode value: \"${mode}\". Valid values: self-hosted, external`);\n }\n\n const cliVerifyUrl = typeof values['dcr-verify-url'] === 'string' ? values['dcr-verify-url'] : undefined;\n const envVerifyUrl = env.DCR_VERIFY_URL;\n const verifyUrl = cliVerifyUrl || envVerifyUrl;\n\n const cliStoreUri = typeof values['dcr-store-uri'] === 'string' ? values['dcr-store-uri'] : undefined;\n const envStoreUri = env.DCR_STORE_URI;\n const storeUri = cliStoreUri || envStoreUri;\n\n if (mode === 'external' && !verifyUrl) {\n throw new Error('DCR external mode requires --dcr-verify-url or DCR_VERIFY_URL environment variable');\n }\n\n const clientId = requiredEnv('GOOGLE_CLIENT_ID');\n const clientSecret = env.GOOGLE_CLIENT_SECRET;\n\n return {\n mode,\n ...(verifyUrl && { verifyUrl }),\n ...(storeUri && { storeUri }),\n clientId,\n ...(clientSecret && { clientSecret }),\n scope,\n };\n}\n"],"names":["resolve","parseArgs","parseAuthMode","value","Error","auth","parseConfig","args","env","transport","cliHeadless","requiredEnv","key","values","options","type","headless","strict","allowPositionals","authArg","undefined","envAuthMode","AUTH_MODE","mode","parsed","envHeadless","HEADLESS","cliRedirectUri","envRedirectUri","REDIRECT_URI","redirectUri","clientId","clientSecret","GOOGLE_CLIENT_SECRET","serviceAccountKeyFile","cliKeyFile","GOOGLE_SERVICE_ACCOUNT_KEY_FILE","createConfig","process","argv","parseDcrConfig","scope","cliMode","envMode","DCR_MODE","cliVerifyUrl","envVerifyUrl","DCR_VERIFY_URL","verifyUrl","cliStoreUri","envStoreUri","DCR_STORE_URI","storeUri"],"mappings":"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"}
1
+ {"version":3,"sources":["/Users/kevin/Dev/Projects/mcp-z/oauth-google/src/setup/config.ts"],"sourcesContent":["/**\n * Google OAuth configuration parsing from CLI arguments and environment variables.\n *\n * This module provides utilities to parse Google OAuth configuration from\n * CLI arguments and environment variables, following the same pattern as @mcp-z/server's\n * parseConfig().\n */\n\nimport { resolve } from 'path';\nimport { parseArgs } from 'util';\nimport type { DcrConfig, OAuthConfig } from '../types.ts';\n\n// Re-export for direct imports from config.ts\nexport type { DcrConfig, OAuthConfig };\n\n/**\n * auth mode type (from OAuthConfig)\n */\ntype AuthMode = 'loopback-oauth' | 'service-account' | 'dcr';\n\n/**\n * Parse auth mode string into auth mode.\n *\n * @param value - Auth mode string ('loopback-oauth', 'service-account', or 'dcr')\n * @returns Parsed auth mode\n * @throws Error if value is invalid\n *\n * @example Valid formats\n * ```typescript\n * parseAuthMode('loopback-oauth') // { auth: 'loopback-oauth' }\n * parseAuthMode('service-account') // { auth: 'service-account' }\n * parseAuthMode('dcr') // { auth: 'dcr' }\n * ```\n */\nfunction parseAuthMode(value: string): {\n auth: AuthMode;\n} {\n if (value !== 'loopback-oauth' && value !== 'service-account' && value !== 'dcr') {\n throw new Error(`Invalid --auth value: \"${value}\". Valid values: loopback-oauth, service-account, dcr`);\n }\n\n return {\n auth: value as AuthMode,\n };\n}\n\n/**\n * Transport type for MCP servers\n *\n * @typedef {('stdio' | 'http')} TransportType\n * @property {'stdio'} stdio - Standard input/output transport for CLI applications\n * @property {'http'} http - HTTP transport for web-based applications\n */\nexport type TransportType = 'stdio' | 'http';\n\n/**\n * Parse Google OAuth configuration from CLI arguments and environment variables.\n *\n * CLI Arguments:\n * - --auth: Auth mode ('loopback-oauth' | 'service-account' | 'dcr')\n * - Default: 'loopback-oauth' (if flag is omitted)\n * - --headless: Disable browser opening for OAuth flow (default: false, true in test env)\n * - --redirect-uri: Override OAuth redirect URI (default: ephemeral loopback)\n * - --service-account-key-file: Service account key file path (required for service-account mode)\n *\n * Required environment variables:\n * - GOOGLE_CLIENT_ID: OAuth 2.0 client ID from Google Cloud Console\n *\n * Optional environment variables:\n * - GOOGLE_CLIENT_SECRET: OAuth 2.0 client secret (optional for public clients)\n * - AUTH_MODE: Auth mode (same format as --auth flag)\n * - HEADLESS: Headless mode flag ('true' to enable)\n * - REDIRECT_URI: OAuth redirect URI (overridden by --redirect-uri CLI flag)\n * - GOOGLE_SERVICE_ACCOUNT_KEY_FILE: Service account key file (for service-account mode)\n *\n * @param args - CLI arguments array (typically process.argv)\n * @param env - Environment variables object (typically process.env)\n * @param transport - Optional transport type. If 'stdio' and auth mode is 'dcr', throws an error.\n * See {@link TransportType} for valid values.\n * @returns Parsed Google OAuth configuration\n * @throws Error if required environment variables are missing, values are invalid, or DCR is used with stdio transport\n *\n * @example Default mode (no flags)\n * ```typescript\n * const config = parseConfig(process.argv, process.env);\n * // { auth: 'loopback-oauth' }\n * ```\n *\n * @example Override auth mode\n * ```typescript\n * parseConfig(['--auth=loopback-oauth'], process.env);\n * parseConfig(['--auth=service-account'], process.env);\n * parseConfig(['--auth=dcr'], process.env);\n * ```\n *\n * @example With transport validation\n * ```typescript\n * parseConfig(['--auth=dcr'], process.env, 'http'); // OK\n * parseConfig(['--auth=dcr'], process.env, 'stdio'); // Throws error\n * ```\n *\n * Valid auth modes:\n * - loopback-oauth (default)\n * - service-account\n * - dcr (HTTP transport only)\n */\nexport function parseConfig(args: string[], env: Record<string, string | undefined>, transport?: TransportType): OAuthConfig {\n function requiredEnv(key: string): string {\n const value = env[key];\n if (!value) {\n throw new Error(`Environment variable ${key} is required for Google OAuth`);\n }\n return value;\n }\n\n // Parse CLI arguments\n const { values } = parseArgs({\n args,\n options: {\n auth: { type: 'string' },\n headless: { type: 'boolean' },\n 'redirect-uri': { type: 'string' },\n 'service-account-key-file': { type: 'string' },\n },\n strict: false, // Allow other arguments\n allowPositionals: true,\n });\n\n const authArg = typeof values.auth === 'string' ? values.auth : undefined;\n const envAuthMode = env.AUTH_MODE;\n const mode = authArg || envAuthMode;\n\n let auth: AuthMode;\n\n if (mode) {\n const parsed = parseAuthMode(mode);\n auth = parsed.auth;\n } else {\n // DEFAULT: No flags provided, use loopback-oauth\n auth = 'loopback-oauth';\n }\n\n // Validate: DCR only works with HTTP transport\n if (auth === 'dcr' && transport === 'stdio') {\n throw new Error('DCR authentication mode requires HTTP transport. DCR is not supported with stdio transport.');\n }\n\n const cliRedirectUri = typeof values['redirect-uri'] === 'string' ? values['redirect-uri'] : undefined;\n const envRedirectUri = env.REDIRECT_URI;\n const redirectUri = cliRedirectUri ?? envRedirectUri;\n if (redirectUri && transport === 'stdio') {\n throw new Error('REDIRECT_URI requires HTTP transport. The OAuth callback must be served over HTTP.');\n }\n\n const cliHeadless = typeof values.headless === 'boolean' ? values.headless : undefined;\n const envHeadless = env.HEADLESS === 'true' ? true : env.HEADLESS === 'false' ? false : undefined;\n const headless = cliHeadless ?? envHeadless ?? redirectUri !== undefined; // default for redirectUri is headless (assume server http deployment); otherwise assume local and non-headless\n\n const clientId = requiredEnv('GOOGLE_CLIENT_ID');\n const clientSecret = env.GOOGLE_CLIENT_SECRET;\n\n let serviceAccountKeyFile: string | undefined;\n if (auth === 'service-account') {\n const cliKeyFile = typeof values['service-account-key-file'] === 'string' ? values['service-account-key-file'] : undefined;\n serviceAccountKeyFile = cliKeyFile ?? env.GOOGLE_SERVICE_ACCOUNT_KEY_FILE;\n\n if (!serviceAccountKeyFile) {\n throw new Error('GOOGLE_SERVICE_ACCOUNT_KEY_FILE environment variable is required when using service account authentication. ' + 'Example: export GOOGLE_SERVICE_ACCOUNT_KEY_FILE=./service-account.json');\n }\n\n // Resolve relative paths now since cwd can change during execution\n serviceAccountKeyFile = resolve(serviceAccountKeyFile);\n }\n\n return {\n clientId,\n ...(clientSecret && { clientSecret }),\n auth,\n headless,\n ...(redirectUri && { redirectUri }),\n ...(serviceAccountKeyFile && { serviceAccountKeyFile }),\n };\n}\n\n/**\n * Build production configuration from process globals.\n * Entry point for production server.\n */\nexport function createConfig(): OAuthConfig {\n return parseConfig(process.argv, process.env);\n}\n\n/**\n * Parse DCR configuration from CLI arguments and environment variables.\n *\n * CLI Arguments:\n * - --dcr-mode: DCR mode ('self-hosted' | 'external')\n * - Default: 'self-hosted' (if flag is omitted)\n * - --dcr-verify-url: External verification endpoint URL (required for external mode)\n * - --dcr-store-uri: DCR client storage URI (required for self-hosted mode)\n *\n * Required environment variables:\n * - GOOGLE_CLIENT_ID: OAuth 2.0 client ID from Google Cloud Console\n *\n * Optional environment variables:\n * - GOOGLE_CLIENT_SECRET: OAuth 2.0 client secret (optional for public clients)\n * - DCR_MODE: DCR mode (same format as --dcr-mode flag)\n * - DCR_VERIFY_URL: External verification URL (same as --dcr-verify-url flag)\n * - DCR_STORE_URI: DCR storage URI (same as --dcr-store-uri flag)\n *\n * @param args - CLI arguments array (typically process.argv)\n * @param env - Environment variables object (typically process.env)\n * @param scope - OAuth scopes to request (space-separated)\n * @returns Parsed DCR configuration\n * @throws Error if required environment variables are missing or validation fails\n *\n * @example Self-hosted mode\n * ```typescript\n * const config = parseDcrConfig(\n * ['--dcr-mode=self-hosted', '--dcr-store-uri=file:///path/to/store.json'],\n * process.env,\n * 'https://www.googleapis.com/auth/drive.readonly'\n * );\n * ```\n *\n * @example External mode\n * ```typescript\n * const config = parseDcrConfig(\n * ['--dcr-mode=external', '--dcr-verify-url=https://auth0.example.com/verify'],\n * process.env,\n * 'https://www.googleapis.com/auth/drive.readonly'\n * );\n * ```\n */\nexport function parseDcrConfig(args: string[], env: Record<string, string | undefined>, scope: string): DcrConfig {\n function requiredEnv(key: string): string {\n const value = env[key];\n if (!value) {\n throw new Error(`Environment variable ${key} is required for DCR configuration`);\n }\n return value;\n }\n\n const { values } = parseArgs({\n args,\n options: {\n 'dcr-mode': { type: 'string' },\n 'dcr-verify-url': { type: 'string' },\n 'dcr-store-uri': { type: 'string' },\n },\n strict: false,\n allowPositionals: true,\n });\n\n const cliMode = typeof values['dcr-mode'] === 'string' ? values['dcr-mode'] : undefined;\n const envMode = env.DCR_MODE;\n const mode = cliMode || envMode || 'self-hosted';\n\n if (mode !== 'self-hosted' && mode !== 'external') {\n throw new Error(`Invalid --dcr-mode value: \"${mode}\". Valid values: self-hosted, external`);\n }\n\n const cliVerifyUrl = typeof values['dcr-verify-url'] === 'string' ? values['dcr-verify-url'] : undefined;\n const envVerifyUrl = env.DCR_VERIFY_URL;\n const verifyUrl = cliVerifyUrl || envVerifyUrl;\n\n const cliStoreUri = typeof values['dcr-store-uri'] === 'string' ? values['dcr-store-uri'] : undefined;\n const envStoreUri = env.DCR_STORE_URI;\n const storeUri = cliStoreUri || envStoreUri;\n\n if (mode === 'external' && !verifyUrl) {\n throw new Error('DCR external mode requires --dcr-verify-url or DCR_VERIFY_URL environment variable');\n }\n\n const clientId = requiredEnv('GOOGLE_CLIENT_ID');\n const clientSecret = env.GOOGLE_CLIENT_SECRET;\n\n return {\n mode,\n ...(verifyUrl && { verifyUrl }),\n ...(storeUri && { storeUri }),\n clientId,\n ...(clientSecret && { clientSecret }),\n scope,\n };\n}\n"],"names":["resolve","parseArgs","parseAuthMode","value","Error","auth","parseConfig","args","env","transport","cliHeadless","requiredEnv","key","values","options","type","headless","strict","allowPositionals","authArg","undefined","envAuthMode","AUTH_MODE","mode","parsed","cliRedirectUri","envRedirectUri","REDIRECT_URI","redirectUri","envHeadless","HEADLESS","clientId","clientSecret","GOOGLE_CLIENT_SECRET","serviceAccountKeyFile","cliKeyFile","GOOGLE_SERVICE_ACCOUNT_KEY_FILE","createConfig","process","argv","parseDcrConfig","scope","cliMode","envMode","DCR_MODE","cliVerifyUrl","envVerifyUrl","DCR_VERIFY_URL","verifyUrl","cliStoreUri","envStoreUri","DCR_STORE_URI","storeUri"],"mappings":"AAAA;;;;;;CAMC,GAED,SAASA,OAAO,QAAQ,OAAO;AAC/B,SAASC,SAAS,QAAQ,OAAO;AAWjC;;;;;;;;;;;;;CAaC,GACD,SAASC,cAAcC,KAAa;IAGlC,IAAIA,UAAU,oBAAoBA,UAAU,qBAAqBA,UAAU,OAAO;QAChF,MAAM,IAAIC,MAAM,CAAC,uBAAuB,EAAED,MAAM,qDAAqD,CAAC;IACxG;IAEA,OAAO;QACLE,MAAMF;IACR;AACF;AAWA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAkDC,GACD,OAAO,SAASG,YAAYC,IAAc,EAAEC,GAAuC,EAAEC,SAAyB;QAkD3FC;IAjDjB,SAASC,YAAYC,GAAW;QAC9B,MAAMT,QAAQK,GAAG,CAACI,IAAI;QACtB,IAAI,CAACT,OAAO;YACV,MAAM,IAAIC,MAAM,CAAC,qBAAqB,EAAEQ,IAAI,6BAA6B,CAAC;QAC5E;QACA,OAAOT;IACT;IAEA,sBAAsB;IACtB,MAAM,EAAEU,MAAM,EAAE,GAAGZ,UAAU;QAC3BM;QACAO,SAAS;YACPT,MAAM;gBAAEU,MAAM;YAAS;YACvBC,UAAU;gBAAED,MAAM;YAAU;YAC5B,gBAAgB;gBAAEA,MAAM;YAAS;YACjC,4BAA4B;gBAAEA,MAAM;YAAS;QAC/C;QACAE,QAAQ;QACRC,kBAAkB;IACpB;IAEA,MAAMC,UAAU,OAAON,OAAOR,IAAI,KAAK,WAAWQ,OAAOR,IAAI,GAAGe;IAChE,MAAMC,cAAcb,IAAIc,SAAS;IACjC,MAAMC,OAAOJ,WAAWE;IAExB,IAAIhB;IAEJ,IAAIkB,MAAM;QACR,MAAMC,SAAStB,cAAcqB;QAC7BlB,OAAOmB,OAAOnB,IAAI;IACpB,OAAO;QACL,iDAAiD;QACjDA,OAAO;IACT;IAEA,+CAA+C;IAC/C,IAAIA,SAAS,SAASI,cAAc,SAAS;QAC3C,MAAM,IAAIL,MAAM;IAClB;IAEA,MAAMqB,iBAAiB,OAAOZ,MAAM,CAAC,eAAe,KAAK,WAAWA,MAAM,CAAC,eAAe,GAAGO;IAC7F,MAAMM,iBAAiBlB,IAAImB,YAAY;IACvC,MAAMC,cAAcH,2BAAAA,4BAAAA,iBAAkBC;IACtC,IAAIE,eAAenB,cAAc,SAAS;QACxC,MAAM,IAAIL,MAAM;IAClB;IAEA,MAAMM,cAAc,OAAOG,OAAOG,QAAQ,KAAK,YAAYH,OAAOG,QAAQ,GAAGI;IAC7E,MAAMS,cAAcrB,IAAIsB,QAAQ,KAAK,SAAS,OAAOtB,IAAIsB,QAAQ,KAAK,UAAU,QAAQV;IACxF,MAAMJ,YAAWN,OAAAA,wBAAAA,yBAAAA,cAAemB,yBAAfnB,kBAAAA,OAA8BkB,gBAAgBR,WAAW,+GAA+G;IAEzL,MAAMW,WAAWpB,YAAY;IAC7B,MAAMqB,eAAexB,IAAIyB,oBAAoB;IAE7C,IAAIC;IACJ,IAAI7B,SAAS,mBAAmB;QAC9B,MAAM8B,aAAa,OAAOtB,MAAM,CAAC,2BAA2B,KAAK,WAAWA,MAAM,CAAC,2BAA2B,GAAGO;QACjHc,wBAAwBC,uBAAAA,wBAAAA,aAAc3B,IAAI4B,+BAA+B;QAEzE,IAAI,CAACF,uBAAuB;YAC1B,MAAM,IAAI9B,MAAM,iHAAiH;QACnI;QAEA,mEAAmE;QACnE8B,wBAAwBlC,QAAQkC;IAClC;IAEA,OAAO;QACLH;QACA,GAAIC,gBAAgB;YAAEA;QAAa,CAAC;QACpC3B;QACAW;QACA,GAAIY,eAAe;YAAEA;QAAY,CAAC;QAClC,GAAIM,yBAAyB;YAAEA;QAAsB,CAAC;IACxD;AACF;AAEA;;;CAGC,GACD,OAAO,SAASG;IACd,OAAO/B,YAAYgC,QAAQC,IAAI,EAAED,QAAQ9B,GAAG;AAC9C;AAEA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAyCC,GACD,OAAO,SAASgC,eAAejC,IAAc,EAAEC,GAAuC,EAAEiC,KAAa;IACnG,SAAS9B,YAAYC,GAAW;QAC9B,MAAMT,QAAQK,GAAG,CAACI,IAAI;QACtB,IAAI,CAACT,OAAO;YACV,MAAM,IAAIC,MAAM,CAAC,qBAAqB,EAAEQ,IAAI,kCAAkC,CAAC;QACjF;QACA,OAAOT;IACT;IAEA,MAAM,EAAEU,MAAM,EAAE,GAAGZ,UAAU;QAC3BM;QACAO,SAAS;YACP,YAAY;gBAAEC,MAAM;YAAS;YAC7B,kBAAkB;gBAAEA,MAAM;YAAS;YACnC,iBAAiB;gBAAEA,MAAM;YAAS;QACpC;QACAE,QAAQ;QACRC,kBAAkB;IACpB;IAEA,MAAMwB,UAAU,OAAO7B,MAAM,CAAC,WAAW,KAAK,WAAWA,MAAM,CAAC,WAAW,GAAGO;IAC9E,MAAMuB,UAAUnC,IAAIoC,QAAQ;IAC5B,MAAMrB,OAAOmB,WAAWC,WAAW;IAEnC,IAAIpB,SAAS,iBAAiBA,SAAS,YAAY;QACjD,MAAM,IAAInB,MAAM,CAAC,2BAA2B,EAAEmB,KAAK,sCAAsC,CAAC;IAC5F;IAEA,MAAMsB,eAAe,OAAOhC,MAAM,CAAC,iBAAiB,KAAK,WAAWA,MAAM,CAAC,iBAAiB,GAAGO;IAC/F,MAAM0B,eAAetC,IAAIuC,cAAc;IACvC,MAAMC,YAAYH,gBAAgBC;IAElC,MAAMG,cAAc,OAAOpC,MAAM,CAAC,gBAAgB,KAAK,WAAWA,MAAM,CAAC,gBAAgB,GAAGO;IAC5F,MAAM8B,cAAc1C,IAAI2C,aAAa;IACrC,MAAMC,WAAWH,eAAeC;IAEhC,IAAI3B,SAAS,cAAc,CAACyB,WAAW;QACrC,MAAM,IAAI5C,MAAM;IAClB;IAEA,MAAM2B,WAAWpB,YAAY;IAC7B,MAAMqB,eAAexB,IAAIyB,oBAAoB;IAE7C,OAAO;QACLV;QACA,GAAIyB,aAAa;YAAEA;QAAU,CAAC;QAC9B,GAAII,YAAY;YAAEA;QAAS,CAAC;QAC5BrB;QACA,GAAIC,gBAAgB;YAAEA;QAAa,CAAC;QACpCS;IACF;AACF"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mcp-z/oauth-google",
3
- "version": "1.0.1",
3
+ "version": "1.0.2",
4
4
  "description": "OAuth 2.0 client for Google APIs with multi-account support, PKCE security, and swappable storage backends",
5
5
  "keywords": [
6
6
  "oauth2",