{"version":3,"file":"http.mjs","names":["transport: StreamableHTTPServerTransport"],"sources":["../src/http.ts"],"sourcesContent":["/**\n * HTTP transport support for the Tusk Drift MCP server.\n * This module provides utilities for running the MCP server over HTTP.\n */\n\nimport type { Request, Response } from \"express\";\nimport { StreamableHTTPServerTransport } from \"@modelcontextprotocol/sdk/server/streamableHttp.js\";\nimport type { McpServer } from \"@modelcontextprotocol/sdk/server/mcp.js\";\n\n/**\n * Session manager for stateful MCP connections.\n * Stores active transports by session ID.\n */\nexport class McpSessionManager {\n  private transports = new Map<string, StreamableHTTPServerTransport>();\n\n  /**\n   * Get an existing transport by session ID.\n   */\n  get(sessionId: string): StreamableHTTPServerTransport | undefined {\n    return this.transports.get(sessionId);\n  }\n\n  /**\n   * Store a transport with its session ID.\n   */\n  set(sessionId: string, transport: StreamableHTTPServerTransport): void {\n    this.transports.set(sessionId, transport);\n  }\n\n  /**\n   * Remove a transport by session ID.\n   */\n  delete(sessionId: string): boolean {\n    return this.transports.delete(sessionId);\n  }\n\n  /**\n   * Check if a session exists.\n   */\n  has(sessionId: string): boolean {\n    return this.transports.has(sessionId);\n  }\n\n  /**\n   * Get all active session IDs.\n   */\n  getSessionIds(): string[] {\n    return Array.from(this.transports.keys());\n  }\n\n  /**\n   * Clear all sessions.\n   */\n  clear(): void {\n    this.transports.clear();\n  }\n}\n\n/**\n * Options for creating an HTTP handler.\n */\nexport interface HttpHandlerOptions {\n  /**\n   * Factory function to create a new MCP server for each session.\n   * This allows per-session customization (e.g., different auth contexts).\n   */\n  createServer: () => McpServer | Promise<McpServer>;\n\n  /**\n   * Session manager for stateful connections.\n   * If not provided, a new one will be created.\n   */\n  sessionManager?: McpSessionManager;\n\n  /**\n   * Optional callback when a new session is created.\n   */\n  onSessionCreated?: (sessionId: string) => void;\n\n  /**\n   * Optional callback when a session is closed.\n   */\n  onSessionClosed?: (sessionId: string) => void;\n\n  /**\n   * Optional error handler.\n   */\n  onError?: (error: unknown) => void;\n}\n\n/**\n * Create HTTP request handlers for the MCP server.\n * Returns handlers for POST, GET, and DELETE methods.\n */\nexport function createHttpHandlers(options: HttpHandlerOptions) {\n  const {\n    createServer,\n    sessionManager = new McpSessionManager(),\n    onSessionCreated,\n    onSessionClosed,\n    onError,\n  } = options;\n\n  /**\n   * Handle POST requests (main MCP messages).\n   */\n  async function handlePost(req: Request, res: Response): Promise<void> {\n    const sessionId = req.headers[\"mcp-session-id\"] as string | undefined;\n    let transport: StreamableHTTPServerTransport;\n\n    if (sessionId && sessionManager.has(sessionId)) {\n      // Reuse existing transport for stateful session\n      transport = sessionManager.get(sessionId)!;\n    } else {\n      // Create new transport for this request\n      transport = new StreamableHTTPServerTransport({\n        sessionIdGenerator: () => crypto.randomUUID(),\n        onsessioninitialized: (newSessionId) => {\n          sessionManager.set(newSessionId, transport);\n          onSessionCreated?.(newSessionId);\n        },\n      });\n\n      // Create and connect server\n      const server = await createServer();\n\n      // Clean up on close\n      transport.onclose = () => {\n        const sid = transport.sessionId;\n        if (sid) {\n          sessionManager.delete(sid);\n          onSessionClosed?.(sid);\n        }\n      };\n\n      await server.connect(transport);\n    }\n\n    // Handle the request\n    try {\n      await transport.handleRequest(req, res, req.body);\n    } catch (error) {\n      onError?.(error);\n      if (!res.headersSent) {\n        res.status(500).json({ error: \"Internal server error\" });\n      }\n    }\n  }\n\n  /**\n   * Handle GET requests (SSE streaming).\n   */\n  async function handleGet(req: Request, res: Response): Promise<void> {\n    const sessionId = req.headers[\"mcp-session-id\"] as string | undefined;\n\n    if (!sessionId || !sessionManager.has(sessionId)) {\n      res.status(400).json({ error: \"Invalid or missing session ID\" });\n      return;\n    }\n\n    const transport = sessionManager.get(sessionId)!;\n\n    try {\n      await transport.handleRequest(req, res);\n    } catch (error) {\n      onError?.(error);\n      if (!res.headersSent) {\n        res.status(500).json({ error: \"Internal server error\" });\n      }\n    }\n  }\n\n  /**\n   * Handle DELETE requests (session termination).\n   */\n  async function handleDelete(req: Request, res: Response): Promise<void> {\n    const sessionId = req.headers[\"mcp-session-id\"] as string | undefined;\n\n    if (!sessionId) {\n      res.status(400).json({ error: \"Missing session ID\" });\n      return;\n    }\n\n    if (sessionManager.has(sessionId)) {\n      const transport = sessionManager.get(sessionId)!;\n      await transport.close();\n      sessionManager.delete(sessionId);\n      onSessionClosed?.(sessionId);\n    }\n\n    res.status(204).send();\n  }\n\n  return {\n    handlePost,\n    handleGet,\n    handleDelete,\n    sessionManager,\n  };\n}\n\n// Re-export for convenience\nexport { StreamableHTTPServerTransport };\n"],"mappings":";;;;;;;AAaA,IAAa,oBAAb,MAA+B;CAC7B,AAAQ,6BAAa,IAAI,KAA4C;;;;CAKrE,IAAI,WAA8D;AAChE,SAAO,KAAK,WAAW,IAAI,UAAU;;;;;CAMvC,IAAI,WAAmB,WAAgD;AACrE,OAAK,WAAW,IAAI,WAAW,UAAU;;;;;CAM3C,OAAO,WAA4B;AACjC,SAAO,KAAK,WAAW,OAAO,UAAU;;;;;CAM1C,IAAI,WAA4B;AAC9B,SAAO,KAAK,WAAW,IAAI,UAAU;;;;;CAMvC,gBAA0B;AACxB,SAAO,MAAM,KAAK,KAAK,WAAW,MAAM,CAAC;;;;;CAM3C,QAAc;AACZ,OAAK,WAAW,OAAO;;;;;;;AAwC3B,SAAgB,mBAAmB,SAA6B;CAC9D,MAAM,EACJ,cACA,iBAAiB,IAAI,mBAAmB,EACxC,kBACA,iBACA,YACE;;;;CAKJ,eAAe,WAAW,KAAc,KAA8B;EACpE,MAAM,YAAY,IAAI,QAAQ;EAC9B,IAAIA;AAEJ,MAAI,aAAa,eAAe,IAAI,UAAU,CAE5C,aAAY,eAAe,IAAI,UAAU;OACpC;AAEL,eAAY,IAAI,8BAA8B;IAC5C,0BAA0B,OAAO,YAAY;IAC7C,uBAAuB,iBAAiB;AACtC,oBAAe,IAAI,cAAc,UAAU;AAC3C,wBAAmB,aAAa;;IAEnC,CAAC;GAGF,MAAM,SAAS,MAAM,cAAc;AAGnC,aAAU,gBAAgB;IACxB,MAAM,MAAM,UAAU;AACtB,QAAI,KAAK;AACP,oBAAe,OAAO,IAAI;AAC1B,uBAAkB,IAAI;;;AAI1B,SAAM,OAAO,QAAQ,UAAU;;AAIjC,MAAI;AACF,SAAM,UAAU,cAAc,KAAK,KAAK,IAAI,KAAK;WAC1C,OAAO;AACd,aAAU,MAAM;AAChB,OAAI,CAAC,IAAI,YACP,KAAI,OAAO,IAAI,CAAC,KAAK,EAAE,OAAO,yBAAyB,CAAC;;;;;;CAQ9D,eAAe,UAAU,KAAc,KAA8B;EACnE,MAAM,YAAY,IAAI,QAAQ;AAE9B,MAAI,CAAC,aAAa,CAAC,eAAe,IAAI,UAAU,EAAE;AAChD,OAAI,OAAO,IAAI,CAAC,KAAK,EAAE,OAAO,iCAAiC,CAAC;AAChE;;EAGF,MAAM,YAAY,eAAe,IAAI,UAAU;AAE/C,MAAI;AACF,SAAM,UAAU,cAAc,KAAK,IAAI;WAChC,OAAO;AACd,aAAU,MAAM;AAChB,OAAI,CAAC,IAAI,YACP,KAAI,OAAO,IAAI,CAAC,KAAK,EAAE,OAAO,yBAAyB,CAAC;;;;;;CAQ9D,eAAe,aAAa,KAAc,KAA8B;EACtE,MAAM,YAAY,IAAI,QAAQ;AAE9B,MAAI,CAAC,WAAW;AACd,OAAI,OAAO,IAAI,CAAC,KAAK,EAAE,OAAO,sBAAsB,CAAC;AACrD;;AAGF,MAAI,eAAe,IAAI,UAAU,EAAE;AAEjC,SADkB,eAAe,IAAI,UAAU,CAC/B,OAAO;AACvB,kBAAe,OAAO,UAAU;AAChC,qBAAkB,UAAU;;AAG9B,MAAI,OAAO,IAAI,CAAC,MAAM;;AAGxB,QAAO;EACL;EACA;EACA;EACA;EACD"}