Fixed test failures induced by MCP SDK update (added session id generator to Streamable test server). Cleanup up some port usage to avoid potential issues (always use port 0 for servers). Added stdout/stderr to cli status assertion failures to make CLI test failures easier to debug from test output.

This commit is contained in:
Bob Dickinson 2026-02-05 16:34:25 -08:00
commit ee29c34bd7
3 changed files with 41 additions and 20 deletions

View file

@ -1,18 +1,30 @@
import { expect } from "vitest"; import { expect } from "vitest";
import type { CliResult } from "./cli-runner.js"; import type { CliResult } from "./cli-runner.js";
function formatCliOutput(result: CliResult): string {
const out = result.stdout?.trim() || "(empty)";
const err = result.stderr?.trim() || "(empty)";
return `stdout: ${out}\nstderr: ${err}`;
}
/** /**
* Assert that CLI command succeeded (exit code 0) * Assert that CLI command succeeded (exit code 0)
*/ */
export function expectCliSuccess(result: CliResult) { export function expectCliSuccess(result: CliResult) {
expect(result.exitCode).toBe(0); expect(
result.exitCode,
`CLI exited with code ${result.exitCode}. ${formatCliOutput(result)}`,
).toBe(0);
} }
/** /**
* Assert that CLI command failed (non-zero exit code) * Assert that CLI command failed (non-zero exit code)
*/ */
export function expectCliFailure(result: CliResult) { export function expectCliFailure(result: CliResult) {
expect(result.exitCode).not.toBe(0); expect(
result.exitCode,
`CLI unexpectedly exited with code ${result.exitCode}. ${formatCliOutput(result)}`,
).not.toBe(0);
} }
/** /**

View file

@ -1,7 +1,7 @@
import fs from "fs"; import * as fs from "fs";
import path from "path"; import * as path from "path";
import os from "os"; import * as os from "os";
import crypto from "crypto"; import * as crypto from "crypto";
import { getTestMcpServerCommand } from "./test-server-stdio.js"; import { getTestMcpServerCommand } from "./test-server-stdio.js";
/** /**

View file

@ -6,6 +6,7 @@ import type { Request, Response } from "express";
import express from "express"; import express from "express";
import { createServer as createHttpServer, Server as HttpServer } from "http"; import { createServer as createHttpServer, Server as HttpServer } from "http";
import { createServer as createNetServer } from "net"; import { createServer as createNetServer } from "net";
import { randomUUID } from "crypto";
import * as z from "zod/v4"; import * as z from "zod/v4";
import type { ServerConfig } from "./test-fixtures.js"; import type { ServerConfig } from "./test-fixtures.js";
@ -187,22 +188,24 @@ export class TestServerHttp {
} }
/** /**
* Start the server with the specified transport * Start the server with the specified transport.
* When requestedPort is omitted, uses port 0 so the OS assigns a unique port (avoids EADDRINUSE when tests run in parallel).
*/ */
async start( async start(
transport: "http" | "sse", transport: "http" | "sse",
requestedPort?: number, requestedPort?: number,
): Promise<number> { ): Promise<number> {
const port = requestedPort const port =
? await findAvailablePort(requestedPort) requestedPort !== undefined ? await findAvailablePort(requestedPort) : 0;
: await findAvailablePort(transport === "http" ? 3001 : 3000);
this.url = `http://localhost:${port}`;
if (transport === "http") { if (transport === "http") {
return this.startHttp(port); const actualPort = await this.startHttp(port);
this.url = `http://localhost:${actualPort}`;
return actualPort;
} else { } else {
return this.startSse(port); const actualPort = await this.startSse(port);
this.url = `http://localhost:${actualPort}`;
return actualPort;
} }
} }
@ -213,8 +216,10 @@ export class TestServerHttp {
// Create HTTP server // Create HTTP server
this.httpServer = createHttpServer(app); this.httpServer = createHttpServer(app);
// Create StreamableHTTP transport // Create StreamableHTTP transport (stateful so it can handle multiple requests per session)
this.transport = new StreamableHTTPServerTransport({}); this.transport = new StreamableHTTPServerTransport({
sessionIdGenerator: () => randomUUID(),
});
// Set up Express route to handle MCP requests // Set up Express route to handle MCP requests
app.post("/mcp", async (req: Request, res: Response) => { app.post("/mcp", async (req: Request, res: Response) => {
@ -292,10 +297,12 @@ export class TestServerHttp {
// Connect transport to server // Connect transport to server
await this.mcpServer.connect(this.transport); await this.mcpServer.connect(this.transport);
// Start listening // Start listening (port 0 = OS assigns a unique port)
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
this.httpServer!.listen(port, () => { this.httpServer!.listen(port, () => {
resolve(port); const assignedPort = (this.httpServer!.address() as { port: number })
?.port;
resolve(assignedPort ?? port);
}); });
this.httpServer!.on("error", reject); this.httpServer!.on("error", reject);
}); });
@ -371,10 +378,12 @@ export class TestServerHttp {
// Note: SSE transport is created per request, so we don't store a single instance // Note: SSE transport is created per request, so we don't store a single instance
this.transport = undefined; this.transport = undefined;
// Start listening // Start listening (port 0 = OS assigns a unique port)
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
this.httpServer!.listen(port, () => { this.httpServer!.listen(port, () => {
resolve(port); const assignedPort = (this.httpServer!.address() as { port: number })
?.port;
resolve(assignedPort ?? port);
}); });
this.httpServer!.on("error", reject); this.httpServer!.on("error", reject);
}); });