@civic/hub-bridge
Version:
Stdio <-> HTTP/SSE MCP bridge with Civic auth handling
87 lines • 3.7 kB
JavaScript
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