UNPKG

@civic/hub-bridge

Version:

Stdio <-> HTTP/SSE MCP bridge with Civic auth handling

87 lines 3.7 kB
import { AbstractHook } from "@civic/hook-common"; import { ServiceAuthorizationHandler } from "../auth/service-authorization.js"; import { logger } from "../utils/logger.js"; /** * AuthenticationHook listens for special authentication messages and triggers * authentication flows when needed. * * This hook processes incoming tool calls and responses to detect when * authentication is required and handles the authentication flow. */ export class AuthenticationHook extends AbstractHook { authProvider; continueJobCallback = null; constructor(authProvider) { super(); this.authProvider = authProvider; } /** * Set the callback for continuing jobs. This should be called after the proxy * is created but before any authentication flows are triggered. * @param callback The callback function to use for continuing jobs */ setContinueJobCallback(callback) { this.continueJobCallback = callback; } get name() { return "AuthenticationHook"; } /** * Check if a response is an authorization required response */ isAuthorizationRequiredResponse(result) { return (result.content?.length === 3 && result.content[0].type === 'text' && result.content[1].type === 'resource' && result.content[2].type === 'resource' && result.content[1].resource.name === 'authorization_url' && result.content[2].resource.name === 'continue_job_id'); } /** * Detect if a response contains service authorization flow information */ detectServiceAuthorizationFlow(result) { logger.debug("Checking for service authorization flow in response"); if (!this.isAuthorizationRequiredResponse(result)) { return null; } logger.info("Service authorization flow detected"); const authUrl = result.content[1].resource.text; const continueJobId = result.content[2].resource.text; logger.debug(`Authorization URL: ${authUrl}`); logger.debug(`Continue job ID: ${continueJobId}`); return { authUrl, continueJobId }; } async processCallToolResult(result, // eslint-disable-next-line @typescript-eslint/no-unused-vars _originalCallToolRequest, // eslint-disable-next-line @typescript-eslint/no-unused-vars _originalRequestExtra) { try { // Check if this response requires auth const authInfo = this.detectServiceAuthorizationFlow(result); // If no auth required, just return the original response if (!authInfo) { return { resultType: "continue", response: result }; } // Ensure we have a callback set if (!this.continueJobCallback) { logger.error("AuthenticationHook: continueJobCallback not set, cannot handle service authorization"); return { resultType: "continue", response: result }; } // Create auth handler and process the response const authHandler = new ServiceAuthorizationHandler(this.continueJobCallback); const processedResponse = await authHandler.handleServiceAuthorization(result); return { resultType: "continue", response: processedResponse }; } catch (error) { logger.error("Error in AuthenticationHook.processResponse:", error); // Continue with original response on error to avoid breaking the flow return { resultType: "continue", response: result }; } } } //# sourceMappingURL=authentication-hook.js.map