Skip to content

Commit ba1fca0

Browse files
authored
Merge pull request #11 from false200/fix/windows-ci-proxy-port
fix: use OS-assigned ports in proxy integration tests
2 parents ddc84ec + dd144a8 commit ba1fca0

3 files changed

Lines changed: 23 additions & 12 deletions

File tree

src/proxy.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@ export interface RunProxyOptions {
1919

2020
export interface ProxyHandle {
2121
close: () => Promise<void>;
22+
/** Set when inbound HTTP is enabled; actual bound port (config port 0 → OS-assigned). */
23+
httpPort?: number;
2224
}
2325

2426
export async function runProxy(opts: RunProxyOptions): Promise<ProxyHandle> {
@@ -65,13 +67,15 @@ export async function runProxy(opts: RunProxyOptions): Promise<ProxyHandle> {
6567
const handle = await startStdioServer(aggregator.createServer());
6668
closers.push(handle.close);
6769
}
70+
let httpPort: number | undefined;
6871
if (cfg.inbound.http.enabled) {
6972
const handle = await startHttpServer({
7073
cfg,
7174
createServer: () => aggregator.createServer(),
7275
upstream,
7376
audit,
7477
});
78+
httpPort = handle.port;
7579
closers.push(handle.close);
7680
}
7781

@@ -85,6 +89,7 @@ export async function runProxy(opts: RunProxyOptions): Promise<ProxyHandle> {
8589
);
8690

8791
return {
92+
httpPort,
8893
close: async () => {
8994
for (const c of [...closers].reverse()) {
9095
try {

src/server/http.ts

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ import { child as childLogger } from "../logger.js";
99

1010
export interface InboundHttpHandle {
1111
close: () => Promise<void>;
12+
/** Actual bound port (useful when config port is 0). */
13+
port: number;
1214
}
1315

1416
/**
@@ -29,7 +31,9 @@ export async function startHttpServer(args: {
2931
}): Promise<InboundHttpHandle> {
3032
const { cfg, createServer: createMcp, upstream } = args;
3133
const log = childLogger({ component: "inbound-http" });
32-
const { host, port, path: mcpPath, sessions } = cfg.inbound.http;
34+
const { host, path: mcpPath, sessions } = cfg.inbound.http;
35+
const requestedPort = cfg.inbound.http.port;
36+
let boundPort = requestedPort;
3337

3438
if (sessions !== "stateless") {
3539
log.warn(
@@ -39,7 +43,7 @@ export async function startHttpServer(args: {
3943
}
4044

4145
const handler = async (req: IncomingMessage, res: ServerResponse) => {
42-
const url = new URL(req.url ?? "/", `http://${host}:${port}`);
46+
const url = new URL(req.url ?? "/", `http://${host}:${boundPort}`);
4347

4448
if (url.pathname === "/healthz") {
4549
const upstreams = [...upstream.connections.values()].map((c) => ({
@@ -101,14 +105,19 @@ export async function startHttpServer(args: {
101105

102106
await new Promise<void>((resolve, reject) => {
103107
httpServer.once("error", reject);
104-
httpServer.listen(port, host, () => {
108+
httpServer.listen(requestedPort, host, () => {
105109
httpServer.off("error", reject);
110+
const addr = httpServer.address();
111+
if (addr && typeof addr === "object") {
112+
boundPort = addr.port;
113+
}
106114
resolve();
107115
});
108116
});
109-
log.info({ host, port, path: mcpPath, sessions }, "inbound HTTP transport listening");
117+
log.info({ host, port: boundPort, path: mcpPath, sessions }, "inbound HTTP transport listening");
110118

111119
return {
120+
port: boundPort,
112121
close: () =>
113122
new Promise<void>((resolve) => {
114123
httpServer.close(() => resolve());

tests/integration/proxy.test.ts

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -32,18 +32,19 @@ async function startHarness(opts: {
3232
servers: Record<string, ReturnType<typeof echoStdioConfig>>;
3333
filters?: { allow?: string[]; deny?: string[] };
3434
shrink?: { mode?: "off" | "rules" | "llm" };
35-
port?: number;
3635
}): Promise<ProxyHarness> {
37-
const port = opts.port ?? randomPort();
3836
const cfg = buildTestConfig({
3937
servers: opts.servers,
4038
filters: opts.filters,
4139
shrink: opts.shrink,
4240
inboundHttp: true,
4341
});
44-
cfg.inbound.http.port = port;
42+
// Port 0 → OS picks a free port. Random high ports flake on Windows CI (EACCES).
43+
cfg.inbound.http.port = 0;
4544
cfg.inbound.http.host = "127.0.0.1";
4645
const handle = await runProxy({ cfg, disableInboundStdio: true });
46+
const port = handle.httpPort;
47+
if (!port) throw new Error("proxy HTTP port not available");
4748

4849
const url = `http://127.0.0.1:${port}/mcp`;
4950
const client = new Client({ name: "test-client", version: "0.0.1" }, { capabilities: {} });
@@ -55,11 +56,7 @@ async function startHarness(opts: {
5556
return harness;
5657
}
5758

58-
function randomPort(): number {
59-
return 30_000 + Math.floor(Math.random() * 20_000);
60-
}
61-
62-
describe("end-to-end proxy", () => {
59+
describe.sequential("end-to-end proxy", () => {
6360
it(
6461
"merges tool lists from two upstream stdio servers under namespaced names",
6562
async () => {

0 commit comments

Comments
 (0)