UNPKG

@grebyn/toolflow-mcp-server

Version:

MCP server for managing other MCP servers - discover, install, organize into bundles, and automate with workflows. Uses StreamableHTTP transport with dual OAuth/API key authentication.

108 lines 4.18 kB
/** * Token Pass-through Handler * * This handler does NOT validate JWTs locally. * It simply extracts the token and passes it to the backend API * where validation happens with the actual JWT secret. */ export class TokenPassthrough { apiEndpoint; constructor(apiEndpoint) { this.apiEndpoint = apiEndpoint; } /** * Extract token from request and validate with backend API */ async extractToken(req) { const authHeader = req.headers.authorization; // No authorization header - anonymous request if (!authHeader || !authHeader.startsWith('Bearer ')) { return { isAuthenticated: false }; } const token = authHeader.substring(7); // Remove "Bearer " prefix // Check for common OAuth implementation bugs if (token.startsWith('ac_')) { return { isAuthenticated: false, error: 'Authorization code received instead of JWT token. Client must exchange the authorization code for an access token first.' }; } // Validate token with backend API to get full user context try { const response = await fetch(`${this.apiEndpoint}/proxy`, { method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${token}` }, body: JSON.stringify({ operation: 'validateOAuthToken', params: {} }) }); if (!response.ok) { const errorText = await response.text(); return { isAuthenticated: false, error: `Token validation failed: ${errorText}` }; } const result = await response.json(); if (result.is_valid) { return { isAuthenticated: true, token: token, userId: result.user_id, email: result.email, organizationId: result.organization_id, sessionId: result.session_id, tokenJti: result.token_jti }; } else { return { isAuthenticated: false, error: 'Invalid or expired OAuth token' }; } } catch (error) { return { isAuthenticated: false, error: `Token validation error: ${error.message}` }; } } /** * Send unauthorized response when API rejects the token */ sendUnauthorizedResponse(req, res, error) { const host = req.headers.host; const protocol = req.headers['x-forwarded-proto'] || 'http'; const baseUrl = `${protocol}://${host}`; const protectedResourceMetadataUrl = `${baseUrl}/.well-known/oauth-protected-resource`; const authorizationServerMetadataUrl = `${baseUrl}/.well-known/oauth-authorization-server`; const errorResponse = { error: 'unauthorized', error_description: error ? error.message : 'OAuth authentication required', resource: protectedResourceMetadataUrl }; // MCP-compliant WWW-Authenticate header format const wwwAuthenticate = [ 'Bearer', `realm="MCP Server"`, `resource_metadata_uri="${protectedResourceMetadataUrl}"`, `authorization_uri="${baseUrl}/api/oauth/authorize"`, `discovery_uri="${authorizationServerMetadataUrl}"` ].join(', '); res.writeHead(401, { 'WWW-Authenticate': wwwAuthenticate, 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*', 'Access-Control-Allow-Methods': 'GET, POST, OPTIONS', 'Access-Control-Allow-Headers': 'Content-Type, Authorization, mcp-protocol-version', }); res.end(JSON.stringify(errorResponse)); } } //# sourceMappingURL=token-passthrough.js.map