// ---------------------------------------------------------------------------// Local app × the pre-initialize dispatch guard — through the real handler// ---------------------------------------------------------------------------//// `executor mcp` bridges a stdio client to this handler's `/mcp` endpoint, so// what the handler answers on a session-less POST decides whether the client's// connection survives its first probe://// test → createMcpRequestHandler().handleRequest(Request)// → pre-initialize guard (an unknown method -> -32601 on a 200)// → WebStandardStreamableHTTPServerTransport (everything else)//// The guard replaces exactly ONE transport answer — the connection-killing// `400 -32000 Server not initialized` for a method that is not `initialize`.// These assert that replacement AND that no other transport answer is shadowed:// a request that fails content negotiation still gets the transport's 415/406,// and a structurally invalid JSON-RPC message still gets its parse error, not a// "method not found" that was never true.// --------------------------------------------------------------------------- import { describe, expect, it } from "@effect/vitest";import { Effect } from "effect"; import type { ExecutionEngine } from "@executor-js/execution"; import { createMcpRequestHandler } from "./mcp"; /** The headers a streamable-HTTP client must send on a POST; less is a 406/415. */const MCP_POST_HEADERS = { "content-type": "application/json", accept: "application/json, text/event-stream",} as const; /** No code ever runs here: these requests are answered before any tool call. */const stubEngine: ExecutionEngine<never> = { execute: () => Effect.succeed({ result: "unused" }), executeWithPause: () => Effect.succeed({ status: "completed", result: { result: "unused" } }), resume: () => Effect.succeed(null), getPausedExecution: () => Effect.succeed(null), pausedExecutionCount: () => Effect.succeed(0), hasPausedExecutions: () => Effect.succeed(false), getDescription: Effect.succeed("test executor"), shutdown: Effect.void,}; const post = ( body: unknown, headers: Record<string, string> = MCP_POST_HEADERS,): Promise<Response> => createMcpRequestHandler({ engine: stubEngine }).handleRequest( new Request("http://local.test/mcp", { method: "POST", headers, body: JSON.stringify(body) }), ); interface JsonRpcErrorBody { readonly error: { readonly code: number; readonly message: string };} describe("local MCP handler, pre-initialize", () => { it("answers a valid unknown pre-session method with -32601 on a 200", async () => { const response = await post({ jsonrpc: "2.0", id: 7, method: "server/discover", params: {} }); // 200, not the transport's 400: a per-request error the bridged client // survives, so it can still fall back to `initialize`. expect(response.status).toBe(200); expect(await response.json()).toEqual({ jsonrpc: "2.0", id: 7, error: { code: -32601, message: "Method not found" }, }); }); it("passes a pre-session notification to the transport", async () => { const response = await post({ jsonrpc: "2.0", method: "notifications/initialized" }); // A notification carries no id, so the guard may not answer it at all; // whatever comes back is the transport's own answer. const body = (await response.json()) as JsonRpcErrorBody; expect(body.error.code).not.toBe(-32601); expect(body.error.code).toBe(-32000); }); it("leaves a structurally invalid request to the transport's parse error", async () => { // A fractional id is not a JSON-RPC id, so this is not a request the guard // may report an unknown method for. const response = await post({ jsonrpc: "2.0", id: 1.5, method: "server/discover" }); expect(response.status).toBe(400); const body = (await response.json()) as JsonRpcErrorBody; expect(body.error.code).toBe(-32700); expect(body.error.code).not.toBe(-32601); }); it("leaves a wrong Content-Type to the transport's 415", async () => { const response = await post( { jsonrpc: "2.0", id: 1, method: "server/discover" }, { "content-type": "text/plain", accept: MCP_POST_HEADERS.accept }, ); expect(response.status).toBe(415); }); it("leaves an incomplete Accept to the transport's 406", async () => { const response = await post( { jsonrpc: "2.0", id: 1, method: "server/discover" }, { "content-type": "application/json", accept: "application/json" }, ); expect(response.status).toBe(406); });});
// ---------------------------------------------------------------------------
// Local app × the pre-initialize dispatch guard — through the real handler
// ---------------------------------------------------------------------------
//
// `executor mcp` bridges a stdio client to this handler's `/mcp` endpoint, so
// what the handler answers on a session-less POST decides whether the client's
// connection survives its first probe:
//
// test → createMcpRequestHandler().handleRequest(Request)
// → pre-initialize guard (an unknown method -> -32601 on a 200)
// → WebStandardStreamableHTTPServerTransport (everything else)
//
// The guard replaces exactly ONE transport answer — the connection-killing
// `400 -32000 Server not initialized` for a method that is not `initialize`.
// These assert that replacement AND that no other transport answer is shadowed:
// a request that fails content negotiation still gets the transport's 415/406,
// and a structurally invalid JSON-RPC message still gets its parse error, not a
// "method not found" that was never true.
// ---------------------------------------------------------------------------
import { describe, expect, it } from "@effect/vitest";
import { Effect } from "effect";
import type { ExecutionEngine } from "@executor-js/execution";
import { createMcpRequestHandler } from "./mcp";
/** The headers a streamable-HTTP client must send on a POST; less is a 406/415. */
const MCP_POST_HEADERS = {
"content-type": "application/json",
accept: "application/json, text/event-stream",
} as const;
/** No code ever runs here: these requests are answered before any tool call. */
const stubEngine: ExecutionEngine<never> = {
execute: () => Effect.succeed({ result: "unused" }),
executeWithPause: () => Effect.succeed({ status: "completed", result: { result: "unused" } }),
resume: () => Effect.succeed(null),
getPausedExecution: () => Effect.succeed(null),
pausedExecutionCount: () => Effect.succeed(0),
hasPausedExecutions: () => Effect.succeed(false),
getDescription: Effect.succeed("test executor"),
shutdown: Effect.void,
};
const post = (
body: unknown,
headers: Record<string, string> = MCP_POST_HEADERS,
): Promise<Response> =>
createMcpRequestHandler({ engine: stubEngine }).handleRequest(
new Request("http://local.test/mcp", { method: "POST", headers, body: JSON.stringify(body) }),
);
interface JsonRpcErrorBody {
readonly error: { readonly code: number; readonly message: string };
}
describe("local MCP handler, pre-initialize", () => {
it("answers a valid unknown pre-session method with -32601 on a 200", async () => {
const response = await post({ jsonrpc: "2.0", id: 7, method: "server/discover", params: {} });
// 200, not the transport's 400: a per-request error the bridged client
// survives, so it can still fall back to `initialize`.
expect(response.status).toBe(200);
expect(await response.json()).toEqual({
jsonrpc: "2.0",
id: 7,
error: { code: -32601, message: "Method not found" },
});
});
it("passes a pre-session notification to the transport", async () => {
const response = await post({ jsonrpc: "2.0", method: "notifications/initialized" });
// A notification carries no id, so the guard may not answer it at all;
// whatever comes back is the transport's own answer.
const body = (await response.json()) as JsonRpcErrorBody;
expect(body.error.code).not.toBe(-32601);
expect(body.error.code).toBe(-32000);
});
it("leaves a structurally invalid request to the transport's parse error", async () => {
// A fractional id is not a JSON-RPC id, so this is not a request the guard
// may report an unknown method for.
const response = await post({ jsonrpc: "2.0", id: 1.5, method: "server/discover" });
expect(response.status).toBe(400);
const body = (await response.json()) as JsonRpcErrorBody;
expect(body.error.code).toBe(-32700);
expect(body.error.code).not.toBe(-32601);
});
it("leaves a wrong Content-Type to the transport's 415", async () => {
const response = await post(
{ jsonrpc: "2.0", id: 1, method: "server/discover" },
{ "content-type": "text/plain", accept: MCP_POST_HEADERS.accept },
);
expect(response.status).toBe(415);
});
it("leaves an incomplete Accept to the transport's 406", async () => {
const response = await post(
{ jsonrpc: "2.0", id: 1, method: "server/discover" },
{ "content-type": "application/json", accept: "application/json" },
);
expect(response.status).toBe(406);
});
});