/*
 * Express server implementation used for standby Actor mode.
 */

!function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="05507403-0133-596b-8baa-97dd535b76d8")}catch(e){}}();
import { randomUUID } from 'node:crypto';
import { InMemoryTaskStore } from '@modelcontextprotocol/sdk/experimental/tasks/stores/in-memory.js';
import { SSEServerTransport } from '@modelcontextprotocol/sdk/server/sse.js';
import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js';
import express from 'express';
import log from '@apify/log';
import { parseBooleanOrNull } from '@apify/utilities';
import { ApifyClient } from '../apify_client.js';
import { ActorsMcpServer } from '../mcp/server.js';
import { parseUiMode } from '../types.js';
import { getHelpMessage, Routes, TransportType } from './const.js';
export function createExpressApp(host) {
    const app = express();
    const mcpServers = {};
    const transportsSSE = {};
    const transports = {};
    const taskStore = new InMemoryTaskStore();
    function respondWithError(res, error, logMessage, statusCode = 500) {
        if (statusCode >= 500) {
            // Server errors (>= 500) - log as exception
            log.exception(error instanceof Error ? error : new Error(String(error)), 'Error in request', { logMessage, statusCode });
        }
        else {
            // Client errors (< 500) - log as softFail without stack trace
            const errorMessage = error instanceof Error ? error.message : String(error);
            log.softFail('Error in request', { logMessage, errMessage: errorMessage, statusCode });
        }
        if (!res.headersSent) {
            res.status(statusCode).json({
                jsonrpc: '2.0',
                error: {
                    code: statusCode === 500 ? -32603 : -32000,
                    message: statusCode === 500 ? 'Internal server error' : 'Bad Request',
                },
                id: null,
            });
        }
    }
    app.get(Routes.SSE, async (req, res) => {
        var _a, _b, _c;
        try {
            log.info('MCP API', {
                mth: req.method,
                rt: Routes.SSE,
                tr: TransportType.SSE,
            });
            // Extract telemetry query parameters
            const urlParams = new URL(req.url, `http://${req.headers.host}`).searchParams;
            const telemetryEnabledParam = urlParams.get('telemetry-enabled');
            // URL param > env var > default (true)
            const telemetryEnabled = (_b = (_a = parseBooleanOrNull(telemetryEnabledParam)) !== null && _a !== void 0 ? _a : parseBooleanOrNull(process.env.TELEMETRY_ENABLED)) !== null && _b !== void 0 ? _b : true;
            const uiMode = (_c = parseUiMode(urlParams.get('ui'))) !== null && _c !== void 0 ? _c : parseUiMode(process.env.UI_MODE);
            // Extract payment mode parameter - if payment=skyfire, enable skyfire mode
            const paymentParam = urlParams.get('payment');
            const skyfireMode = paymentParam === 'skyfire';
            const mcpServer = new ActorsMcpServer({
                taskStore,
                setupSigintHandler: false,
                transportType: 'sse',
                telemetry: {
                    enabled: telemetryEnabled,
                },
                uiMode,
                skyfireMode,
            });
            const transport = new SSEServerTransport(Routes.MESSAGE, res);
            // Generate a unique session ID for this SSE connection
            const mcpSessionId = transport.sessionId;
            // Load MCP server tools
            const apifyToken = process.env.APIFY_TOKEN;
            log.debug('Loading tools from URL', { mcpSessionId: transport.sessionId, tr: TransportType.SSE });
            const apifyClient = new ApifyClient({ token: apifyToken });
            await mcpServer.loadToolsFromUrl(req.url, apifyClient);
            transportsSSE[transport.sessionId] = transport;
            mcpServers[transport.sessionId] = mcpServer;
            // Create a proxy for transport.onmessage to inject session ID into all requests
            const originalOnMessage = transport.onmessage;
            transport.onmessage = (message) => {
                var _a;
                const msgRecord = message;
                // Inject session ID into all requests with params
                if (msgRecord.params) {
                    const params = msgRecord.params;
                    (_a = params._meta) !== null && _a !== void 0 ? _a : (params._meta = {});
                    params._meta.mcpSessionId = mcpSessionId;
                }
                // Call the original onmessage handler
                if (originalOnMessage) {
                    originalOnMessage(message);
                }
            };
            await mcpServer.connect(transport);
            res.on('close', () => {
                log.info('Connection closed, cleaning up', {
                    mcpSessionId: transport.sessionId,
                });
                delete transportsSSE[transport.sessionId];
                delete mcpServers[transport.sessionId];
            });
        }
        catch (error) {
            respondWithError(res, error, `Error in GET ${Routes.SSE}`);
        }
    });
    app.post(Routes.MESSAGE, async (req, res) => {
        try {
            log.info('MCP API', {
                mth: req.method,
                rt: Routes.MESSAGE,
                tr: TransportType.HTTP,
            });
            const sessionId = new URL(req.url, `http://${req.headers.host}`).searchParams.get('sessionId');
            if (!sessionId) {
                log.softFail('No session ID provided in POST request', { statusCode: 400 });
                res.status(400).json({
                    jsonrpc: '2.0',
                    error: {
                        code: -32000,
                        message: 'Bad Request: No session ID provided',
                    },
                    id: null,
                });
                return;
            }
            const transport = transportsSSE[sessionId];
            if (transport) {
                await transport.handlePostMessage(req, res);
            }
            else {
                log.softFail('Server is not connected to the client.', { statusCode: 404 });
                res.status(404).json({
                    jsonrpc: '2.0',
                    error: {
                        code: -32000,
                        message: 'Not Found: Server is not connected to the client. '
                            + 'Connect to the server with GET request to /sse endpoint',
                    },
                    id: null,
                });
            }
        }
        catch (error) {
            respondWithError(res, error, `Error in POST ${Routes.MESSAGE}`);
        }
    });
    // express.json() middleware to parse JSON bodies.
    // It must be used before the POST / route but after the GET /sse route :shrug:
    app.use(express.json());
    app.post(Routes.MCP, async (req, res) => {
        var _a, _b, _c, _d, _e, _f, _g;
        var _h, _j;
        log.info('Received MCP request:', req.body);
        try {
            // Check for existing session ID
            const sessionId = req.headers['mcp-session-id'];
            let transport;
            if (sessionId && transports[sessionId]) {
                // Reuse existing transport
                transport = transports[sessionId];
                // Inject session ID into request params for existing sessions
                if ((_a = req.body) === null || _a === void 0 ? void 0 : _a.params) {
                    (_b = (_h = req.body.params)._meta) !== null && _b !== void 0 ? _b : (_h._meta = {});
                    req.body.params._meta.mcpSessionId = sessionId;
                }
            }
            else if (!sessionId && isInitializeRequest(req.body)) {
                // New initialization request
                transport = new StreamableHTTPServerTransport({
                    sessionIdGenerator: () => randomUUID(),
                    enableJsonResponse: false, // Use SSE response mode
                });
                // Extract telemetry query parameters
                const urlParams = new URL(req.url, `http://${req.headers.host}`).searchParams;
                const telemetryEnabledParam = urlParams.get('telemetry-enabled');
                // URL param > env var > default (true)
                const telemetryEnabled = (_d = (_c = parseBooleanOrNull(telemetryEnabledParam)) !== null && _c !== void 0 ? _c : parseBooleanOrNull(process.env.TELEMETRY_ENABLED)) !== null && _d !== void 0 ? _d : true;
                const uiMode = (_e = parseUiMode(urlParams.get('ui'))) !== null && _e !== void 0 ? _e : parseUiMode(process.env.UI_MODE);
                // Extract payment mode parameter - if payment=skyfire, enable skyfire mode
                const paymentParam = urlParams.get('payment');
                const skyfireMode = paymentParam === 'skyfire';
                const mcpServer = new ActorsMcpServer({
                    taskStore,
                    setupSigintHandler: false,
                    initializeRequestData: req.body,
                    transportType: 'http',
                    telemetry: {
                        enabled: telemetryEnabled,
                    },
                    uiMode,
                    skyfireMode,
                });
                // Load MCP server tools
                const apifyToken = process.env.APIFY_TOKEN;
                log.debug('Loading tools from URL', { mcpSessionId: transport.sessionId, tr: TransportType.HTTP });
                const apifyClient = new ApifyClient({ token: apifyToken });
                await mcpServer.loadToolsFromUrl(req.url, apifyClient);
                // Connect the transport to the MCP server BEFORE handling the request
                await mcpServer.connect(transport);
                // After handling the request, if we get a session ID back, store the transport
                await transport.handleRequest(req, res, req.body);
                // Store the transport by session ID for future requests
                if (transport.sessionId) {
                    transports[transport.sessionId] = transport;
                    mcpServers[transport.sessionId] = mcpServer;
                }
                return; // Already handled
            }
            else {
                // Invalid request - no session ID or not initialization request
                res.status(404).json({
                    jsonrpc: '2.0',
                    error: {
                        code: -32000,
                        message: 'Not Found: No valid session ID provided or not initialization request',
                    },
                    id: null,
                });
                return;
            }
            // Inject session ID into request params for all requests
            if (((_f = req.body) === null || _f === void 0 ? void 0 : _f.params) && sessionId) {
                (_g = (_j = req.body.params)._meta) !== null && _g !== void 0 ? _g : (_j._meta = {});
                req.body.params._meta.mcpSessionId = sessionId;
            }
            // Handle the request with existing transport - no need to reconnect
            await transport.handleRequest(req, res, req.body);
        }
        catch (error) {
            respondWithError(res, error, 'Error handling MCP request');
        }
    });
    // Handle GET requests for SSE streams according to spec
    app.get(Routes.MCP, async (_req, res) => {
        // We don't support GET requests for this server
        // The spec requires returning 405 Method Not Allowed in this case
        res.status(405).set('Allow', 'POST').send('Method Not Allowed');
    });
    app.delete(Routes.MCP, async (req, res) => {
        const sessionId = req.headers['mcp-session-id'];
        const transport = transports[sessionId || ''];
        if (transport) {
            log.info('MCP API', {
                mth: req.method,
                rt: Routes.MESSAGE,
                tr: TransportType.HTTP,
                mcpSessionId: sessionId,
            });
            await transport.handleRequest(req, res, req.body);
            return;
        }
        log.softFail('Session not found', { mcpSessionId: sessionId, statusCode: 404 });
        res.status(404).send('Not Found: Session not found').end();
    });
    // Catch-all for undefined routes
    app.use((req, res) => {
        res.status(404).json({ message: `There is nothing at route ${req.method} ${req.originalUrl}. ${getHelpMessage(host)}` }).end();
    });
    return app;
}
// Helper function to detect initialize requests
function isInitializeRequest(body) {
    if (Array.isArray(body)) {
        return body.some((msg) => typeof msg === 'object' && msg !== null && 'method' in msg && msg.method === 'initialize');
    }
    return typeof body === 'object' && body !== null && 'method' in body && body.method === 'initialize';
}
//# sourceMappingURL=server.js.map
//# debugId=05507403-0133-596b-8baa-97dd535b76d8
