diff --git a/server/mcp-core/auth-context-store.ts b/server/mcp-core/auth-context-store.ts index 4cead12..c871d16 100644 --- a/server/mcp-core/auth-context-store.ts +++ b/server/mcp-core/auth-context-store.ts @@ -28,6 +28,7 @@ export interface AuthContext { // Multi-provider tokens (nested structure per Q21, Q22) atlassian?: ProviderAuthInfo; figma?: ProviderAuthInfo; + google?: ProviderAuthInfo; // JWT metadata (preserved for compatibility) iss?: string; aud?: string; @@ -68,6 +69,7 @@ function getTokenLogInfo(token?: string, prefix: string = 'Token'): Record now; + const hasValidGoogleToken = authInfo.google && + authInfo.google.expires_at > now; + // If at least one provider has a valid token, not expired - const hasAnyValidToken = hasValidAtlassianToken || hasValidFigmaToken; + const hasAnyValidToken = hasValidAtlassianToken || hasValidFigmaToken || hasValidGoogleToken; return !hasAnyValidToken; } diff --git a/server/mcp-core/server-factory.ts b/server/mcp-core/server-factory.ts index 12b589d..7652937 100644 --- a/server/mcp-core/server-factory.ts +++ b/server/mcp-core/server-factory.ts @@ -15,6 +15,7 @@ import type { AuthContext } from './auth-context-store.ts'; // Import provider implementations import { atlassianProvider } from '../providers/atlassian/index.js'; import { figmaProvider } from '../providers/figma/index.js'; +import { googleProvider } from '../providers/google/index.js'; import { utilityProvider } from '../providers/utility/index.js'; import { combinedProvider } from '../providers/combined/index.js'; @@ -79,6 +80,12 @@ export function createMcpServer(authContext: AuthContext): McpServer { registeredProviders.push('figma'); } + if (authContext.google) { + console.log(' Registering Google Drive tools'); + googleProvider.registerTools(mcp, authContext); + registeredProviders.push('google'); + } + // Register combined tools only if BOTH providers are available if (authContext.atlassian && authContext.figma) { console.log(' Registering combined tools (atlassian + figma)'); diff --git a/server/mcp-service.ts b/server/mcp-service.ts index 7d67fc3..35a30a6 100644 --- a/server/mcp-service.ts +++ b/server/mcp-service.ts @@ -327,8 +327,8 @@ function validateAndExtractJwt(token: string, req: Request, res: Response, sourc console.log(`Successfully parsed JWT payload from ${source}:`, JSON.stringify(sanitizeJwtPayload(payload), null, 2)); // Validate that we have at least one provider's credentials (nested structure per Q21) - if (!payload.atlassian && !payload.figma) { - console.log(`JWT payload missing provider credentials (${source}) - expected 'atlassian' or 'figma' nested structure`); + if (!payload.atlassian && !payload.figma && !payload.google) { + console.log(`JWT payload missing provider credentials (${source}) - expected 'atlassian', 'figma', or 'google' nested structure`); sendMissingAtlassianAccessToken(res, req, source); return { authInfo: null, errored: true }; } @@ -371,7 +371,7 @@ function send401(res: Response, jsonResponse: { error: string }, includeInvalidT } function sendMissingAtlassianAccessToken(res: Response, req: Request, where: string = 'bearer header'): Response { - const message = `Authentication token missing Atlassian access token in ${where}.`; + const message = `Authentication token missing provider access token in ${where}.`; console.log(`❌🔑 ${message}`); return res .status(401) diff --git a/server/pkce/refresh-token.ts b/server/pkce/refresh-token.ts index c7b42af..7ddb754 100644 --- a/server/pkce/refresh-token.ts +++ b/server/pkce/refresh-token.ts @@ -33,6 +33,7 @@ import { } from './token-helpers.ts'; import { atlassianProvider } from '../providers/atlassian/index.ts'; import { figmaProvider } from '../providers/figma/index.ts'; +import { googleProvider } from '../providers/google/index.ts'; import type { OAuthHandler, OAuthRequest, OAuthErrorResponse } from './types.ts'; /** @@ -107,10 +108,12 @@ export const refreshToken: OAuthHandler = async (req: Request, res: Response): P // Extract provider refresh tokens from nested structure const atlassianRefreshToken = refreshPayload.atlassian?.refresh_token; const figmaRefreshToken = refreshPayload.figma?.refresh_token; + const googleRefreshToken = refreshPayload.google?.refresh_token; // Refresh each provider using the provider interface let newAtlassianTokens: any = null; let newFigmaTokens: any = null; + let newGoogleTokens: any = null; try { // Refresh Atlassian if present @@ -127,8 +130,15 @@ export const refreshToken: OAuthHandler = async (req: Request, res: Response): P }); } + // Refresh Google if present + if (googleRefreshToken) { + newGoogleTokens = await googleProvider.refreshAccessToken!({ + refreshToken: googleRefreshToken, + }); + } + // Check if we refreshed any provider - if (!newAtlassianTokens && !newFigmaTokens) { + if (!newAtlassianTokens && !newFigmaTokens && !newGoogleTokens) { console.error( '🔄 REFRESH TOKEN FLOW - ERROR: No provider refresh tokens found in JWT' ); @@ -165,6 +175,7 @@ export const refreshToken: OAuthHandler = async (req: Request, res: Response): P const multiProviderTokens: MultiProviderTokens = {}; addProviderTokens(multiProviderTokens, 'atlassian', newAtlassianTokens); addProviderTokens(multiProviderTokens, 'figma', newFigmaTokens); + addProviderTokens(multiProviderTokens, 'google', newGoogleTokens); // Create new access token with refreshed provider tokens const newAccessToken = await createMultiProviderAccessToken( @@ -204,6 +215,7 @@ export const refreshToken: OAuthHandler = async (req: Request, res: Response): P providers_refreshed: [ newAtlassianTokens && 'atlassian', newFigmaTokens && 'figma', + newGoogleTokens && 'google', ].filter(Boolean), expires_in: expiresIn, }); diff --git a/server/pkce/token-helpers.ts b/server/pkce/token-helpers.ts index 8a92316..bc266a0 100644 --- a/server/pkce/token-helpers.ts +++ b/server/pkce/token-helpers.ts @@ -46,6 +46,7 @@ export interface ProviderTokenData { export interface MultiProviderTokens { atlassian?: ProviderTokenData; figma?: ProviderTokenData; + google?: ProviderTokenData; } // Extended interface to handle optional refresh token expiration @@ -58,12 +59,12 @@ export interface ExtendedAtlassianTokenResponse extends AtlassianTokenResponse { * Mutates the target object by adding provider token data with calculated expiration * * @param target - The MultiProviderTokens object to mutate - * @param providerKey - The provider key ('atlassian' or 'figma') + * @param providerKey - The provider key ('atlassian', 'figma', or 'google') * @param tokens - Provider token response containing access_token, refresh_token, expires_in, scope */ export function addProviderTokens( target: MultiProviderTokens, - providerKey: 'atlassian' | 'figma', + providerKey: 'atlassian' | 'figma' | 'google', tokens: any ): void { if (tokens) { diff --git a/server/provider-server-oauth/connection-done.ts b/server/provider-server-oauth/connection-done.ts index cc62e2f..c1c6774 100644 --- a/server/provider-server-oauth/connection-done.ts +++ b/server/provider-server-oauth/connection-done.ts @@ -61,8 +61,9 @@ export async function handleConnectionDone(req: Request, res: Response): Promise // Build multi-provider tokens structure for JWT creation const atlassianTokens = providerTokens['atlassian']; const figmaTokens = providerTokens['figma']; + const googleTokens = providerTokens['google']; - if (!atlassianTokens && !figmaTokens) { + if (!atlassianTokens && !figmaTokens && !googleTokens) { throw new Error('No provider tokens found - please connect at least one service'); } @@ -93,6 +94,18 @@ export async function handleConnectionDone(req: Request, res: Response): Promise console.log(' Warning: Figma tokens incomplete (missing access or refresh token)'); } + if (googleTokens && googleTokens.access_token && googleTokens.refresh_token) { + console.log(' Adding Google credentials to JWT'); + multiProviderTokens.google = { + access_token: googleTokens.access_token, + refresh_token: googleTokens.refresh_token, + expires_at: googleTokens.expires_at, + scope: googleTokens.scope, + }; + } else if (googleTokens) { + console.log(' Warning: Google tokens incomplete (missing access or refresh token)'); + } + // Create JWT access token with nested provider structure const tokenOptions = { resource: req.session.mcpResource || process.env.VITE_AUTH_SERVER_URL, diff --git a/server/provider-server-oauth/connection-hub.ts b/server/provider-server-oauth/connection-hub.ts index 5eaba2d..00ca55e 100644 --- a/server/provider-server-oauth/connection-hub.ts +++ b/server/provider-server-oauth/connection-hub.ts @@ -23,7 +23,7 @@ import crypto from 'crypto'; // All providers that must be connected for auto-redirect to /auth/done // If only some providers are connected, user can manually click "Done" button -const REQUIRED_PROVIDERS = ['atlassian', 'figma'] as const; +const REQUIRED_PROVIDERS = ['atlassian', 'figma', 'google'] as const; /** * Renders the connection hub UI @@ -185,6 +185,15 @@ export function renderConnectionHub(req: Request, res: Response): void { } +
+

Google Drive

+

Access Google Drive files

+ ${connectedProviders.includes('google') + ? '✓ Connected' + : '' + } +
+