Files
agenticSystem/mcp-server/auth/apiClient.js
2026-04-01 23:16:45 +01:00

143 lines
5.1 KiB
JavaScript

import axios from "axios";
import { McpError, ErrorCode } from "@modelcontextprotocol/sdk/types.js";
import { sessionApiClients, getSessionCredentials, setCredentials, findRoleByToken } from "./credentials.js";
import { assertSafeCmsTarget } from "../utils/cmsTargetSafety.js";
const DEFAULT_ROLE = 'developer';
/**
* Check if session is configured with valid credentials
*/
export const ensureConfigured = async (sessionId) => {
const creds = await getSessionCredentials(sessionId);
if (!creds.token || !creds.web_url || !creds.api_web_url) {
throw new McpError(
ErrorCode.InvalidRequest,
"Acai site not configured for safe local execution. Use the project MCP config/select_project flow so ACAI_API_WEB_URL points to the local environment."
);
}
try {
assertSafeCmsTarget(creds, "apiClient");
} catch (error) {
throw new McpError(
ErrorCode.InvalidRequest,
error.message
);
}
};
/**
* Rebuild API client for a session
*/
export const rebuildApiClient = async (sessionId) => {
const creds = await getSessionCredentials(sessionId);
if (!creds.token || !creds.web_url || !creds.api_web_url) {
return null;
}
assertSafeCmsTarget(creds, "apiClient");
const client = axios.create({
baseURL: creds.api_web_url,
headers: {
"Content-Type": "application/json",
"X-Acai-Token": creds.token,
...(creds.forge_host ? { Host: creds.forge_host } : {}),
},
});
// Request interceptor: always send latest token
client.interceptors.request.use((config) => {
if (creds.token) {
config.headers["X-Acai-Token"] = creds.token;
}
return config;
});
sessionApiClients.set(sessionId, client);
return client;
};
/**
* Get or create API client for a session
* @param {string} sessionId - The session ID
*/
export const getApiClient = async (sessionId) => {
const creds = await getSessionCredentials(sessionId);
console.error(`[API Client] getApiClient called for session ${sessionId}`);
console.error(`[API Client] Current creds: token=${!!creds.token}, web_url=${creds.web_url}`);
await ensureConfigured(sessionId);
let client = sessionApiClients.get(sessionId);
if (!client) {
console.error(`[API Client] No cached client, rebuilding...`);
client = await rebuildApiClient(sessionId);
}
if (!client) {
throw new McpError(
ErrorCode.InvalidRequest,
"Unable to create API client. Verify credentials and try again."
);
}
console.error(`[API Client] Returning client for session ${sessionId}`);
return client;
};
/**
* Set credentials and rebuild API client
* @param {Object} credentials - The credentials object
* @param {string} sessionId - The session ID
* @param {string} mcpSessionId - Optional MCP-Session-Id for persistence across SSE reconnections
*/
export const setCredentialsAndRebuild = async (credentials, sessionId, mcpSessionId = null) => {
await setCredentials(credentials, sessionId, mcpSessionId);
await rebuildApiClient(sessionId);
};
/**
* Wrapper for authenticated handlers
* Supports both session-based auth and inline credentials (stateless mode).
*
* If args contains acaiToken + acaiWebsite, these are used directly,
* allowing Claude to send credentials with each request.
*/
export const withAuth = (handler) => {
return async (args, extra) => {
const sessionId = extra?.sessionId || "_default";
console.error(`[withAuth] Called with sessionId: ${sessionId}`);
// Check for inline credentials (stateless mode)
const inlineCredentials = {
acaiToken: args.acaiToken,
acaiWebsite: args.acaiWebsite,
acaiTokenHash: args.acaiTokenHash
};
const hasInlineCredentials = inlineCredentials.acaiToken && inlineCredentials.acaiWebsite;
if (hasInlineCredentials) {
// Lookup role by token before storing credentials
const role = findRoleByToken(inlineCredentials.acaiToken) || DEFAULT_ROLE;
console.error(`[withAuth] Using INLINE credentials: website=${inlineCredentials.acaiWebsite}, role=${role}`);
// Temporarily store inline credentials in session for this request
await setCredentials({
token: inlineCredentials.acaiToken,
website: inlineCredentials.acaiWebsite,
web_url: `https://${inlineCredentials.acaiWebsite}`,
api_web_url: null,
forge_host: null,
tokenHash: inlineCredentials.acaiTokenHash || null,
profileName: 'inline',
role: role
}, sessionId);
}
console.error(`[withAuth] Getting API client for session ${sessionId}...`);
await getApiClient(sessionId);
console.error(`[withAuth] API client ready, calling handler...`);
return handler(args, { ...extra, sessionId, inlineCredentials: hasInlineCredentials ? inlineCredentials : null });
};
};