Initial Commit

This commit is contained in:
2026-05-27 23:29:53 +01:00
commit fbc70f96e6
10000 changed files with 1102152 additions and 0 deletions

View File

@@ -0,0 +1,444 @@
/**
* MCPServer edge-case coverage — exercises server-side branches not naturally
* hit by happy-path tests: provider fall-backs, list-helpers with optional
* fields, normaliseToolResult array path, Zod root-level error formatting,
* authorize-true branch, signal forwarding to direct invocation API.
*/
import { describe, expect, it } from "@suaveplan/testing/runner";
import { MCPClient } from "../client/mcp-client/mcp-client.js";
import { MCP_CODES } from "../error-codes/error-codes.js";
import { JsonRpcDispatcher } from "../jsonrpc/dispatcher/dispatcher.js";
import { MCPServer } from "../server/mcp-server/mcp-server.js";
import { InMemoryTransport } from "../transport/in-memory/in-memory.js";
describe("server edge cases", () => {
it("server without sampling/completion provider falls back", {
timeout: 5000,
}, async () => {
const server = new MCPServer({
info: { name: "s", version: "0.1.0" },
});
const [a, b] = InMemoryTransport.pair();
await server.start(a);
const peer = new JsonRpcDispatcher({
side: "client",
transport: b,
dispatcherId: "X",
});
const completion = await peer.request("completion/complete", {
ref: { type: "ref/prompt", name: "x" },
argument: { name: "a", value: "b" },
});
expect(
(completion as { completion: { values: string[] } }).completion
.values
).toEqual([]);
const pong = await peer.request("ping", {});
expect(pong).toEqual({});
const list = await peer.request("resources/list", {});
expect((list as { resources: unknown[] }).resources).toEqual([]);
const tplList = await peer.request("resources/templates/list", {});
expect(
(tplList as { resourceTemplates: unknown[] }).resourceTemplates
).toEqual([]);
await expect(
peer.request("tools/call", { name: "nope" })
).rejects.toMatchObject({ code: MCP_CODES.INTERNAL_ERROR });
await expect(
peer.request("resources/read", { uri: "mem://nope" })
).rejects.toMatchObject({ code: MCP_CODES.INTERNAL_ERROR });
await expect(
peer.request("prompts/get", { name: "nope" })
).rejects.toMatchObject({ code: MCP_CODES.INTERNAL_ERROR });
await peer.request("resources/subscribe", { uri: "mem://x" });
await peer.request("resources/unsubscribe", { uri: "mem://x" });
await peer.close();
await server.stop();
});
it("tool with paramsSchema validates over the dispatcher", {
timeout: 5000,
}, async () => {
const z = await import("zod");
const server = new MCPServer({
info: { name: "s", version: "0.1.0" },
});
server.registerTool({
name: "validated",
inputSchema: {},
paramsSchema: z.default.object({ n: z.default.number() }),
async handler(args) {
return {
content: [
{
type: "text",
text: String((args as { n: number }).n),
},
],
};
},
});
const [a, b] = InMemoryTransport.pair();
await server.start(a);
const peer = new JsonRpcDispatcher({
side: "client",
transport: b,
dispatcherId: "X",
});
await expect(
peer.request("tools/call", {
name: "validated",
arguments: { n: "not-a-number" },
})
).rejects.toMatchObject({ code: MCP_CODES.INVALID_PARAMS });
const ok = await peer.request("tools/call", {
name: "validated",
arguments: { n: 7 },
});
expect(
(ok as { content: Array<{ text: string }> }).content[0]?.text
).toBe("7");
await peer.close();
await server.stop();
});
it("readResource and getPromptMessages accept signal", {
timeout: 5000,
}, async () => {
const server = new MCPServer({
info: { name: "s", version: "0.1.0" },
});
server.registerResource({
uri: "mem://x",
name: "x",
async handler({ signal }) {
return {
uri: "mem://x",
text: signal.aborted ? "aborted" : "ok",
};
},
});
server.registerPrompt({
name: "p",
async handler() {
return [
{
role: "user",
content: { type: "text", text: "hi" },
},
];
},
});
const ac = new AbortController();
const r = await server.readResource("mem://x", { signal: ac.signal });
expect(r[0]?.text).toBe("ok");
const m = await server.getPromptMessages(
"p",
{},
{ signal: ac.signal }
);
expect(m.length).toBe(1);
server.registerTool({
name: "fast",
inputSchema: {},
async handler(_args, ctx) {
return {
content: [
{
type: "text",
text: ctx.signal.aborted ? "x" : "ok",
},
],
};
},
});
const r2 = await server.invokeTool("fast", {}, { signal: ac.signal });
expect(r2.content[0]).toMatchObject({ text: "ok" });
});
it("server invokeTool with paramsSchema replaces params with parsed data", {
timeout: 5000,
}, async () => {
const z = await import("zod");
const server = new MCPServer({
info: { name: "s", version: "0.1.0" },
});
server.registerTool({
name: "sum",
inputSchema: {},
paramsSchema: z.default.object({
a: z.default.number(),
b: z.default.number(),
}),
async handler(args) {
const { a, b } = args as { a: number; b: number };
return {
content: [{ type: "text", text: String(a + b) }],
};
},
});
const result = await server.invokeTool("sum", { a: 2, b: 3 });
expect(result.content[0]).toMatchObject({ text: "5" });
});
it("server initialized getter starts false and flips true after handshake", {
timeout: 5000,
}, async () => {
const server = new MCPServer({
info: { name: "s", version: "0.1.0" },
});
expect(server.initialized).toBe(false);
const client = new MCPClient({
info: { name: "c", version: "0.1.0" },
});
const [a, b] = InMemoryTransport.pair();
await server.start(a);
await client.connect(b);
expect(server.initialized).toBe(true);
await client.disconnect();
await server.stop();
});
it("server rejects initialize for unsupported protocol version", {
timeout: 5000,
}, async () => {
const server = new MCPServer({
info: { name: "s", version: "0.1.0" },
});
const [a, b] = InMemoryTransport.pair();
await server.start(a);
const peer = new JsonRpcDispatcher({
side: "client",
transport: b,
dispatcherId: "X",
});
await expect(
peer.request("initialize", {
protocolVersion: "9999-01-01",
capabilities: {},
clientInfo: { name: "c", version: "0.1.0" },
})
).rejects.toMatchObject({
code: MCP_CODES.INTERNAL_ERROR,
});
await peer.close();
await server.stop();
});
it("tool handler returning a ContentItem array is normalised", {
timeout: 5000,
}, async () => {
const server = new MCPServer({
info: { name: "s", version: "0.1.0" },
});
server.registerTool({
name: "arr",
inputSchema: {},
async handler() {
return [{ type: "text" as const, text: "x" }];
},
});
const result = await server.invokeTool("arr", {});
expect(result.content).toEqual([{ type: "text", text: "x" }]);
expect(result.isError).toBeUndefined();
});
it("server resource handler returning array passes through", {
timeout: 5000,
}, async () => {
const server = new MCPServer({
info: { name: "s", version: "0.1.0" },
});
server.registerResource({
uri: "mem://multi",
name: "multi",
async handler() {
return [
{ uri: "mem://multi", text: "part1" },
{ uri: "mem://multi", text: "part2" },
];
},
});
const [a, b] = InMemoryTransport.pair();
await server.start(a);
const peer = new JsonRpcDispatcher({
side: "client",
transport: b,
dispatcherId: "X",
});
const out = (await peer.request("resources/read", {
uri: "mem://multi",
})) as { contents: unknown[] };
expect(out.contents.length).toBe(2);
await peer.close();
await server.stop();
});
it("prompts/get over the wire surfaces handler description", {
timeout: 5000,
}, async () => {
const server = new MCPServer({
info: { name: "s", version: "0.1.0" },
});
server.registerPrompt({
name: "with-desc",
description: "a documented prompt",
async handler() {
return [
{
role: "user" as const,
content: { type: "text" as const, text: "hi" },
},
];
},
});
const [a, b] = InMemoryTransport.pair();
await server.start(a);
const peer = new JsonRpcDispatcher({
side: "client",
transport: b,
dispatcherId: "X",
});
const out = (await peer.request("prompts/get", {
name: "with-desc",
})) as { description?: string; messages: unknown[] };
expect(out.description).toBe("a documented prompt");
expect(out.messages.length).toBe(1);
await peer.close();
await server.stop();
});
it("server unregisterTool without dispatcher does not notify", {
timeout: 5000,
}, async () => {
const server = new MCPServer({
info: { name: "s", version: "0.1.0" },
});
server.registerTool({
name: "z",
inputSchema: {},
async handler() {
return { content: [] };
},
});
expect(server.unregisterTool("z")).toBe(true);
expect(server.unregisterTool("z")).toBe(false);
});
it("unregister returns false for unknown resource/prompt names", {
timeout: 5000,
}, async () => {
const server = new MCPServer({
info: { name: "s", version: "0.1.0" },
});
expect(server.unregisterResource("mem://missing")).toBe(false);
expect(server.unregisterPrompt("missing")).toBe(false);
});
it("list helpers surface optional description/mimeType/arguments", {
timeout: 5000,
}, async () => {
const server = new MCPServer({
info: { name: "s", version: "0.1.0" },
});
server.registerResource({
uri: "mem://r",
name: "r",
description: "rd",
mimeType: "text/plain",
async handler() {
return { uri: "mem://r" };
},
});
server.registerPrompt({
name: "p",
description: "pd",
arguments: [{ name: "a" }],
async handler() {
return [];
},
});
server.registerTool({
name: "t",
description: "td",
inputSchema: {},
async handler() {
return { content: [] };
},
});
const resources = server.listResources();
expect(resources[0]?.description).toBe("rd");
expect(resources[0]?.mimeType).toBe("text/plain");
const prompts = server.listPrompts();
expect(prompts[0]?.description).toBe("pd");
expect(prompts[0]?.arguments).toEqual([{ name: "a" }]);
const tools = server.listTools();
expect(tools[0]?.description).toBe("td");
});
it("markResourceUpdated before start is a silent no-op", {
timeout: 5000,
}, async () => {
const server = new MCPServer({
info: { name: "s", version: "0.1.0" },
});
server.markResourceUpdated("mem://x");
});
it("server invokeTool wraps non-Error handler throws into isError", {
timeout: 5000,
}, async () => {
const server = new MCPServer({
info: { name: "s", version: "0.1.0" },
});
server.registerTool({
name: "throwsString",
inputSchema: {},
async handler() {
const v: unknown = "raw-string";
throw v;
},
});
const result = await server.invokeTool("throwsString", {});
expect(result.isError).toBe(true);
expect(result.content[0]).toMatchObject({ text: "Error: raw-string" });
});
it("server readResource returns array handler output unwrapped", {
timeout: 5000,
}, async () => {
const server = new MCPServer({
info: { name: "s", version: "0.1.0" },
});
server.registerResource({
uri: "mem://multi",
name: "multi",
async handler() {
return [
{ uri: "mem://multi", text: "a" },
{ uri: "mem://multi", text: "b" },
];
},
});
const out = await server.readResource("mem://multi");
expect(out.length).toBe(2);
});
it("server start wires injected clock and idGenerator", {
timeout: 5000,
}, async () => {
let n = 0;
const server = new MCPServer({
info: { name: "s", version: "0.1.0" },
clock: () => 4242,
idGenerator: () => {
n += 1;
return `s-${n}`;
},
});
const [t] = InMemoryTransport.pair();
await server.start(t);
expect(server.running).toBe(true);
await server.stop();
});
});