Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-opencode) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
102 lines
3.2 KiB
TypeScript
102 lines
3.2 KiB
TypeScript
import { readFile } from "node:fs/promises";
|
|
import type { Server } from "node:http";
|
|
import type { AddressInfo } from "node:net";
|
|
import { dirname, resolve } from "node:path";
|
|
import { fileURLToPath } from "node:url";
|
|
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
|
|
import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
|
|
import { afterEach, describe, expect, it } from "vitest";
|
|
import { EXAMPLE_ECHO_TOOL_NAME } from "../src/capabilities/index.js";
|
|
import { startHttpServer } from "../src/http.js";
|
|
|
|
const testsDir = dirname(fileURLToPath(import.meta.url));
|
|
const projectRoot = resolve(testsDir, "..");
|
|
const packageJsonPath = resolve(projectRoot, "package.json");
|
|
|
|
let activeServer: Server | undefined;
|
|
|
|
afterEach(async () => {
|
|
if (activeServer !== undefined) {
|
|
await new Promise<void>((resolveClose, rejectClose) => {
|
|
activeServer?.close((error) => {
|
|
if (error) {
|
|
rejectClose(error);
|
|
return;
|
|
}
|
|
|
|
resolveClose();
|
|
});
|
|
});
|
|
activeServer = undefined;
|
|
}
|
|
});
|
|
|
|
async function startTestServer(): Promise<URL> {
|
|
activeServer = await startHttpServer({ host: "127.0.0.1", port: 0 });
|
|
const address = activeServer.address() as AddressInfo;
|
|
return new URL(`http://127.0.0.1:${address.port}/mcp`);
|
|
}
|
|
|
|
describe("HTTP entrypoint", () => {
|
|
it("supports streamable HTTP initialize flow and shared capabilities", async () => {
|
|
const endpoint = await startTestServer();
|
|
const transport = new StreamableHTTPClientTransport(endpoint);
|
|
const client = new Client({ name: "http-test-client", version: "0.0.0" }, { capabilities: {} });
|
|
|
|
await client.connect(transport);
|
|
const tools = await client.listTools();
|
|
|
|
expect(tools.tools.map((tool) => tool.name)).toContain(EXAMPLE_ECHO_TOOL_NAME);
|
|
|
|
await client.close();
|
|
await transport.close();
|
|
});
|
|
|
|
it("returns 404 on unknown routes", async () => {
|
|
await startTestServer();
|
|
const address = activeServer?.address() as AddressInfo;
|
|
const response = await fetch(`http://127.0.0.1:${address.port}/not-mcp`);
|
|
|
|
expect(response.status).toBe(404);
|
|
await expect(response.json()).resolves.toMatchObject({
|
|
error: "Not Found",
|
|
});
|
|
});
|
|
|
|
it("returns explicit invalid-session errors", async () => {
|
|
await startTestServer();
|
|
const address = activeServer?.address() as AddressInfo;
|
|
const response = await fetch(`http://127.0.0.1:${address.port}/mcp`, {
|
|
method: "POST",
|
|
headers: {
|
|
"content-type": "application/json",
|
|
"mcp-session-id": "missing-session",
|
|
},
|
|
body: JSON.stringify({
|
|
jsonrpc: "2.0",
|
|
id: "invalid-session",
|
|
method: "ping",
|
|
params: {},
|
|
}),
|
|
});
|
|
|
|
expect(response.status).toBe(404);
|
|
await expect(response.json()).resolves.toMatchObject({
|
|
jsonrpc: "2.0",
|
|
error: {
|
|
message: "Session not found",
|
|
},
|
|
id: null,
|
|
});
|
|
});
|
|
|
|
it("publishes an HTTP entry command", async () => {
|
|
const packageJsonRaw = await readFile(packageJsonPath, "utf8");
|
|
const packageJson = JSON.parse(packageJsonRaw) as {
|
|
scripts?: Record<string, string>;
|
|
};
|
|
|
|
expect(packageJson.scripts?.["dev:http"]).toBe("tsx src/http.ts");
|
|
});
|
|
});
|