rhombus-node-mcp
Version:
MCP server for Rhombus API
130 lines (129 loc) • 5 kB
JavaScript
import { logger } from "../logger.js";
import { requestAuthContext } from "../auth-context.js";
export const RHOMBUS_API_KEY = process.env.RHOMBUS_API_KEY;
export const serverUrl = process.env.RHOMBUS_API_SERVER || "api2.rhombussystems.com";
export const BASE_URL = `https://${serverUrl}/api`;
export const STATIC_HEADERS = {
"Content-Type": "application/json",
"x-rhombus-agent": "chatbot",
accept: "application/json",
};
export const AUTH_HEADERS = {
"x-auth-apikey": RHOMBUS_API_KEY ?? "",
"x-auth-scheme": "api-token",
};
export const appendQueryParams = (url, params) => {
if (!params || typeof params !== "object")
return url;
const urlObj = new URL(url);
const existingSearchParams = new URLSearchParams(urlObj.search);
for (const [key, value] of Object.entries(params)) {
if (value !== undefined && value !== null) {
existingSearchParams.append(key, String(value));
}
}
const baseUrl = url.split("?")[0];
const queryString = existingSearchParams.toString();
return queryString ? `${baseUrl}?${queryString}` : baseUrl;
};
export function constructRequestHeaders(url, modifiers, sessionId // kept for API compatibility; ignored — always uses AsyncLocalStorage
) {
// construct auth headers from async context (stateless: set per-request by the transport handler)
let authHeaders = {};
const contextAuth = requestAuthContext.getStore();
if (contextAuth) {
if ("oauthBearer" in contextAuth) {
// The Bearer is an opaque Rhombus access token issued by the Rhombus
// OAuth 2.1 authorization server. api2 validates it directly.
authHeaders = {
"x-auth-access-token": contextAuth.oauthBearer,
"x-auth-scheme": "api-oauth-token",
};
}
else if ("apiKey" in contextAuth) {
authHeaders = {
"x-auth-apikey": contextAuth.apiKey,
"x-auth-scheme": "api-token",
};
}
else if ("sessionId" in contextAuth) {
authHeaders = {
"x-auth-session": contextAuth.sessionId,
"x-auth-chat": contextAuth.latestRecordUuid,
"x-auth-scheme": "chatbot",
};
url = appendQueryParams(url, { _rs: contextAuth.sessionId });
}
else if ("cookie" in contextAuth) {
authHeaders = {
"x-auth-scheme": "web2",
cookie: contextAuth.cookie,
};
if (contextAuth.sessionAlias) {
url = appendQueryParams(url, { _rs: contextAuth.sessionAlias });
}
}
}
else {
// no async context — fall back to env API key (local dev / stdio)
authHeaders = AUTH_HEADERS;
}
// merge headers
const requestHeaders = {
...STATIC_HEADERS,
...authHeaders,
...(modifiers?.headers ?? {}),
};
if (modifiers?.query) {
url = appendQueryParams(url, modifiers.query);
}
return { url, requestHeaders };
}
export async function postApi({ route, body, modifiers, sessionId, }) {
let url = BASE_URL + route;
const { url: newUrl, requestHeaders } = constructRequestHeaders(BASE_URL + route, modifiers, sessionId);
url = newUrl;
// stringify body if it's not already a string
if (typeof body === "object") {
body = JSON.stringify(body);
}
try {
logger.info(`[POSTAPI] REQUEST - ${url} - ${body} - ${JSON.stringify(requestHeaders)}`);
const response = await fetch(url, {
method: "POST",
headers: requestHeaders,
body,
});
if (!response.ok) {
logger.debug(`❌ RESPONSE - ${response.ok} - ${response.status}`);
if (response.status === 401 || response.status === 403) {
return {
error: true,
status: "Sorry, I don't have permission to help with this request. Consider upgrading my permissions by changing the role of the API Key I am using.",
};
}
throw {
body: JSON.parse(body),
error: await response.text(),
};
// throw new Error(`HTTP error! status: ${response.status}`);
}
const ret = await response.json();
const jsonStr = JSON.stringify(ret);
const truncatedJson = jsonStr.length > 150 ? jsonStr.substring(0, 150) + "..." : jsonStr;
logger.debug(`✅ RESPONSE - ${response.ok} - ${truncatedJson}`);
return ret;
}
catch (error) {
logger.error(`[POSTAPI] ERROR - ${JSON.stringify(error || {}, null, 4)}`);
return {
error: true,
status: `Request Error: ${JSON.stringify(error)}`,
};
}
}
export function throwIfApiError(res) {
if (res.error) {
throw new Error(res.status ?? "API request failed.");
}
}