import { Client } from '@modelcontextprotocol/sdk/client/index.js';
import { StreamableHTTPClientTransport } from '@modelcontextprotocol/sdk/client/streamableHttp.js';
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import { InitializeRequestSchema } from '@modelcontextprotocol/sdk/types.js';
import { z } from 'zod';
import { getVersion } from '../lib/getVersion.js';
import { onSignals } from '../lib/onSignals.js';
let mcpClient;
const newInitializeMcpClient = ({ message }) => {
    const clientInfo = message.params?.clientInfo;
    const clientCapabilities = message.params?.capabilities;
    return new Client({
        name: clientInfo?.name ?? 'supergateway',
        version: clientInfo?.version ?? getVersion(),
    }, {
        capabilities: clientCapabilities ?? {},
    });
};
const newFallbackMcpClient = async ({ mcpTransport, }) => {
    const fallbackMcpClient = new Client({
        name: 'supergateway',
        version: getVersion(),
    }, {
        capabilities: {},
    });
    await fallbackMcpClient.connect(mcpTransport);
    return fallbackMcpClient;
};
export async function streamableHttpToStdio(args) {
    const { streamableHttpUrl, logger, headers } = args;
    logger.info(`  - streamableHttp: ${streamableHttpUrl}`);
    logger.info(`  - Headers: ${Object.keys(headers).length ? JSON.stringify(headers) : '(none)'}`);
    logger.info('Connecting to Streamable HTTP...');
    onSignals({ logger });
    const mcpTransport = new StreamableHTTPClientTransport(new URL(streamableHttpUrl), {
        requestInit: {
            headers,
        },
    });
    mcpTransport.onerror = (err) => {
        logger.error('Streamable HTTP error:', err);
    };
    mcpTransport.onclose = () => {
        logger.error('Streamable HTTP connection closed');
        process.exit(1);
    };
    const stdioServer = new Server({
        name: 'supergateway',
        version: getVersion(),
    }, {
        capabilities: {},
    });
    const stdioTransport = new StdioServerTransport();
    await stdioServer.connect(stdioTransport);
    const wrapResponse = (req, payload) => ({
        jsonrpc: req.jsonrpc || '2.0',
        id: req.id,
        ...payload,
    });
    stdioServer.transport.onmessage = async (message) => {
        const isRequest = 'method' in message && 'id' in message;
        if (isRequest) {
            logger.info('Stdio → Streamable HTTP:', message);
            const req = message;
            let result;
            try {
                if (!mcpClient) {
                    if (message.method === 'initialize') {
                        mcpClient = newInitializeMcpClient({
                            message,
                        });
                        const originalRequest = mcpClient.request;
                        mcpClient.request = async function (possibleInitRequestMessage, ...restArgs) {
                            if (InitializeRequestSchema.safeParse(possibleInitRequestMessage)
                                .success &&
                                message.params?.protocolVersion) {
                                // respect the protocol version from the stdio client's init request
                                possibleInitRequestMessage.params.protocolVersion =
                                    message.params.protocolVersion;
                            }
                            result = await originalRequest.apply(this, [
                                possibleInitRequestMessage,
                                ...restArgs,
                            ]);
                            return result;
                        };
                        await mcpClient.connect(mcpTransport);
                        mcpClient.request = originalRequest;
                    }
                    else {
                        logger.info('Streamable HTTP client not initialized, creating fallback client');
                        mcpClient = await newFallbackMcpClient({ mcpTransport });
                    }
                    logger.info('Streamable HTTP connected');
                }
                else {
                    result = await mcpClient.request(req, z.any());
                }
            }
            catch (err) {
                logger.error('Request error:', err);
                const errorCode = err && typeof err === 'object' && 'code' in err
                    ? err.code
                    : -32000;
                let errorMsg = err && typeof err === 'object' && 'message' in err
                    ? err.message
                    : 'Internal error';
                const prefix = `MCP error ${errorCode}:`;
                if (errorMsg.startsWith(prefix)) {
                    errorMsg = errorMsg.slice(prefix.length).trim();
                }
                const errorResp = wrapResponse(req, {
                    error: {
                        code: errorCode,
                        message: errorMsg,
                    },
                });
                process.stdout.write(JSON.stringify(errorResp) + '\n');
                return;
            }
            const response = wrapResponse(req, result.hasOwnProperty('error')
                ? { error: { ...result.error } }
                : { result: { ...result } });
            logger.info('Response:', response);
            process.stdout.write(JSON.stringify(response) + '\n');
        }
        else {
            logger.info('Streamable HTTP → Stdio:', message);
            process.stdout.write(JSON.stringify(message) + '\n');
        }
    };
    logger.info('Stdio server listening');
}
