From 34b7a782a8006dda7e79130e075702ab64eab6e9 Mon Sep 17 00:00:00 2001 From: Filipe Constantinov Menezes Date: Tue, 15 Jul 2025 16:54:17 +0100 Subject: [PATCH 1/7] refactor: move http server --- package.json | 2 +- src/index.ts | 72 +++---- src/transports/base.ts | 34 ++++ src/transports/stdio.ts | 34 ++++ src/transports/streamableHttp.ts | 196 ++++++++++--------- tests/unit/transports/streamableHttp.test.ts | 75 +++---- 6 files changed, 238 insertions(+), 175 deletions(-) create mode 100644 src/transports/base.ts diff --git a/package.json b/package.json index 9b6347b3..b18fa2cb 100644 --- a/package.json +++ b/package.json @@ -29,7 +29,7 @@ "check:types": "tsc --noEmit --project tsconfig.json", "reformat": "prettier --write .", "generate": "./scripts/generate.sh", - "test": "vitest --coverage" + "test": "vitest --run --coverage" }, "license": "Apache-2.0", "devDependencies": { diff --git a/src/index.ts b/src/index.ts index c5f4ddee..06d6ce13 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,52 +1,23 @@ #!/usr/bin/env node import logger, { LogId } from "./common/logger.js"; -import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; import { config } from "./common/config.js"; -import { Session } from "./common/session.js"; -import { Server } from "./server.js"; -import { packageInfo } from "./common/packageInfo.js"; -import { Telemetry } from "./telemetry/telemetry.js"; -import { createStdioTransport } from "./transports/stdio.js"; -import { createHttpTransport } from "./transports/streamableHttp.js"; +import { StdioRunner } from "./transports/stdio.js"; +import { StreamableHttpRunner } from "./transports/streamableHttp.js"; -try { - const session = new Session({ - apiBaseUrl: config.apiBaseUrl, - apiClientId: config.apiClientId, - apiClientSecret: config.apiClientSecret, - }); +async function main() { + const runner = config.transport === "stdio" ? new StdioRunner() : new StreamableHttpRunner(); - const transport = config.transport === "stdio" ? createStdioTransport() : await createHttpTransport(); - - const telemetry = Telemetry.create(session, config); - - const mcpServer = new McpServer({ - name: packageInfo.mcpServerName, - version: packageInfo.version, - }); - - const server = new Server({ - mcpServer, - session, - telemetry, - userConfig: config, - }); - - const shutdown = () => { + const shutdown = async () => { logger.info(LogId.serverCloseRequested, "server", `Server close requested`); - server - .close() - .then(() => { - logger.info(LogId.serverClosed, "server", `Server closed successfully`); - process.exit(0); - }) - .catch((err: unknown) => { - const error = err instanceof Error ? err : new Error(String(err)); - logger.error(LogId.serverCloseFailure, "server", `Error closing server: ${error.message}`); - process.exit(1); - }); + try { + const exitCode = await runner.close(); + process.exit(exitCode); + } catch (error: unknown) { + logger.error(LogId.serverCloseFailure, "server", `Error closing server: ${error as string}`); + process.exit(1); + } }; process.once("SIGINT", shutdown); @@ -54,8 +25,21 @@ try { process.once("SIGTERM", shutdown); process.once("SIGQUIT", shutdown); - await server.connect(transport); -} catch (error: unknown) { + try { + await runner.run(); + } catch (error: unknown) { + logger.emergency(LogId.serverStartFailure, "server", `Fatal error running server: ${error as string}`); + try { + await runner.close(); + } catch (error: unknown) { + logger.error(LogId.serverCloseFailure, "server", `Error closing server: ${error as string}`); + } finally { + process.exit(1); + } + } +} + +main().catch((error: unknown) => { logger.emergency(LogId.serverStartFailure, "server", `Fatal error running server: ${error as string}`); process.exit(1); -} +}); diff --git a/src/transports/base.ts b/src/transports/base.ts new file mode 100644 index 00000000..42545416 --- /dev/null +++ b/src/transports/base.ts @@ -0,0 +1,34 @@ +import { config } from "../common/config.js"; +import { packageInfo } from "../common/packageInfo.js"; +import { Server } from "../server.js"; +import { Session } from "../common/session.js"; +import { Telemetry } from "../telemetry/telemetry.js"; +import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; + +export abstract class Runner { + protected setupServer(): Server { + const session = new Session({ + apiBaseUrl: config.apiBaseUrl, + apiClientId: config.apiClientId, + apiClientSecret: config.apiClientSecret, + }); + + const telemetry = Telemetry.create(session, config); + + const mcpServer = new McpServer({ + name: packageInfo.mcpServerName, + version: packageInfo.version, + }); + + return new Server({ + mcpServer, + session, + telemetry, + userConfig: config, + }); + } + + abstract run(): Promise; + + abstract close(): Promise; +} diff --git a/src/transports/stdio.ts b/src/transports/stdio.ts index 0f9f4c0c..944797c3 100644 --- a/src/transports/stdio.ts +++ b/src/transports/stdio.ts @@ -1,3 +1,6 @@ +import logger, { LogId } from "../common/logger.js"; +import { Server } from "../server.js"; +import { Runner } from "./base.js"; import { JSONRPCMessage, JSONRPCMessageSchema } from "@modelcontextprotocol/sdk/types.js"; import { EJSON } from "bson"; import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; @@ -45,3 +48,34 @@ export function createStdioTransport(): StdioServerTransport { return server; } + +export class StdioRunner extends Runner { + private server: Server | undefined; + + async run() { + try { + this.server = this.setupServer(); + + const transport = createStdioTransport(); + + await this.server.connect(transport); + } catch (error: unknown) { + logger.emergency(LogId.serverStartFailure, "server", `Fatal error running server: ${error as string}`); + process.exit(1); + } + } + + async close(): Promise { + logger.info(LogId.serverCloseRequested, "server", `Server close requested`); + + try { + await this.server?.close(); + logger.info(LogId.serverClosed, "server", `Server closed successfully`); + return 0; + } catch (error: unknown) { + const err = error instanceof Error ? error : new Error(String(error)); + logger.error(LogId.serverCloseFailure, "server", `Error closing server: ${err.message}`); + return 1; + } + } +} diff --git a/src/transports/streamableHttp.ts b/src/transports/streamableHttp.ts index bb4d0f06..b909a5f3 100644 --- a/src/transports/streamableHttp.ts +++ b/src/transports/streamableHttp.ts @@ -1,84 +1,109 @@ import express from "express"; import http from "http"; import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js"; - +import { Runner } from "./base.js"; import { config } from "../common/config.js"; import logger, { LogId } from "../common/logger.js"; const JSON_RPC_ERROR_CODE_PROCESSING_REQUEST_FAILED = -32000; +const JSON_RPC_ERROR_CODE_METHOD_NOT_ALLOWED = -32601; -export async function createHttpTransport(): Promise { - const app = express(); - app.enable("trust proxy"); // needed for reverse proxy support - app.use(express.urlencoded({ extended: true })); - app.use(express.json()); +function promiseHandler( + fn: (req: express.Request, res: express.Response, next: express.NextFunction) => Promise +) { + return (req: express.Request, res: express.Response, next: express.NextFunction) => { + fn(req, res, next).catch(next); + }; +} - const transport = new StreamableHTTPServerTransport({ - sessionIdGenerator: undefined, - }); +export class StreamableHttpRunner extends Runner { + private httpServer: http.Server | undefined; - app.post("/mcp", async (req: express.Request, res: express.Response) => { - try { - await transport.handleRequest(req, res, req.body); - } catch (error) { - logger.error( - LogId.streamableHttpTransportRequestFailure, - "streamableHttpTransport", - `Error handling request: ${error instanceof Error ? error.message : String(error)}` - ); - res.status(400).json({ - jsonrpc: "2.0", - error: { - code: JSON_RPC_ERROR_CODE_PROCESSING_REQUEST_FAILED, - message: `failed to handle request`, - data: error instanceof Error ? error.message : String(error), - }, - }); - } - }); + async run() { + const app = express(); + app.enable("trust proxy"); // needed for reverse proxy support + app.use(express.urlencoded({ extended: true })); + app.use(express.json()); - app.get("/mcp", async (req: express.Request, res: express.Response) => { - try { - await transport.handleRequest(req, res, req.body); - } catch (error) { - logger.error( - LogId.streamableHttpTransportRequestFailure, - "streamableHttpTransport", - `Error handling request: ${error instanceof Error ? error.message : String(error)}` - ); - res.status(400).json({ - jsonrpc: "2.0", - error: { - code: JSON_RPC_ERROR_CODE_PROCESSING_REQUEST_FAILED, - message: `failed to handle request`, - data: error instanceof Error ? error.message : String(error), - }, - }); - } - }); + app.post( + "/mcp", + promiseHandler(async (req: express.Request, res: express.Response) => { + const transport = new StreamableHTTPServerTransport({ + sessionIdGenerator: undefined, + }); - app.delete("/mcp", async (req: express.Request, res: express.Response) => { - try { - await transport.handleRequest(req, res, req.body); - } catch (error) { - logger.error( - LogId.streamableHttpTransportRequestFailure, - "streamableHttpTransport", - `Error handling request: ${error instanceof Error ? error.message : String(error)}` - ); - res.status(400).json({ - jsonrpc: "2.0", - error: { - code: JSON_RPC_ERROR_CODE_PROCESSING_REQUEST_FAILED, - message: `failed to handle request`, - data: error instanceof Error ? error.message : String(error), - }, - }); - } - }); + const server = this.setupServer(); + + await server.connect(transport); + + res.on("close", async () => { + try { + await transport.close(); + } catch (error: unknown) { + logger.error( + LogId.streamableHttpTransportCloseFailure, + "streamableHttpTransport", + `Error closing transport: ${error instanceof Error ? error.message : String(error)}` + ); + } + try { + await server.close(); + } catch (error: unknown) { + logger.error( + LogId.streamableHttpTransportCloseFailure, + "streamableHttpTransport", + `Error closing server: ${error instanceof Error ? error.message : String(error)}` + ); + } + }); + + try { + await transport.handleRequest(req, res, req.body); + } catch (error) { + logger.error( + LogId.streamableHttpTransportRequestFailure, + "streamableHttpTransport", + `Error handling request: ${error instanceof Error ? error.message : String(error)}` + ); + res.status(400).json({ + jsonrpc: "2.0", + error: { + code: JSON_RPC_ERROR_CODE_PROCESSING_REQUEST_FAILED, + message: `failed to handle request`, + data: error instanceof Error ? error.message : String(error), + }, + }); + } + }) + ); - try { - const server = await new Promise((resolve, reject) => { + app.get( + "/mcp", + promiseHandler(async (req: express.Request, res: express.Response) => { + res.status(405).json({ + jsonrpc: "2.0", + error: { + code: JSON_RPC_ERROR_CODE_METHOD_NOT_ALLOWED, + message: `method not allowed`, + }, + }); + }) + ); + + app.delete( + "/mcp", + promiseHandler(async (req: express.Request, res: express.Response) => { + res.status(405).json({ + jsonrpc: "2.0", + error: { + code: JSON_RPC_ERROR_CODE_METHOD_NOT_ALLOWED, + message: `method not allowed`, + }, + }); + }) + ); + + this.httpServer = await new Promise((resolve, reject) => { const result = app.listen(config.httpPort, config.httpHost, (err?: Error) => { if (err) { reject(err); @@ -93,31 +118,16 @@ export async function createHttpTransport(): Promise { - logger.info(LogId.streamableHttpTransportCloseRequested, "streamableHttpTransport", `Closing server`); - server.close((err?: Error) => { - if (err) { - logger.error( - LogId.streamableHttpTransportCloseFailure, - "streamableHttpTransport", - `Error closing server: ${err.message}` - ); - return; - } - logger.info(LogId.streamableHttpTransportCloseSuccess, "streamableHttpTransport", `Server closed`); - }); - }; - - return transport; - } catch (error: unknown) { - const err = error instanceof Error ? error : new Error(String(error)); - logger.info( - LogId.streamableHttpTransportStartFailure, - "streamableHttpTransport", - `Error starting server: ${err.message}` - ); - - throw err; + async close(): Promise { + try { + await this.httpServer?.close(); + return 0; + } catch (error: unknown) { + const err = error instanceof Error ? error : new Error(String(error)); + logger.error(LogId.serverCloseFailure, "server", `Error closing server: ${err.message}`); + return 1; + } } } diff --git a/tests/unit/transports/streamableHttp.test.ts b/tests/unit/transports/streamableHttp.test.ts index 01eeb136..9a2a700e 100644 --- a/tests/unit/transports/streamableHttp.test.ts +++ b/tests/unit/transports/streamableHttp.test.ts @@ -1,34 +1,21 @@ -import { createHttpTransport } from "../../../src/transports/streamableHttp.js"; +import { StreamableHttpRunner } from "../../../src/transports/streamableHttp.js"; import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js"; import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js"; import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; import { JSONRPCMessage } from "@modelcontextprotocol/sdk/types.js"; import { z } from "zod"; import { describe, expect, it, beforeAll, afterAll } from "vitest"; + describe("streamableHttpTransport", () => { - let transport: StreamableHTTPServerTransport; - const mcpServer = new McpServer({ - name: "test", - version: "1.0.0", - }); + let runner: StreamableHttpRunner; + beforeAll(async () => { - transport = await createHttpTransport(); - mcpServer.registerTool( - "hello", - { - title: "Hello Tool", - description: "Say hello", - inputSchema: { name: z.string() }, - }, - ({ name }) => ({ - content: [{ type: "text", text: `Hello, ${name}!` }], - }) - ); - await mcpServer.connect(transport); + runner = new StreamableHttpRunner(); + void runner.run(); }); afterAll(async () => { - await mcpServer.close(); + await runner.close(); }); describe("client connects successfully", () => { @@ -43,24 +30,13 @@ describe("streamableHttpTransport", () => { }); it("handles requests and sends responses", async () => { - client.onmessage = (message: JSONRPCMessage) => { - const messageResult = message as - | { - result?: { - tools: { - name: string; - description: string; - }[]; - }; - } - | undefined; + let fixedResolve: ((value: JSONRPCMessage) => void) | undefined = undefined; + const messagePromise = new Promise((resolve) => { + fixedResolve = resolve; + }); - expect(message.jsonrpc).toBe("2.0"); - expect(messageResult).toBeDefined(); - expect(messageResult?.result?.tools).toBeDefined(); - expect(messageResult?.result?.tools.length).toBe(1); - expect(messageResult?.result?.tools[0]?.name).toBe("hello"); - expect(messageResult?.result?.tools[0]?.description).toBe("Say hello"); + client.onmessage = (message: JSONRPCMessage) => { + fixedResolve?.(message); }; await client.send({ @@ -73,6 +49,31 @@ describe("streamableHttpTransport", () => { }, }, }); + + const message = (await messagePromise) as { + jsonrpc: string; + id: number; + result: { + tools: { + name: string; + description: string; + }[]; + }; + error?: { + code: number; + message: string; + }; + }; + + expect(message.jsonrpc).toBe("2.0"); + expect(message.id).toBe(1); + expect(message.result).toBeDefined(); + expect(message.result?.tools).toBeDefined(); + expect(message.result?.tools.length).toBeGreaterThan(0); + const tools = message.result?.tools; + tools.sort((a, b) => a.name.localeCompare(b.name)); + expect(tools[0]?.name).toBe("aggregate"); + expect(tools[0]?.description).toBe("Run an aggregation against a MongoDB collection"); }); }); }); From dad53e9521d7e37fdebc3726fbc54a41b44dd564 Mon Sep 17 00:00:00 2001 From: Filipe Constantinov Menezes Date: Tue, 15 Jul 2025 17:00:16 +0100 Subject: [PATCH 2/7] move tests --- src/index.ts | 18 +++-- src/transports/base.ts | 2 +- src/transports/stdio.ts | 14 +--- src/transports/streamableHttp.ts | 78 ++++++++----------- tests/integration/transports/stdio.test.ts | 70 +++++++++++++++++ .../transports/streamableHttp.test.ts | 7 +- tests/unit/transports/stdio.test.ts | 6 +- 7 files changed, 119 insertions(+), 76 deletions(-) create mode 100644 tests/integration/transports/stdio.test.ts rename tests/{unit => integration}/transports/streamableHttp.test.ts (90%) diff --git a/src/index.ts b/src/index.ts index 06d6ce13..ad26ec31 100644 --- a/src/index.ts +++ b/src/index.ts @@ -8,16 +8,18 @@ import { StreamableHttpRunner } from "./transports/streamableHttp.js"; async function main() { const runner = config.transport === "stdio" ? new StdioRunner() : new StreamableHttpRunner(); - const shutdown = async () => { + const shutdown = () => { logger.info(LogId.serverCloseRequested, "server", `Server close requested`); - try { - const exitCode = await runner.close(); - process.exit(exitCode); - } catch (error: unknown) { - logger.error(LogId.serverCloseFailure, "server", `Error closing server: ${error as string}`); - process.exit(1); - } + runner + .close() + .then(() => { + process.exit(0); + }) + .catch((error: unknown) => { + logger.error(LogId.serverCloseFailure, "server", `Error closing server: ${error as string}`); + process.exit(1); + }); }; process.once("SIGINT", shutdown); diff --git a/src/transports/base.ts b/src/transports/base.ts index 42545416..b6c44dfd 100644 --- a/src/transports/base.ts +++ b/src/transports/base.ts @@ -30,5 +30,5 @@ export abstract class Runner { abstract run(): Promise; - abstract close(): Promise; + abstract close(): Promise; } diff --git a/src/transports/stdio.ts b/src/transports/stdio.ts index 944797c3..d9f5206a 100644 --- a/src/transports/stdio.ts +++ b/src/transports/stdio.ts @@ -65,17 +65,7 @@ export class StdioRunner extends Runner { } } - async close(): Promise { - logger.info(LogId.serverCloseRequested, "server", `Server close requested`); - - try { - await this.server?.close(); - logger.info(LogId.serverClosed, "server", `Server closed successfully`); - return 0; - } catch (error: unknown) { - const err = error instanceof Error ? error : new Error(String(error)); - logger.error(LogId.serverCloseFailure, "server", `Error closing server: ${err.message}`); - return 1; - } + async close(): Promise { + await this.server?.close(); } } diff --git a/src/transports/streamableHttp.ts b/src/transports/streamableHttp.ts index b909a5f3..f32059ea 100644 --- a/src/transports/streamableHttp.ts +++ b/src/transports/streamableHttp.ts @@ -36,25 +36,14 @@ export class StreamableHttpRunner extends Runner { await server.connect(transport); - res.on("close", async () => { - try { - await transport.close(); - } catch (error: unknown) { - logger.error( - LogId.streamableHttpTransportCloseFailure, - "streamableHttpTransport", - `Error closing transport: ${error instanceof Error ? error.message : String(error)}` - ); - } - try { - await server.close(); - } catch (error: unknown) { + res.on("close", () => { + Promise.all([transport.close(), server.close()]).catch((error: unknown) => { logger.error( LogId.streamableHttpTransportCloseFailure, "streamableHttpTransport", `Error closing server: ${error instanceof Error ? error.message : String(error)}` ); - } + }); }); try { @@ -77,31 +66,25 @@ export class StreamableHttpRunner extends Runner { }) ); - app.get( - "/mcp", - promiseHandler(async (req: express.Request, res: express.Response) => { - res.status(405).json({ - jsonrpc: "2.0", - error: { - code: JSON_RPC_ERROR_CODE_METHOD_NOT_ALLOWED, - message: `method not allowed`, - }, - }); - }) - ); + app.get("/mcp", (req: express.Request, res: express.Response) => { + res.status(405).json({ + jsonrpc: "2.0", + error: { + code: JSON_RPC_ERROR_CODE_METHOD_NOT_ALLOWED, + message: `method not allowed`, + }, + }); + }); - app.delete( - "/mcp", - promiseHandler(async (req: express.Request, res: express.Response) => { - res.status(405).json({ - jsonrpc: "2.0", - error: { - code: JSON_RPC_ERROR_CODE_METHOD_NOT_ALLOWED, - message: `method not allowed`, - }, - }); - }) - ); + app.delete("/mcp", (req: express.Request, res: express.Response) => { + res.status(405).json({ + jsonrpc: "2.0", + error: { + code: JSON_RPC_ERROR_CODE_METHOD_NOT_ALLOWED, + message: `method not allowed`, + }, + }); + }); this.httpServer = await new Promise((resolve, reject) => { const result = app.listen(config.httpPort, config.httpHost, (err?: Error) => { @@ -120,14 +103,15 @@ export class StreamableHttpRunner extends Runner { ); } - async close(): Promise { - try { - await this.httpServer?.close(); - return 0; - } catch (error: unknown) { - const err = error instanceof Error ? error : new Error(String(error)); - logger.error(LogId.serverCloseFailure, "server", `Error closing server: ${err.message}`); - return 1; - } + async close(): Promise { + await new Promise((resolve, reject) => { + this.httpServer?.close((err) => { + if (err) { + reject(err); + return; + } + resolve(); + }); + }); } } diff --git a/tests/integration/transports/stdio.test.ts b/tests/integration/transports/stdio.test.ts new file mode 100644 index 00000000..afbcce00 --- /dev/null +++ b/tests/integration/transports/stdio.test.ts @@ -0,0 +1,70 @@ +import { JSONRPCMessage } from "@modelcontextprotocol/sdk/types.js"; +import { describe, expect, it, beforeAll, afterAll } from "vitest"; +import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js"; + +describe("StdioRunner", () => { + describe("client connects successfully", () => { + let client: StdioClientTransport; + beforeAll(async () => { + client = new StdioClientTransport({ + command: "node", + args: ["dist/index.js"], + env: { + MDB_MCP_TRANSPORT: "stdio", + }, + }); + await client.start(); + }); + + afterAll(async () => { + await client.close(); + }); + + it("handles requests and sends responses", async () => { + let fixedResolve: ((value: JSONRPCMessage) => void) | undefined = undefined; + const messagePromise = new Promise((resolve) => { + fixedResolve = resolve; + }); + + client.onmessage = (message: JSONRPCMessage) => { + fixedResolve?.(message); + }; + + await client.send({ + jsonrpc: "2.0", + id: 1, + method: "tools/list", + params: { + _meta: { + progressToken: 1, + }, + }, + }); + + const message = (await messagePromise) as { + jsonrpc: string; + id: number; + result: { + tools: { + name: string; + description: string; + }[]; + }; + error?: { + code: number; + message: string; + }; + }; + + expect(message.jsonrpc).toBe("2.0"); + expect(message.id).toBe(1); + expect(message.result).toBeDefined(); + expect(message.result?.tools).toBeDefined(); + expect(message.result?.tools.length).toBeGreaterThan(0); + const tools = message.result?.tools; + tools.sort((a, b) => a.name.localeCompare(b.name)); + expect(tools[0]?.name).toBe("aggregate"); + expect(tools[0]?.description).toBe("Run an aggregation against a MongoDB collection"); + }); + }); +}); diff --git a/tests/unit/transports/streamableHttp.test.ts b/tests/integration/transports/streamableHttp.test.ts similarity index 90% rename from tests/unit/transports/streamableHttp.test.ts rename to tests/integration/transports/streamableHttp.test.ts index 9a2a700e..1b3b17e9 100644 --- a/tests/unit/transports/streamableHttp.test.ts +++ b/tests/integration/transports/streamableHttp.test.ts @@ -1,15 +1,12 @@ import { StreamableHttpRunner } from "../../../src/transports/streamableHttp.js"; -import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js"; import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js"; -import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; import { JSONRPCMessage } from "@modelcontextprotocol/sdk/types.js"; -import { z } from "zod"; import { describe, expect, it, beforeAll, afterAll } from "vitest"; -describe("streamableHttpTransport", () => { +describe("StreamableHttpRunner", () => { let runner: StreamableHttpRunner; - beforeAll(async () => { + beforeAll(() => { runner = new StreamableHttpRunner(); void runner.run(); }); diff --git a/tests/unit/transports/stdio.test.ts b/tests/unit/transports/stdio.test.ts index 2a1c62de..0311f980 100644 --- a/tests/unit/transports/stdio.test.ts +++ b/tests/unit/transports/stdio.test.ts @@ -1,12 +1,12 @@ import { Decimal128, MaxKey, MinKey, ObjectId, Timestamp, UUID } from "bson"; -import { createStdioTransport, EJsonReadBuffer } from "../../../src/transports/stdio.js"; +import { createStdioTransport, EJsonReadBuffer, StdioRunner } from "../../../src/transports/stdio.js"; import { JSONRPCMessage } from "@modelcontextprotocol/sdk/types.js"; import { AuthInfo } from "@modelcontextprotocol/sdk/server/auth/types.js"; import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; import { Readable } from "stream"; import { ReadBuffer } from "@modelcontextprotocol/sdk/shared/stdio.js"; -import { describe, expect, it, beforeEach, afterEach } from "vitest"; - +import { describe, expect, it, beforeEach, afterEach, beforeAll, afterAll } from "vitest"; +import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js"; describe("stdioTransport", () => { let transport: StdioServerTransport; beforeEach(async () => { From dc15a80e7eba82e5d0470f7af17b8427a19f304f Mon Sep 17 00:00:00 2001 From: Filipe Constantinov Menezes Date: Tue, 15 Jul 2025 17:03:53 +0100 Subject: [PATCH 3/7] fix: format --- tests/unit/transports/stdio.test.ts | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/tests/unit/transports/stdio.test.ts b/tests/unit/transports/stdio.test.ts index 0311f980..6a53f67b 100644 --- a/tests/unit/transports/stdio.test.ts +++ b/tests/unit/transports/stdio.test.ts @@ -1,12 +1,11 @@ import { Decimal128, MaxKey, MinKey, ObjectId, Timestamp, UUID } from "bson"; -import { createStdioTransport, EJsonReadBuffer, StdioRunner } from "../../../src/transports/stdio.js"; +import { createStdioTransport, EJsonReadBuffer } from "../../../src/transports/stdio.js"; import { JSONRPCMessage } from "@modelcontextprotocol/sdk/types.js"; import { AuthInfo } from "@modelcontextprotocol/sdk/server/auth/types.js"; import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; import { Readable } from "stream"; import { ReadBuffer } from "@modelcontextprotocol/sdk/shared/stdio.js"; -import { describe, expect, it, beforeEach, afterEach, beforeAll, afterAll } from "vitest"; -import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js"; +import { describe, expect, it, beforeEach, afterEach } from "vitest"; describe("stdioTransport", () => { let transport: StdioServerTransport; beforeEach(async () => { From ad3c491a60c713015989d9bd84e4b7ba3244361e Mon Sep 17 00:00:00 2001 From: Filipe Constantinov Menezes Date: Tue, 15 Jul 2025 17:07:36 +0100 Subject: [PATCH 4/7] fix: symbol names --- src/index.ts | 8 ++++---- src/transports/base.ts | 2 +- src/transports/stdio.ts | 4 ++-- src/transports/streamableHttp.ts | 4 ++-- 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/index.ts b/src/index.ts index ad26ec31..780a7631 100644 --- a/src/index.ts +++ b/src/index.ts @@ -6,12 +6,12 @@ import { StdioRunner } from "./transports/stdio.js"; import { StreamableHttpRunner } from "./transports/streamableHttp.js"; async function main() { - const runner = config.transport === "stdio" ? new StdioRunner() : new StreamableHttpRunner(); + const transportRunner = config.transport === "stdio" ? new StdioRunner() : new StreamableHttpRunner(); const shutdown = () => { logger.info(LogId.serverCloseRequested, "server", `Server close requested`); - runner + transportRunner .close() .then(() => { process.exit(0); @@ -28,11 +28,11 @@ async function main() { process.once("SIGQUIT", shutdown); try { - await runner.run(); + await transportRunner.run(); } catch (error: unknown) { logger.emergency(LogId.serverStartFailure, "server", `Fatal error running server: ${error as string}`); try { - await runner.close(); + await transportRunner.close(); } catch (error: unknown) { logger.error(LogId.serverCloseFailure, "server", `Error closing server: ${error as string}`); } finally { diff --git a/src/transports/base.ts b/src/transports/base.ts index b6c44dfd..71837ee6 100644 --- a/src/transports/base.ts +++ b/src/transports/base.ts @@ -5,7 +5,7 @@ import { Session } from "../common/session.js"; import { Telemetry } from "../telemetry/telemetry.js"; import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; -export abstract class Runner { +export abstract class TransportRunnerBase { protected setupServer(): Server { const session = new Session({ apiBaseUrl: config.apiBaseUrl, diff --git a/src/transports/stdio.ts b/src/transports/stdio.ts index d9f5206a..d4c7e3d6 100644 --- a/src/transports/stdio.ts +++ b/src/transports/stdio.ts @@ -1,6 +1,6 @@ import logger, { LogId } from "../common/logger.js"; import { Server } from "../server.js"; -import { Runner } from "./base.js"; +import { TransportRunnerBase } from "./base.js"; import { JSONRPCMessage, JSONRPCMessageSchema } from "@modelcontextprotocol/sdk/types.js"; import { EJSON } from "bson"; import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; @@ -49,7 +49,7 @@ export function createStdioTransport(): StdioServerTransport { return server; } -export class StdioRunner extends Runner { +export class StdioRunner extends TransportRunnerBase { private server: Server | undefined; async run() { diff --git a/src/transports/streamableHttp.ts b/src/transports/streamableHttp.ts index f32059ea..773c6f16 100644 --- a/src/transports/streamableHttp.ts +++ b/src/transports/streamableHttp.ts @@ -1,7 +1,7 @@ import express from "express"; import http from "http"; import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js"; -import { Runner } from "./base.js"; +import { TransportRunnerBase } from "./base.js"; import { config } from "../common/config.js"; import logger, { LogId } from "../common/logger.js"; @@ -16,7 +16,7 @@ function promiseHandler( }; } -export class StreamableHttpRunner extends Runner { +export class StreamableHttpRunner extends TransportRunnerBase { private httpServer: http.Server | undefined; async run() { From fe83f1e88d3d281728bf3b1ddd56e1cd71ae1341 Mon Sep 17 00:00:00 2001 From: Filipe Constantinov Menezes Date: Tue, 15 Jul 2025 18:06:24 +0100 Subject: [PATCH 5/7] fix: log --- src/index.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/index.ts b/src/index.ts index 780a7631..2f0c6259 100644 --- a/src/index.ts +++ b/src/index.ts @@ -33,6 +33,7 @@ async function main() { logger.emergency(LogId.serverStartFailure, "server", `Fatal error running server: ${error as string}`); try { await transportRunner.close(); + logger.error(LogId.serverClosed, "server", "Server closed"); } catch (error: unknown) { logger.error(LogId.serverCloseFailure, "server", `Error closing server: ${error as string}`); } finally { From 35e08129bf461938664dc93d6c877ed481816f49 Mon Sep 17 00:00:00 2001 From: Filipe Constantinov Menezes Date: Tue, 15 Jul 2025 18:09:05 +0100 Subject: [PATCH 6/7] fix: rename run to start --- src/index.ts | 2 +- src/transports/base.ts | 2 +- src/transports/stdio.ts | 2 +- src/transports/streamableHttp.ts | 2 +- tests/integration/transports/streamableHttp.test.ts | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/index.ts b/src/index.ts index 2f0c6259..73457dd6 100644 --- a/src/index.ts +++ b/src/index.ts @@ -28,7 +28,7 @@ async function main() { process.once("SIGQUIT", shutdown); try { - await transportRunner.run(); + await transportRunner.start(); } catch (error: unknown) { logger.emergency(LogId.serverStartFailure, "server", `Fatal error running server: ${error as string}`); try { diff --git a/src/transports/base.ts b/src/transports/base.ts index 71837ee6..442db18a 100644 --- a/src/transports/base.ts +++ b/src/transports/base.ts @@ -28,7 +28,7 @@ export abstract class TransportRunnerBase { }); } - abstract run(): Promise; + abstract start(): Promise; abstract close(): Promise; } diff --git a/src/transports/stdio.ts b/src/transports/stdio.ts index d4c7e3d6..9f18627c 100644 --- a/src/transports/stdio.ts +++ b/src/transports/stdio.ts @@ -52,7 +52,7 @@ export function createStdioTransport(): StdioServerTransport { export class StdioRunner extends TransportRunnerBase { private server: Server | undefined; - async run() { + async start() { try { this.server = this.setupServer(); diff --git a/src/transports/streamableHttp.ts b/src/transports/streamableHttp.ts index 773c6f16..e15af8d5 100644 --- a/src/transports/streamableHttp.ts +++ b/src/transports/streamableHttp.ts @@ -19,7 +19,7 @@ function promiseHandler( export class StreamableHttpRunner extends TransportRunnerBase { private httpServer: http.Server | undefined; - async run() { + async start() { const app = express(); app.enable("trust proxy"); // needed for reverse proxy support app.use(express.urlencoded({ extended: true })); diff --git a/tests/integration/transports/streamableHttp.test.ts b/tests/integration/transports/streamableHttp.test.ts index 1b3b17e9..df21066e 100644 --- a/tests/integration/transports/streamableHttp.test.ts +++ b/tests/integration/transports/streamableHttp.test.ts @@ -8,7 +8,7 @@ describe("StreamableHttpRunner", () => { beforeAll(() => { runner = new StreamableHttpRunner(); - void runner.run(); + void runner.start(); }); afterAll(async () => { From 1af182511acb09ca3f9d0e5244fb3e4220d56f82 Mon Sep 17 00:00:00 2001 From: Filipe Constantinov Menezes Date: Tue, 15 Jul 2025 18:12:24 +0100 Subject: [PATCH 7/7] fix: test --- tests/integration/transports/streamableHttp.test.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/integration/transports/streamableHttp.test.ts b/tests/integration/transports/streamableHttp.test.ts index df21066e..031e7798 100644 --- a/tests/integration/transports/streamableHttp.test.ts +++ b/tests/integration/transports/streamableHttp.test.ts @@ -6,9 +6,9 @@ import { describe, expect, it, beforeAll, afterAll } from "vitest"; describe("StreamableHttpRunner", () => { let runner: StreamableHttpRunner; - beforeAll(() => { + beforeAll(async () => { runner = new StreamableHttpRunner(); - void runner.start(); + await runner.start(); }); afterAll(async () => {