import { SseError } from "@modelcontextprotocol/sdk/client/sse.js";
import { StreamableHTTPError } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
import { isJSONRPCRequest } from "@modelcontextprotocol/sdk/types.js";
/**
 * JSON-RPC error code for failed proxy → upstream transport sends.
 *
 * JSON-RPC 2.0 reserves **-32000 .. -32099** for implementation-defined *server* errors.
 * MCP uses that band for protocol/SDK errors (see `@modelcontextprotocol/sdk` `ErrorCode`, e.g.
 * `-32000` connection closed, `-32001` request timeout, `-32042` URL elicitation; other codes in
 * the band are assigned over time — e.g. **-32002** is used for **ResourceNotFound** in parts of
 * the MCP ecosystem). We use **-32099** so this inspector-only bridge error stays clear of those
 * registered meanings; the client still keys off `error.data` shape, not the number alone.
 *
 * Keep in sync with `client/src/lib/constants.ts`; drift is caught by
 * `client/src/lib/__tests__/mcpProxyTransportErrorCode.test.ts`.
 */
export const MCP_PROXY_TRANSPORT_ERROR_CODE = -32099;
function onClientError(error) {
    console.error("Error from inspector client:", error);
}
function onServerError(error) {
    if (error?.cause && JSON.stringify(error.cause).includes("ECONNREFUSED")) {
        console.error("Connection refused. Is the MCP server running?");
    }
    else if (error.message && error.message.includes("404")) {
        console.error("Error accessing endpoint (HTTP 404)");
    }
    else {
        console.error("Error from MCP server:", error);
    }
}
/** Exported for unit tests; used by the proxy when forwarding transport failures. */
export function serializeProxyTransportError(error, headerHolder) {
    const data = {
        message: error.message,
        name: error.name,
    };
    if (error.cause !== undefined) {
        data.cause =
            error.cause instanceof Error ? error.cause.message : String(error.cause);
    }
    const attachHttpStatusIfValid = (code) => {
        if (Number.isInteger(code) && code >= 100 && code <= 599) {
            data.httpStatus = code;
        }
    };
    if (error instanceof StreamableHTTPError) {
        if (error.code !== undefined)
            attachHttpStatusIfValid(error.code);
    }
    else if (error instanceof SseError) {
        if (error.code !== undefined)
            attachHttpStatusIfValid(error.code);
    }
    if (headerHolder?.lastUpstream401) {
        const u = headerHolder.lastUpstream401;
        data.upstream401 = {
            wwwAuthenticate: u.wwwAuthenticate,
            body: u.body,
            contentType: u.contentType,
        };
        delete headerHolder.lastUpstream401;
    }
    return data;
}
export default function mcpProxy({ transportToClient, transportToServer, headerHolder, }) {
    let transportToClientClosed = false;
    let transportToServerClosed = false;
    let reportedServerSession = false;
    transportToClient.onmessage = (message) => {
        transportToServer.send(message).catch((err) => {
            const error = err instanceof Error ? err : new Error(String(err));
            // Send error response back to client if it was a request (has id) and connection is still open
            if (isJSONRPCRequest(message) && !transportToClientClosed) {
                const causeStr = error.cause !== undefined
                    ? error.cause instanceof Error
                        ? error.cause.message
                        : String(error.cause)
                    : null;
                const errorResponse = {
                    jsonrpc: "2.0",
                    id: message.id,
                    error: {
                        code: MCP_PROXY_TRANSPORT_ERROR_CODE,
                        message: causeStr
                            ? `${error.message} (cause: ${causeStr})`
                            : error.message,
                        data: serializeProxyTransportError(error, headerHolder),
                    },
                };
                transportToClient.send(errorResponse).catch(onClientError);
            }
        });
    };
    transportToServer.onmessage = (message) => {
        if (!reportedServerSession) {
            if (transportToServer.sessionId) {
                // Can only report for StreamableHttp
                console.error("Proxy  <-> Server sessionId: " + transportToServer.sessionId);
            }
            reportedServerSession = true;
        }
        transportToClient.send(message).catch(onClientError);
    };
    transportToClient.onclose = () => {
        if (transportToServerClosed) {
            return;
        }
        transportToClientClosed = true;
        transportToServer.close().catch(onServerError);
    };
    transportToServer.onclose = () => {
        if (transportToClientClosed) {
            return;
        }
        transportToServerClosed = true;
        transportToClient.close().catch(onClientError);
    };
    transportToClient.onerror = onClientError;
    transportToServer.onerror = onServerError;
}
