UNPKG

clinicaltrialsgov-mcp-server

Version:

ClinicalTrials.gov Model Context Protocol (MCP) Server that provides a suite of tools for interacting with the official ClinicalTrials.gov v2 API. Enables AI agents and LLMs to programmatically search, retrieve, and analyze clinical trial data.

93 lines (92 loc) 3.93 kB
/** * @fileoverview Stateless Transport Manager implementation for MCP SDK. * This manager handles single-request operations without maintaining sessions. * Each request creates a temporary server instance that is cleaned up immediately. * This version is adapted for Hono by bridging the SDK's Node.js-style * request handling with Hono's stream-based response model. * @module src/mcp-server/transports/core/statelessTransportManager */ import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js"; import { Readable } from "stream"; import { ErrorHandler, logger, requestContextService, } from "../../../utils/index.js"; import { BaseTransportManager } from "./baseTransportManager.js"; import { HonoStreamResponse } from "./honoNodeBridge.js"; /** * Stateless Transport Manager that handles ephemeral MCP operations. */ export class StatelessTransportManager extends BaseTransportManager { async handleRequest(headers, body, context) { const opContext = { ...context, operation: "StatelessTransportManager.handleRequest", }; logger.debug("Creating ephemeral server instance for stateless request.", opContext); let server; let transport; try { server = await this.createServerInstanceFn(); transport = new StreamableHTTPServerTransport({ sessionIdGenerator: undefined, onsessioninitialized: undefined, }); await server.connect(transport); logger.debug("Ephemeral server connected to transport.", opContext); const mockReq = { headers, method: "POST", }; const mockRes = new HonoStreamResponse(); await transport.handleRequest(mockReq, mockRes, body); logger.info("Stateless request handled successfully.", opContext); const responseHeaders = new Headers(); for (const [key, value] of Object.entries(mockRes.getHeaders())) { responseHeaders.set(key, Array.isArray(value) ? value.join(", ") : String(value)); } // Bridge the Node.js stream (PassThrough) to a Web Stream (ReadableStream) const webStream = Readable.toWeb(mockRes); return { headers: responseHeaders, statusCode: mockRes.statusCode, stream: webStream, }; } catch (error) { throw ErrorHandler.handleError(error, { operation: "StatelessTransportManager.handleRequest", context: opContext, rethrow: true, }); } finally { if (server || transport) { this.cleanup(server, transport, opContext); } } } async shutdown() { const context = requestContextService.createRequestContext({ operation: "StatelessTransportManager.shutdown", }); logger.info("Stateless transport manager shutdown - no persistent resources to clean up.", context); return Promise.resolve(); } cleanup(server, transport, context) { const opContext = { ...context, operation: "StatelessTransportManager.cleanup", }; logger.debug("Scheduling cleanup for ephemeral resources.", opContext); Promise.all([transport?.close(), server?.close()]) .then(() => { logger.debug("Ephemeral resources cleaned up successfully.", opContext); }) .catch((cleanupError) => { logger.warning("Error during stateless resource cleanup.", { ...opContext, error: cleanupError instanceof Error ? cleanupError.message : String(cleanupError), }); }); } }