51 lines
2.8 KiB
JavaScript
51 lines
2.8 KiB
JavaScript
import test from "node:test";
|
|
import assert from "node:assert/strict";
|
|
import { startStreamableHttpServer } from "../src/streamable-http.js";
|
|
|
|
const token = "a".repeat(32);
|
|
|
|
test("Streamable HTTP requires auth, binds sessions to identities, and supports deletion", async (t) => {
|
|
let listener;
|
|
try {
|
|
listener = await startStreamableHttpServer({
|
|
serverName: "test", serverVersion: "1", host: "127.0.0.1", port: 0,
|
|
apiKeys: [`one=${token}`, `two=${"b".repeat(32)}`],
|
|
tools: [{ name: "echo", inputSchema: { type: "object" } }],
|
|
callTool: async (_name, args, context) => ({ ...args, subject: context.subject })
|
|
});
|
|
} catch (error) {
|
|
if (error?.code === "EPERM") return t.skip("TCP listeners unavailable");
|
|
throw error;
|
|
}
|
|
const url = `http://${listener.host}:${listener.port}/mcp`;
|
|
try {
|
|
assert.equal((await fetch(url, { method: "POST", body: "{}" })).status, 401);
|
|
const initialized = await fetch(url, {
|
|
method: "POST", headers: { authorization: `Bearer ${token}`, "content-type": "application/json" },
|
|
body: JSON.stringify({ jsonrpc: "2.0", id: 1, method: "initialize", params: { protocolVersion: "2025-03-26" } })
|
|
});
|
|
assert.equal(initialized.status, 200);
|
|
const sessionId = initialized.headers.get("mcp-session-id");
|
|
assert.ok(sessionId);
|
|
const wrongIdentity = await fetch(url, { method: "POST", headers: { authorization: `Bearer ${"b".repeat(32)}`, "content-type": "application/json", "mcp-session-id": sessionId }, body: JSON.stringify({ jsonrpc: "2.0", id: 2, method: "ping" }) });
|
|
assert.equal(wrongIdentity.status, 404);
|
|
const called = await fetch(url, { method: "POST", headers: { authorization: `Bearer ${token}`, "content-type": "application/json", "mcp-session-id": sessionId }, body: JSON.stringify({ jsonrpc: "2.0", id: 3, method: "tools/call", params: { name: "echo", arguments: { ok: true } } }) });
|
|
assert.equal((await called.json()).result.structuredContent.subject, "one");
|
|
assert.equal((await fetch(url, { method: "DELETE", headers: { authorization: `Bearer ${token}`, "mcp-session-id": sessionId } })).status, 204);
|
|
} finally { await listener.close(); }
|
|
});
|
|
|
|
test("browser Origin requests are rejected unless allowlisted", async (t) => {
|
|
let listener;
|
|
try {
|
|
listener = await startStreamableHttpServer({ serverName: "test", serverVersion: "1", host: "127.0.0.1", port: 0, apiKeys: [token], allowedOrigins: ["https://console.example"], tools: [], callTool: async () => ({}) });
|
|
} catch (error) {
|
|
if (error?.code === "EPERM") return t.skip("TCP listeners unavailable");
|
|
throw error;
|
|
}
|
|
try {
|
|
const denied = await fetch(`http://${listener.host}:${listener.port}/health`, { headers: { authorization: `Bearer ${token}`, origin: "https://evil.example" } });
|
|
assert.equal(denied.status, 403);
|
|
} finally { await listener.close(); }
|
|
});
|