ragatanga-mcp-sdk
Version:
SDK for integrating with the Ragatanga Management Control Plane (MCP) with Next.js 15, React 19, WebSocket and Arrow IPC support
1,483 lines (1,472 loc) • 107 kB
JavaScript
'use strict';
var react = require('react');
var zod = require('zod');
var jsxRuntime = require('react/jsx-runtime');
var useSWR = require('swr');
function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
var useSWR__default = /*#__PURE__*/_interopDefault(useSWR);
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
var __esm = (fn, res) => function __init() {
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
};
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
// src/errors/ontology-errors.ts
var init_ontology_errors = __esm({
"src/errors/ontology-errors.ts"() {
init_errors();
}
});
// src/errors/index.ts
function createHttpError(response) {
const status = response.status;
let message = `HTTP Error ${status}`;
let code = `HTTP_${status}`;
if (status === 400) {
message = "Bad Request: The request was invalid";
return new HTTPError(message, response);
} else if (status === 401) {
message = "Unauthorized: Authentication is required";
return new AuthError(message, { status, code, response });
} else if (status === 403) {
message = "Forbidden: You don't have permission to access this resource";
return new AuthError(message, { status, code, response });
} else if (status === 404) {
message = "Not Found: The requested resource does not exist";
return new HTTPError(message, response);
} else if (status === 429) {
message = "Too Many Requests: Rate limit exceeded";
return new HTTPError(message, response);
} else if (status >= 500) {
message = "Server Error: Something went wrong on the server";
return new HTTPError(message, response);
}
return new HTTPError(message, response);
}
var _MCPError, MCPError, _NetworkError, NetworkError, _TimeoutError, TimeoutError, _HTTPError, HTTPError, _AuthError, AuthError, _WebSocketError, WebSocketError, _ParseError, ParseError, _ValidationError, ValidationError;
var init_errors = __esm({
"src/errors/index.ts"() {
init_ontology_errors();
_MCPError = class _MCPError extends Error {
constructor(message, options) {
super(message);
this.name = "MCPError";
this.status = options?.status;
this.code = options?.code;
this.cause = options?.cause;
Object.setPrototypeOf(this, _MCPError.prototype);
}
/**
* Creates a formatted error message with all available details
*/
toFormattedString() {
const parts = [
`[${this.name}] ${this.message}`,
this.code ? `Code: ${this.code}` : null,
this.status ? `Status: ${this.status}` : null,
this.cause ? `Cause: ${this.cause instanceof Error ? this.cause.message : String(this.cause)}` : null
].filter(Boolean);
return parts.join(" | ");
}
};
__name(_MCPError, "MCPError");
MCPError = _MCPError;
_NetworkError = class _NetworkError extends MCPError {
constructor(message, options) {
super(message, { status: 0, code: options?.code || "NETWORK_ERROR", cause: options?.cause });
this.name = "NetworkError";
Object.setPrototypeOf(this, _NetworkError.prototype);
}
};
__name(_NetworkError, "NetworkError");
NetworkError = _NetworkError;
_TimeoutError = class _TimeoutError extends MCPError {
constructor(message = "Request timed out", options) {
super(message, { status: 0, code: options?.code || "TIMEOUT", cause: options?.cause });
this.name = "TimeoutError";
Object.setPrototypeOf(this, _TimeoutError.prototype);
}
};
__name(_TimeoutError, "TimeoutError");
TimeoutError = _TimeoutError;
_HTTPError = class _HTTPError extends MCPError {
constructor(message, response, options) {
super(message, {
status: response.status,
code: options?.code || `HTTP_${response.status}`,
cause: options?.cause
});
this.name = "HTTPError";
this.response = response;
Object.setPrototypeOf(this, _HTTPError.prototype);
}
};
__name(_HTTPError, "HTTPError");
HTTPError = _HTTPError;
_AuthError = class _AuthError extends MCPError {
constructor(message, options) {
super(message, {
status: options?.status || 401,
code: options?.code || "AUTH_ERROR",
cause: options?.cause
});
this.name = "AuthError";
this.response = options?.response;
Object.setPrototypeOf(this, _AuthError.prototype);
}
};
__name(_AuthError, "AuthError");
AuthError = _AuthError;
_WebSocketError = class _WebSocketError extends MCPError {
constructor(message, options) {
super(message, {
code: options?.code || "WEBSOCKET_ERROR",
cause: options?.cause
});
this.name = "WebSocketError";
this.event = options?.event;
Object.setPrototypeOf(this, _WebSocketError.prototype);
}
};
__name(_WebSocketError, "WebSocketError");
WebSocketError = _WebSocketError;
_ParseError = class _ParseError extends MCPError {
constructor(message, options) {
super(message, {
code: options?.code || "PARSE_ERROR",
cause: options?.cause
});
this.name = "ParseError";
this.data = options?.data;
Object.setPrototypeOf(this, _ParseError.prototype);
}
};
__name(_ParseError, "ParseError");
ParseError = _ParseError;
_ValidationError = class _ValidationError extends MCPError {
constructor(message, options) {
super(message, {
code: options?.code || "VALIDATION_ERROR",
cause: options?.cause
});
this.name = "ValidationError";
this.errors = options?.errors;
Object.setPrototypeOf(this, _ValidationError.prototype);
}
};
__name(_ValidationError, "ValidationError");
ValidationError = _ValidationError;
__name(createHttpError, "createHttpError");
}
});
// src/client/paths.ts
function buildPath(pathTemplate, params) {
if (!params) return pathTemplate;
let result = pathTemplate;
for (const [key, value] of Object.entries(params)) {
result = result.replace(`:${key}`, encodeURIComponent(String(value)));
}
return result;
}
function createFullUrl(baseUrl, path) {
const normalizedBaseUrl = baseUrl.endsWith("/") ? baseUrl.slice(0, -1) : baseUrl;
const normalizedPath = path.startsWith("/") ? path : `/${path}`;
return `${normalizedBaseUrl}${normalizedPath}`;
}
var ApiEndpoints;
var init_paths = __esm({
"src/client/paths.ts"() {
ApiEndpoints = {
// Ontology endpoints
Ontologies: "/ontologies",
OntologyById: "/ontologies/:id",
// User endpoints
CurrentUser: "/users/me",
UserById: "/users/:id",
// Authentication endpoints
Login: "/auth/login",
Logout: "/auth/logout",
RefreshToken: "/auth/refresh",
// Tenant endpoints
CurrentTenant: "/tenants/current",
TenantById: "/tenants/:id",
// MCP context endpoints
ResolveContext: "/resolve",
WebSocketResolve: "/resolve_ws",
// Tools endpoints
ToolsList: "/tools/list",
ToolsExecute: "/tools/execute",
ToolsStream: "/tools/stream",
// JSON-RPC endpoint
JsonRPC: "/mcp"
};
__name(buildPath, "buildPath");
__name(createFullUrl, "createFullUrl");
}
});
// src/client/arrow-ipc.ts
var arrow_ipc_exports = {};
__export(arrow_ipc_exports, {
ArrowConnectionStatus: () => ArrowConnectionStatus,
MCPArrowClient: () => MCPArrowClient,
createArrowClient: () => createArrowClient,
default: () => arrow_ipc_default
});
function createArrowClient(options) {
return new MCPArrowClient(options);
}
var ArrowConnectionStatus, _MCPArrowClient, MCPArrowClient, arrow_ipc_default;
var init_arrow_ipc = __esm({
"src/client/arrow-ipc.ts"() {
init_errors();
init_paths();
ArrowConnectionStatus = /* @__PURE__ */ ((ArrowConnectionStatus2) => {
ArrowConnectionStatus2[ArrowConnectionStatus2["DISCONNECTED"] = 0] = "DISCONNECTED";
ArrowConnectionStatus2[ArrowConnectionStatus2["CONNECTING"] = 1] = "CONNECTING";
ArrowConnectionStatus2[ArrowConnectionStatus2["CONNECTED"] = 2] = "CONNECTED";
ArrowConnectionStatus2[ArrowConnectionStatus2["CLOSING"] = 3] = "CLOSING";
ArrowConnectionStatus2[ArrowConnectionStatus2["ERROR"] = 4] = "ERROR";
ArrowConnectionStatus2[ArrowConnectionStatus2["RECONNECTING"] = 5] = "RECONNECTING";
return ArrowConnectionStatus2;
})(ArrowConnectionStatus || {});
_MCPArrowClient = class _MCPArrowClient {
/**
* Creates a new Arrow IPC client
*/
constructor(options) {
this.ws = null;
this.status = 0 /* DISCONNECTED */;
this.handlers = {};
this.messageQueue = [];
this.pendingRequests = /* @__PURE__ */ new Map();
this.defaultTimeout = 3e4;
this.reconnectAttempts = 0;
this.maxReconnectAttempts = 5;
this.reconnectDelay = 1e3;
this.autoReconnect = true;
this.connectionPath = "/arrow";
this.connectionRetryTimer = null;
this.manualClose = false;
this.lastConnectionTime = 0;
this.nextMessageId = 1;
// Heartbeat properties
this.heartbeatInterval = 15e3;
// 15 seconds
this.heartbeatTimer = null;
this.missedHeartbeats = 0;
this.maxMissedHeartbeats = 3;
this.lastHeartbeatResponse = 0;
/**
* Event listener map for custom events
*/
this.eventListeners = /* @__PURE__ */ new Map();
let wsBaseUrl = options.baseUrl || "https://api.mcp.ai";
wsBaseUrl = wsBaseUrl.replace(/^http/, "ws");
this.baseUrl = wsBaseUrl;
this.apiKey = options.apiKey;
this.token = options.token;
this.tenantId = options.tenantId;
this.autoReconnect = options.autoReconnect !== false;
if (options.maxReconnectAttempts !== void 0) {
this.maxReconnectAttempts = options.maxReconnectAttempts;
}
if (options.defaultTimeout) {
this.defaultTimeout = options.defaultTimeout;
}
if (options.streamTimeout) {
this.streamTimeout = options.streamTimeout;
}
}
/**
* Add an event listener for a specific event type
*/
addEventListener(event, handler) {
if (!this.eventListeners.has(event)) {
this.eventListeners.set(event, /* @__PURE__ */ new Set());
}
this.eventListeners.get(event)?.add(handler);
}
/**
* Remove an event listener
*/
removeEventListener(event, handler) {
if (this.eventListeners.has(event)) {
this.eventListeners.get(event)?.delete(handler);
}
}
/**
* Trigger event handlers for a specific event
*/
triggerEvent(event, message) {
if (this.eventListeners.has(event)) {
for (const handler of this.eventListeners.get(event) || []) {
try {
handler(message);
} catch (error) {
console.error(`Error in ${event} event handler:`, error);
}
}
}
}
/**
* Connect to the Arrow IPC WebSocket endpoint
*/
connect(path = "/arrow", handlers = {}) {
if (this.ws && this.ws.readyState === WebSocket.OPEN) {
console.warn("Arrow WebSocket is already connected. Disconnect first to reconnect.");
return;
}
this.connectionPath = path;
this.manualClose = false;
this.setStatus(1 /* CONNECTING */);
this.handlers = handlers;
this.lastConnectionTime = Date.now();
const url = new URL(createFullUrl(this.baseUrl, path));
if (this.apiKey) {
url.searchParams.append("apiKey", this.apiKey);
}
if (this.token) {
url.searchParams.append("token", this.token);
}
if (this.tenantId) {
url.searchParams.append("tenantId", this.tenantId);
}
try {
this.ws = new WebSocket(url.toString());
this.ws.binaryType = "arraybuffer";
this.ws.onopen = this.handleOpen.bind(this);
this.ws.onmessage = this.handleMessage.bind(this);
this.ws.onerror = this.handleError.bind(this);
this.ws.onclose = this.handleClose.bind(this);
} catch (error) {
this.handleConnectionError(error);
}
}
/**
* Update and notify about status changes
*/
setStatus(newStatus) {
if (this.status !== newStatus) {
this.status = newStatus;
if (this.handlers.onStatusChange) {
try {
this.handlers.onStatusChange(newStatus);
} catch (error) {
console.error("Error in Arrow status change handler:", error);
}
}
}
}
/**
* Handle WebSocket open event
*/
handleOpen(event) {
console.log("Arrow WebSocket connection opened");
this.setStatus(2 /* CONNECTED */);
this.reconnectAttempts = 0;
if (this.handlers.onOpen) {
try {
this.handlers.onOpen(event);
} catch (error) {
console.error("Error in Arrow open handler:", error);
}
}
this.processQueue();
this.startHeartbeat();
}
/**
* Start heartbeat mechanism to keep connection alive
*/
startHeartbeat() {
if (this.heartbeatTimer) {
clearTimeout(this.heartbeatTimer);
this.heartbeatTimer = null;
}
this.missedHeartbeats = 0;
this.heartbeatTimer = setInterval(() => {
this.sendHeartbeat();
}, this.heartbeatInterval);
this.sendHeartbeat();
}
/**
* Stop heartbeat mechanism
*/
stopHeartbeat() {
if (this.heartbeatTimer) {
clearTimeout(this.heartbeatTimer);
this.heartbeatTimer = null;
}
}
/**
* Send a heartbeat ping
*/
sendHeartbeat() {
if (!this.ws || this.ws.readyState !== WebSocket.OPEN) {
return;
}
if (this.missedHeartbeats >= this.maxMissedHeartbeats) {
console.warn(`Missed ${this.missedHeartbeats} heartbeats, reconnecting...`);
try {
this.ws.close();
} catch (e) {
console.error("Error closing WebSocket during heartbeat failure:", e);
}
if (this.autoReconnect && !this.manualClose) {
this.attemptReconnect();
}
return;
}
this.missedHeartbeats++;
try {
console.debug("Ping sent");
const pingMessage = {
jsonrpc: "2.0",
method: "ping",
id: `ping-${Date.now()}`
};
this.ws.send(JSON.stringify(pingMessage));
} catch (error) {
console.error("Error sending heartbeat:", error);
}
}
/**
* Handle a WebSocket message event
*/
handleMessage(event) {
try {
this.missedHeartbeats = 0;
this.lastHeartbeatResponse = Date.now();
if (event.data instanceof ArrayBuffer) {
const dataSize = event.data.byteLength;
console.log(`Received binary data (${dataSize} bytes)`);
if (this.handlers.onBinaryMessage) {
try {
this.handlers.onBinaryMessage(event.data);
} catch (error) {
console.error("Error in binary message handler:", error);
}
}
try {
const message = this.decodeMessage(event.data);
if (message.method === "ping") {
if (this.ws && this.ws.readyState === WebSocket.OPEN) {
const pongMessage = {
jsonrpc: "2.0",
method: "pong",
id: message.id
};
try {
this.ws.send(JSON.stringify(pongMessage));
} catch (e) {
console.warn("Failed to send pong response:", e);
}
}
return;
}
if (message.error) {
console.warn("Received error message:", message.error);
}
this.triggerEvent("message", message);
this.processMessage(message);
} catch (decodeError) {
console.error("Error decoding binary message:", decodeError);
}
} else if (typeof event.data === "string") {
try {
const message = JSON.parse(event.data);
if (message.method === "pong" || message.id && String(message.id).startsWith("ping-")) {
console.debug("Pong received");
return;
}
if (this.handlers.onMessage) {
try {
this.handlers.onMessage(message);
} catch (error) {
console.error("Error in message handler:", error);
}
}
this.triggerEvent("message", message);
this.processMessage(message);
} catch (error) {
console.error("Error parsing JSON message:", error, "Raw data:", event.data);
}
} else {
console.warn("Received unsupported message type:", typeof event.data);
}
} catch (error) {
console.error("Error handling WebSocket message:", error);
}
}
/**
* Handle WebSocket error event
*/
handleError(event) {
console.error("Arrow WebSocket error:", event);
this.setStatus(4 /* ERROR */);
if (this.handlers.onError) {
try {
this.handlers.onError(event);
} catch (error) {
console.error("Error in Arrow error handler:", error);
}
}
if (this.autoReconnect && !this.manualClose) {
this.attemptReconnect();
}
}
/**
* Handle WebSocket close event
*/
handleClose(event) {
console.log("Arrow WebSocket connection closed:", event);
this.setStatus(0 /* DISCONNECTED */);
this.stopHeartbeat();
if (this.handlers.onClose) {
try {
this.handlers.onClose(event);
} catch (error) {
console.error("Error in Arrow close handler:", error);
}
}
for (const [id, request] of this.pendingRequests.entries()) {
this.pendingRequests.delete(id);
if (request.timer) {
clearTimeout(request.timer);
}
request.reject(new WebSocketError("Connection closed", {
code: `CLOSE_${event.code}`,
cause: event
}));
}
if (this.autoReconnect && !this.manualClose && event.code !== 1e3) {
this.attemptReconnect();
}
}
/**
* Handle connection error
*/
handleConnectionError(error) {
console.error("Arrow WebSocket connection error:", error);
this.setStatus(4 /* ERROR */);
if (this.autoReconnect && !this.manualClose) {
this.attemptReconnect();
}
}
/**
* Send a message over the WebSocket
*/
async sendArrowMessage(message) {
if (message.id === void 0) {
message.id = this.nextMessageId++;
}
const id = String(message.id);
return new Promise((resolve, reject) => {
if (!this.ws || this.ws.readyState !== WebSocket.OPEN) {
this.queueMessage({
...message
});
const timer = setTimeout(() => {
this.pendingRequests.delete(id);
reject(new WebSocketError("WebSocket not connected", {
code: "CONNECTION_ERROR"
}));
}, this.defaultTimeout);
this.pendingRequests.set(id, {
resolve,
reject,
timer
});
if (this.status !== 1 /* CONNECTING */ && this.autoReconnect && !this.manualClose) {
this.attemptReconnect();
}
return;
}
try {
const data = this.encodeMessage(message);
const timer = setTimeout(() => {
this.pendingRequests.delete(id);
reject(new MCPError("Request timed out", {
code: "TIMEOUT"
}));
}, this.defaultTimeout);
this.pendingRequests.set(id, {
resolve,
reject,
timer
});
this.ws.send(data);
} catch (error) {
reject(new MCPError("Failed to send message", {
cause: error
}));
}
});
}
/**
* Encode a message to binary format
*/
encodeMessage(message) {
try {
if ("binaryData" in message && message.binaryData instanceof Uint8Array && message.binaryData) {
const { binaryData, ...jsonPart } = message;
const jsonString = JSON.stringify(jsonPart);
const headerDelimiter = new Uint8Array([0, 65, 82, 82, 79, 87, 58]);
const encoder2 = new TextEncoder();
const jsonBytes = encoder2.encode(jsonString);
const totalLength = jsonBytes.length + headerDelimiter.length + binaryData.length;
const result = new Uint8Array(totalLength);
result.set(jsonBytes, 0);
result.set(headerDelimiter, jsonBytes.length);
result.set(binaryData, jsonBytes.length + headerDelimiter.length);
return result.buffer;
}
const encoder = new TextEncoder();
const encodedData = encoder.encode(JSON.stringify(message));
return encodedData.buffer;
} catch (error) {
console.error("Error encoding message:", error);
throw new MCPError("Failed to encode message", {
code: "ENCODING_ERROR",
cause: error
});
}
}
/**
* Decode an Arrow IPC message from binary data
*/
decodeMessage(buffer) {
try {
const decoder = new TextDecoder();
const text = decoder.decode(new Uint8Array(buffer));
try {
const jsonMessage = JSON.parse(text);
if (jsonMessage && typeof jsonMessage === "object") {
return jsonMessage;
}
} catch (jsonError) {
console.debug("Not valid JSON, trying binary parse");
}
const headerDelimiter = new Uint8Array([0, 65, 82, 82, 79, 87, 58]);
const data = new Uint8Array(buffer);
console.debug(`Received binary data (${data.length} bytes)`);
let delimiterIndex = -1;
for (let i = 0; i <= data.length - headerDelimiter.length; i++) {
let match = true;
for (let j = 0; j < headerDelimiter.length; j++) {
if (data[i + j] !== headerDelimiter[j]) {
match = false;
break;
}
}
if (match) {
delimiterIndex = i;
break;
}
}
let message;
if (delimiterIndex > 0) {
const headerPart = new Uint8Array(buffer, 0, delimiterIndex);
const headerText = decoder.decode(headerPart);
try {
message = JSON.parse(headerText);
} catch (e) {
console.error("Failed to parse Arrow IPC header:", e);
message = { jsonrpc: "2.0" };
}
const binaryDataStart = delimiterIndex + headerDelimiter.length;
const binaryData = new Uint8Array(buffer, binaryDataStart);
message.binaryData = binaryData;
} else {
if (data.length < 10) {
console.debug("Received likely heartbeat/ping message");
message = {
jsonrpc: "2.0",
method: "ping",
id: String(Date.now())
};
} else {
console.warn(`Received unrecognized binary format (${data.length} bytes): ${data.slice(0, 20)}`);
message = {
jsonrpc: "2.0",
id: String(Date.now()),
binaryData: data
};
}
}
return message;
} catch (error) {
console.error("Error decoding message:", error);
return {
jsonrpc: "2.0",
error: {
code: -32700,
message: "Failed to decode binary message"
}
};
}
}
/**
* Queue a message for later
*/
queueMessage(message) {
console.log("Queueing message for later:", message);
this.messageQueue.push(message);
if (this.status !== 1 /* CONNECTING */ && this.autoReconnect && !this.manualClose) {
this.attemptReconnect();
}
}
/**
* Process the message queue
*/
processQueue() {
if (this.messageQueue.length === 0) {
return;
}
console.log(`Processing ${this.messageQueue.length} queued messages`);
const queue = [...this.messageQueue];
this.messageQueue = [];
for (const message of queue) {
if (this.ws && this.ws.readyState === WebSocket.OPEN) {
this.sendArrowMessage(message).catch(console.error);
} else {
this.messageQueue.push(message);
}
}
}
/**
* Attempt to reconnect to the WebSocket
*/
attemptReconnect() {
if (this.reconnectAttempts >= this.maxReconnectAttempts) {
console.error(`Maximum reconnect attempts (${this.maxReconnectAttempts}) reached. Giving up.`);
return;
}
if (this.status === 1 /* CONNECTING */ || this.status === 5 /* RECONNECTING */) {
return;
}
this.setStatus(5 /* RECONNECTING */);
const delay = Math.min(this.reconnectDelay * Math.pow(1.5, this.reconnectAttempts), 3e4);
this.reconnectAttempts++;
console.log(`Attempting to reconnect in ${delay}ms (attempt ${this.reconnectAttempts}/${this.maxReconnectAttempts})`);
this.connectionRetryTimer = setTimeout(() => {
this.connectionRetryTimer = null;
this.connect(this.connectionPath, this.handlers);
}, delay);
}
/**
* Generate a unique message ID
*/
generateMessageId() {
return this.nextMessageId++;
}
/**
* Get server capabilities
*/
async getCapabilities() {
return this.sendArrowMessage({
jsonrpc: "2.0",
method: "capabilities",
params: {},
id: this.generateMessageId()
});
}
/**
* List available resources
*/
async listResources() {
return this.sendArrowMessage({
jsonrpc: "2.0",
method: "listResources",
params: {},
id: this.generateMessageId()
});
}
/**
* Get a specific resource
*/
async getResource(uri) {
return this.sendArrowMessage({
jsonrpc: "2.0",
method: "getResource",
params: { uri },
id: this.generateMessageId()
});
}
/**
* List available tools
*/
async listTools() {
return this.sendArrowMessage({
jsonrpc: "2.0",
method: "listTools",
params: {},
id: this.generateMessageId()
});
}
/**
* Get the current connection status
*/
getStatus() {
return this.status;
}
/**
* Disconnect from the WebSocket
*/
disconnect() {
if (!this.ws) {
return;
}
console.log("Disconnecting Arrow WebSocket");
this.manualClose = true;
this.stopHeartbeat();
this.setStatus(3 /* CLOSING */);
try {
this.ws.close();
} catch (error) {
console.error("Error closing WebSocket:", error);
} finally {
this.ws = null;
this.setStatus(0 /* DISCONNECTED */);
}
if (this.connectionRetryTimer) {
clearTimeout(this.connectionRetryTimer);
this.connectionRetryTimer = null;
}
}
/**
* Send a request to a model (like Claude) through the MCP server
*/
async sendRequest(options) {
const startTime = performance.now();
let serializationTime = 0;
let networkStart = 0;
let networkEnd = 0;
let deserializationTime = 0;
try {
const serializationStart = performance.now();
const method = options.method || "POST";
const responseType = options.responseType || "json";
const isStreaming = options.onChunk && options.body?.stream;
const params = {
path: options.path,
method,
body: options.body || {},
responseType,
stream: isStreaming
};
serializationTime = performance.now() - serializationStart;
networkStart = performance.now();
if (isStreaming) {
const messageId = this.generateMessageId();
const streamingPromise = new Promise((resolve, reject) => {
const streamHandler = /* @__PURE__ */ __name((message) => {
if (message.id === messageId && message.method === "streamChunk") {
options.onChunk?.(message.params);
} else if (message.id === messageId && message.method === "streamEnd") {
this.removeEventListener("message", streamHandler);
networkEnd = performance.now();
resolve(message.params?.result || {});
} else if (message.id === messageId && message.error) {
this.removeEventListener("message", streamHandler);
reject(new MCPError("Streaming error", {
code: "STREAM_ERROR",
cause: message.error
}));
}
}, "streamHandler");
this.addEventListener("message", streamHandler);
const timer = setTimeout(() => {
this.removeEventListener("message", streamHandler);
reject(new MCPError("Stream request timed out", { code: "TIMEOUT" }));
}, this.streamTimeout || 6e4);
this.sendArrowMessage({
jsonrpc: "2.0",
method: "startStream",
params,
id: messageId
}).catch((error) => {
this.removeEventListener("message", streamHandler);
clearTimeout(timer);
reject(error);
});
});
const response = await streamingPromise;
deserializationTime = 0;
const result = {
data: response.data || {},
status: response.status || 200,
headers: response.headers || {}
};
if (options.metrics) {
result.metrics = {
totalTime: performance.now() - startTime,
serializationTime,
networkTime: networkEnd - networkStart,
deserializationTime
};
}
return result;
} else {
const response = await this.sendArrowMessage({
jsonrpc: "2.0",
method: "sendRequest",
params,
id: this.generateMessageId()
});
networkEnd = performance.now();
const deserializationStart = performance.now();
const result = {
data: response.data || {},
status: response.status || 200,
headers: response.headers || {}
};
deserializationTime = performance.now() - deserializationStart;
if (options.metrics) {
result.metrics = {
totalTime: performance.now() - startTime,
serializationTime,
networkTime: networkEnd - networkStart,
deserializationTime
};
}
return result;
}
} catch (error) {
console.error("Error in sendRequest:", error);
throw error;
}
}
/**
* Check server health
*/
async checkHealth() {
try {
const healthUrl = new URL(this.baseUrl.replace("ws://", "http://").replace("wss://", "https://"));
healthUrl.pathname = "/health";
const response = await fetch(healthUrl.toString(), {
method: "GET",
headers: {
"Accept": "application/json"
}
});
if (response.ok) {
const data = await response.json();
return data.status === "ok";
}
if (this.status !== 2 /* CONNECTED */) {
return false;
}
await this.getCapabilities();
return true;
} catch (error) {
console.error("Health check failed:", error);
return false;
}
}
/**
* Get server connection status as string
*/
getConnectionStatusText() {
switch (this.status) {
case 2 /* CONNECTED */:
return "Connected";
case 1 /* CONNECTING */:
return "Connecting";
case 0 /* DISCONNECTED */:
return "Disconnected";
case 4 /* ERROR */:
return "Error";
case 5 /* RECONNECTING */:
return "Reconnecting";
case 3 /* CLOSING */:
return "Closing";
default:
return "Unknown";
}
}
/**
* Process an incoming message
*/
processMessage(message) {
const id = String(message.id || "");
const pendingRequest = this.pendingRequests.get(id);
if (pendingRequest) {
if (pendingRequest.timer) {
clearTimeout(pendingRequest.timer);
}
this.pendingRequests.delete(id);
if (message.error) {
pendingRequest.reject(new MCPError(message.error.message || "Unknown error", {
code: String(message.error.code),
cause: message.error.data
}));
} else {
let result = message.result;
if (message.binaryData && message.binaryData.length > 0) {
try {
if (!result) {
result = {};
}
if (typeof result === "object") {
result.arrowData = message.binaryData.buffer;
if (message.params?.schema) {
result.schema = message.params.schema;
}
}
} catch (error) {
console.warn("Error processing Arrow binary data:", error);
}
}
pendingRequest.resolve(result);
}
}
if (message.method === "streamChunk" || message.method === "streamEnd") {
return;
}
if (this.handlers.onMessage) {
try {
this.handlers.onMessage(message);
} catch (error) {
console.error("Error in message handler:", error);
}
}
}
};
__name(_MCPArrowClient, "MCPArrowClient");
MCPArrowClient = _MCPArrowClient;
__name(createArrowClient, "createArrowClient");
arrow_ipc_default = MCPArrowClient;
}
});
// src/auth/index.ts
function createAuthHeaders(options) {
const headers = {};
if (options.apiKey) {
headers["X-API-Key"] = options.apiKey;
}
if (options.token) {
headers["Authorization"] = `Bearer ${options.token}`;
}
if (options.tenantId) {
headers["X-Tenant-ID"] = options.tenantId;
}
return headers;
}
__name(createAuthHeaders, "createAuthHeaders");
// src/utils/fetcher.ts
init_errors();
var sleep = /* @__PURE__ */ __name((ms) => new Promise((resolve) => setTimeout(resolve, ms)), "sleep");
var DEFAULT_RETRY_STATUS_CODES = [408, 429, 500, 502, 503, 504];
function createFetchParams(url, options = {}) {
const {
cache,
revalidate,
tags,
...fetchOptions
} = options;
const nextFetchOptions = { ...fetchOptions };
if (typeof cache === "boolean") {
nextFetchOptions.cache = cache ? "force-cache" : "no-store";
} else if (typeof cache === "string") {
nextFetchOptions.cache = cache;
} else if (cache && typeof cache === "object") {
nextFetchOptions.next = nextFetchOptions.next || {};
const cacheConfig = cache;
if (cacheConfig.maxAge !== void 0) {
nextFetchOptions.next.revalidate = cacheConfig.maxAge;
}
if (cacheConfig.tag) {
nextFetchOptions.next.tags = Array.isArray(cacheConfig.tag) ? cacheConfig.tag : [cacheConfig.tag];
}
}
if (revalidate !== void 0) {
nextFetchOptions.next = nextFetchOptions.next || {};
nextFetchOptions.next.revalidate = revalidate;
}
if (tags && tags.length > 0) {
nextFetchOptions.next = nextFetchOptions.next || {};
nextFetchOptions.next.tags = tags;
}
return { url, options: nextFetchOptions };
}
__name(createFetchParams, "createFetchParams");
async function fetcher(url, options = {}) {
const {
timeout = 1e4,
retry = false,
...fetchOptions
} = options;
const retryOptions = retry === true ? {} : retry || {};
const {
retries = 0,
retryBackoffFactor = 2,
retryInitialDelay = 300,
retryMaxDelay = 1e4,
retryStatusCodes = DEFAULT_RETRY_STATUS_CODES,
shouldRetry
} = retryOptions;
const controller = new AbortController();
const { signal } = controller;
let timeoutId = null;
if (timeout > 0) {
timeoutId = setTimeout(() => {
controller.abort();
}, timeout);
}
const originalSignal = fetchOptions.signal;
if (originalSignal) {
originalSignal.addEventListener("abort", () => {
controller.abort(originalSignal.reason);
if (timeoutId) clearTimeout(timeoutId);
});
}
const mergedOptions = {
...fetchOptions,
signal
};
const { url: parsedUrl, options: nextOptions } = createFetchParams(url, {
...mergedOptions,
cache: options.cache,
revalidate: options.revalidate,
tags: options.tags
});
const clearTimeoutIfExists = /* @__PURE__ */ __name(() => {
if (timeoutId !== null) {
clearTimeout(timeoutId);
timeoutId = null;
}
}, "clearTimeoutIfExists");
const customShouldRetry = /* @__PURE__ */ __name((error, attempt2) => {
if (attempt2 >= retries) return false;
if (shouldRetry) return shouldRetry(error, attempt2);
if (error instanceof TimeoutError) return true;
if (error instanceof NetworkError) return true;
if (error instanceof HTTPError && retryStatusCodes.includes(error.status || 0)) return true;
return false;
}, "customShouldRetry");
let attempt = 0;
let lastError = null;
while (attempt <= retries) {
try {
const response = await fetch(parsedUrl, nextOptions);
clearTimeoutIfExists();
if (!response.ok) {
throw createHttpError(response);
}
let data;
const contentType = response.headers.get("content-type");
try {
if (contentType && contentType.includes("application/json")) {
data = await response.json();
} else {
try {
data = await response.json();
} catch {
const text = await response.text();
data = text.trim() ? text : {};
}
}
} catch (error) {
throw new ParseError("Failed to parse response", {
cause: error,
data: await response.text().catch(() => void 0)
});
}
return {
data,
status: response.status,
headers: response.headers,
response
};
} catch (error) {
clearTimeoutIfExists();
let mcpError;
if (error instanceof MCPError) {
mcpError = error;
} else if (error instanceof DOMException && error.name === "AbortError") {
mcpError = new TimeoutError("Request timed out", { cause: error });
} else if (error instanceof TypeError && error.message.includes("fetch")) {
mcpError = new NetworkError("Network error occurred", { cause: error });
} else if (error instanceof Error) {
mcpError = new MCPError(error.message, { cause: error });
} else {
mcpError = new MCPError(String(error));
}
lastError = mcpError;
if (!customShouldRetry(mcpError, attempt)) {
throw mcpError;
}
const delay = Math.min(
retryInitialDelay * Math.pow(retryBackoffFactor, attempt),
retryMaxDelay
);
if (process.env.NODE_ENV !== "production") {
console.warn(`Retry attempt ${attempt + 1}/${retries} for ${parsedUrl} after ${delay}ms delay`);
}
await sleep(delay);
attempt++;
}
}
if (lastError) {
throw lastError;
}
throw new MCPError("Unknown error occurred");
}
__name(fetcher, "fetcher");
// src/client/websocket.ts
init_paths();
var _MCPWebSocket = class _MCPWebSocket {
/**
* Creates a new WebSocket client
*/
constructor(options) {
this.ws = null;
this.reconnectAttempts = 0;
this.maxReconnectAttempts = 10;
this.reconnectDelay = 1e3;
this.handlers = {};
this.status = 3 /* CLOSED */;
this.messageQueue = [];
this.subscriptions = /* @__PURE__ */ new Map();
this.heartbeatDelay = 15e3;
// 15 seconds to match server's interval
this.connectionId = "";
this.binarySupport = false;
this.connectionPath = "/resolve_ws";
this.autoReconnect = true;
this.lastConnectionTime = 0;
this.connectionRetryTimer = null;
this.manualClose = false;
let wsBaseUrl = options.baseUrl || "https://api.mcp.ai";
wsBaseUrl = wsBaseUrl.replace(/^http/, "ws");
this.baseUrl = wsBaseUrl;
this.apiKey = options.apiKey;
this.token = options.token;
this.tenantId = options.tenantId;
this.autoReconnect = options.autoReconnect !== false;
if (options.maxReconnectAttempts !== void 0) {
this.maxReconnectAttempts = options.maxReconnectAttempts;
}
}
/**
* Connects to the MCP WebSocket endpoint
*/
connect(path = "/resolve_ws", handlers = {}) {
if (this.ws && this.ws.readyState === WebSocket.OPEN) {
console.warn("WebSocket is already connected. Disconnect first to reconnect.");
return;
}
this.connectionPath = path;
this.manualClose = false;
this.setStatus(0 /* CONNECTING */);
this.handlers = handlers;
this.lastConnectionTime = Date.now();
const url = new URL(createFullUrl(this.baseUrl, path));
this.connectionId = this.generateConnectionId();
if (this.apiKey) {
url.searchParams.append("apiKey", this.apiKey);
}
if (this.token) {
url.searchParams.append("token", this.token);
}
if (this.tenantId) {
url.searchParams.append("tenantId", this.tenantId);
}
url.searchParams.append("connectionId", this.connectionId);
try {
this.ws = new WebSocket(url.toString());
this.binarySupport = typeof this.ws.binaryType !== "undefined";
if (this.binarySupport) {
this.ws.binaryType = "arraybuffer";
}
this.ws.onopen = this.handleOpen.bind(this);
this.ws.onmessage = this.handleMessage.bind(this);
this.ws.onerror = this.handleError.bind(this);
this.ws.onclose = this.handleClose.bind(this);
} catch (error) {
this.handleConnectionError(error);
}
}
/**
* Update and notify about status changes
*/
setStatus(newStatus) {
if (this.status !== newStatus) {
this.status = newStatus;
if (this.handlers.onStatusChange) {
try {
this.handlers.onStatusChange(newStatus);
} catch (error) {
console.error("Error in WebSocket status change handler:", error);
}
}
}
}
/**
* Handle WebSocket open event
*/
handleOpen(event) {
console.log("WebSocket connection opened");
this.setStatus(1 /* CONNECTED */);
this.reconnectAttempts = 0;
if (this.handlers.onOpen) {
try {
this.handlers.onOpen(event);
} catch (error) {
console.error("Error in WebSocket open handler:", error);
}
}
this.startHeartbeat();
this.processQueue();
}
/**
* Handle WebSocket message event
*/
handleMessage(event) {
let data;
let messageType;
try {
if (this.binarySupport && event.data instanceof ArrayBuffer) {
const view = new Uint8Array(event.data);
const textDecoder = new TextDecoder();
data = textDecoder.decode(view);
} else {
data = JSON.parse(event.data);
messageType = data.type;
}
if (this.handlers.onMessage) {
try {
this.handlers.onMessage(event.data, messageType);
} catch (error) {
console.error("Error in WebSocket message handler:", error);
}
}
if (messageType && this.handlers.onMessageType && this.handlers.onMessageType[messageType]) {
try {
this.handlers.onMessageType[messageType](data);
} catch (error) {
console.error(`Error in WebSocket message type handler for ${messageType}:`, error);
}
}
if (messageType && this.subscriptions.has(messageType)) {
const callbacks = this.subscriptions.get(messageType);
if (callbacks) {
callbacks.forEach((callback) => {
try {
callback(data);
} catch (error) {
console.error(`Error in WebSocket subscription callback for ${messageType}:`, error);
}
});
}
}
if (messageType === "heartbeat" && this.lastHeartbeatTime) {
const latency = Date.now() - this.lastHeartbeatTime;
if (this.handlers.onHeartbeat) {
try {
this.handlers.onHeartbeat(latency);
} catch (error) {
console.error("Error in WebSocket heartbeat handler:", error);
}
}
}
} catch (error) {
console.error("Error processing WebSocket message:", error);
}
}
/**
* Handle WebSocket error event
*/
handleError(event) {
console.error("WebSocket error:", event);
this.setStatus(5 /* ERROR */);
if (this.handlers.onError) {
try {
this.handlers.onError(event);
} catch (error) {
console.error("Error in WebSocket error handler:", error);
}
}
}
/**
* Handle WebSocket close event
*/
handleClose(event) {
console.log(`WebSocket closed with code ${event.code}, reason: ${event.reason}`);
this.setStatus(3 /* CLOSED */);
this.stopHeartbeat();
if (this.handlers.onClose) {
try {
this.handlers.onClose(event);
} catch (error) {
console.error("Error in WebSocket close handler:", error);
}
}
if (!this.manualClose && this.autoReconnect) {
this.attemptReconnect();
}
}
/**
* Handle connection initialization error
*/
handleConnectionError(error) {
console.error("WebSocket connection error:", error);
this.setStatus(5 /* ERROR */);
if (this.autoReconnect) {
this.attemptReconnect();
}
}
/**
* Send a message over the WebSocket connection
*/
send(message) {
if (!this.ws || this.ws.readyState !== WebSocket.OPEN) {
this.queueMessage(message);
if (!this.ws || this.ws.readyState === WebSocket.CLOSED) {
this.connect(this.connectionPath, this.handlers);
}
return false;
}
try {
if (!message.id) {
message.id = this.generateMessageId();
}
this.ws.send(JSON.stringify(message));
return true;
} catch (error) {
console.error("Error sending WebSocket message:", error);
this.queueMessage(message);
return false;
}
}
/**
* Queue a message for later sending
*/
queueMessage(message) {
if (this.messageQueue.length >= 100) {
console.warn("WebSocket message queue is full, dropping oldest message");
this.messageQueue.shift();
}
this.messageQueue.push(message);
}
/**
* Process the message queue
*/
processQueue() {
if (this.ws && this.ws.readyState === WebSocket.OPEN && this.messageQueue.length > 0) {
console.log(`Processing ${this.messageQueue.length} queued