feat: migrate verified template implementation into main repo
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-opencode) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
This commit is contained in:
@@ -0,0 +1,57 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import {
|
||||
EXAMPLE_ECHO_TOOL_NAME,
|
||||
EXAMPLE_SUMMARY_PROMPT_NAME,
|
||||
type CapabilityRegistration,
|
||||
createCapabilityRegistry,
|
||||
registerCapabilities,
|
||||
registerCoreCapabilities,
|
||||
} from "../src/capabilities/index.js";
|
||||
|
||||
describe("capability registration", () => {
|
||||
it("aggregates core registrations through plain function composition", () => {
|
||||
const registry = registerCapabilities();
|
||||
|
||||
expect(registry.tools.map((entry) => entry.name)).toEqual([
|
||||
EXAMPLE_ECHO_TOOL_NAME,
|
||||
]);
|
||||
expect(registry.resources.map((entry) => entry.name)).toEqual([
|
||||
"template://status",
|
||||
]);
|
||||
expect(registry.prompts.map((entry) => entry.name)).toEqual([
|
||||
EXAMPLE_SUMMARY_PROMPT_NAME,
|
||||
]);
|
||||
});
|
||||
|
||||
it("supports explicit composition with custom registrations", () => {
|
||||
const registerCustom: CapabilityRegistration = (registry) => {
|
||||
registry.tools.push({
|
||||
name: "custom.tool",
|
||||
kind: "tool",
|
||||
description: "Custom tool descriptor",
|
||||
});
|
||||
};
|
||||
|
||||
const registry = registerCapabilities([
|
||||
registerCoreCapabilities,
|
||||
registerCustom,
|
||||
]);
|
||||
|
||||
expect(registry.tools.map((entry) => entry.name)).toEqual([
|
||||
EXAMPLE_ECHO_TOOL_NAME,
|
||||
"custom.tool",
|
||||
]);
|
||||
expect(registry.resources).toHaveLength(1);
|
||||
expect(registry.prompts).toHaveLength(1);
|
||||
});
|
||||
|
||||
it("lets callers register into an existing registry", () => {
|
||||
const registry = createCapabilityRegistry();
|
||||
|
||||
registerCoreCapabilities(registry);
|
||||
|
||||
expect(registry.tools).toHaveLength(1);
|
||||
expect(registry.resources).toHaveLength(1);
|
||||
expect(registry.prompts).toHaveLength(1);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,64 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { AppError } from "../src/lib/errors.js";
|
||||
import {
|
||||
parseExampleEchoToolInput,
|
||||
parseExampleSummaryPromptInput,
|
||||
renderExampleSummaryPrompt,
|
||||
runExampleEchoTool,
|
||||
} from "../src/capabilities/index.js";
|
||||
|
||||
describe("example capability contracts", () => {
|
||||
it("accepts valid tool input and applies uppercase transform", () => {
|
||||
expect(
|
||||
parseExampleEchoToolInput({ message: " hello team ", uppercase: true }),
|
||||
).toEqual({
|
||||
message: "hello team",
|
||||
uppercase: true,
|
||||
});
|
||||
|
||||
expect(runExampleEchoTool({ message: "hello", uppercase: true })).toEqual({
|
||||
output: "HELLO",
|
||||
});
|
||||
});
|
||||
|
||||
it("rejects invalid tool input shapes", () => {
|
||||
expect(() => parseExampleEchoToolInput("bad-input")).toThrow(AppError);
|
||||
expect(() => parseExampleEchoToolInput({ message: "" })).toThrow(
|
||||
"message must not be empty",
|
||||
);
|
||||
expect(() =>
|
||||
parseExampleEchoToolInput({ message: "valid", uppercase: "yes" }),
|
||||
).toThrow("uppercase must be a boolean when provided");
|
||||
});
|
||||
|
||||
it("accepts valid prompt input and renders generic prompt text", () => {
|
||||
expect(
|
||||
parseExampleSummaryPromptInput({
|
||||
topic: "event-driven systems",
|
||||
audience: "new contributors",
|
||||
}),
|
||||
).toEqual({
|
||||
topic: "event-driven systems",
|
||||
audience: "new contributors",
|
||||
});
|
||||
|
||||
expect(
|
||||
renderExampleSummaryPrompt({
|
||||
topic: "API design",
|
||||
}),
|
||||
).toBe("Write a concise summary about API design.");
|
||||
});
|
||||
|
||||
it("rejects invalid prompt input shapes", () => {
|
||||
expect(() => parseExampleSummaryPromptInput(null)).toThrow(AppError);
|
||||
expect(() => parseExampleSummaryPromptInput({ topic: 42 })).toThrow(
|
||||
"topic must be a string",
|
||||
);
|
||||
expect(() =>
|
||||
parseExampleSummaryPromptInput({
|
||||
topic: "valid topic",
|
||||
audience: " ",
|
||||
}),
|
||||
).toThrow("audience must not be empty");
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,69 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { parseRuntimeConfig } from "../src/config/index.js";
|
||||
|
||||
describe("parseRuntimeConfig", () => {
|
||||
it("returns defaults when env values are missing", () => {
|
||||
expect(parseRuntimeConfig({})).toEqual({
|
||||
mode: "development",
|
||||
port: 3000,
|
||||
});
|
||||
});
|
||||
|
||||
it("parses valid runtime mode and port", () => {
|
||||
expect(
|
||||
parseRuntimeConfig({
|
||||
RUNTIME_MODE: "production",
|
||||
HTTP_PORT: "8080",
|
||||
}),
|
||||
).toEqual({
|
||||
mode: "production",
|
||||
port: 8080,
|
||||
});
|
||||
});
|
||||
|
||||
it("normalizes mode casing and surrounding whitespace", () => {
|
||||
expect(
|
||||
parseRuntimeConfig({
|
||||
RUNTIME_MODE: " PrOdUcTiOn ",
|
||||
HTTP_PORT: " 8081 ",
|
||||
}),
|
||||
).toEqual({
|
||||
mode: "production",
|
||||
port: 8081,
|
||||
});
|
||||
});
|
||||
|
||||
it("rejects invalid runtime mode", () => {
|
||||
expect(() =>
|
||||
parseRuntimeConfig({
|
||||
RUNTIME_MODE: "staging",
|
||||
}),
|
||||
).toThrow(/Invalid RUNTIME_MODE/);
|
||||
});
|
||||
|
||||
it("rejects invalid http port", () => {
|
||||
expect(() =>
|
||||
parseRuntimeConfig({
|
||||
HTTP_PORT: "0",
|
||||
}),
|
||||
).toThrow(/Invalid HTTP_PORT/);
|
||||
|
||||
expect(() =>
|
||||
parseRuntimeConfig({
|
||||
HTTP_PORT: "not-a-number",
|
||||
}),
|
||||
).toThrow(/Invalid HTTP_PORT/);
|
||||
|
||||
expect(() =>
|
||||
parseRuntimeConfig({
|
||||
HTTP_PORT: "65536",
|
||||
}),
|
||||
).toThrow(/Invalid HTTP_PORT/);
|
||||
|
||||
expect(() =>
|
||||
parseRuntimeConfig({
|
||||
HTTP_PORT: "42.5",
|
||||
}),
|
||||
).toThrow(/Invalid HTTP_PORT/);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,101 @@
|
||||
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");
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,67 @@
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
import { AppError, asError, isAppError, toErrorPayload } from "../src/lib/errors.js";
|
||||
import { createLogger } from "../src/lib/logger.js";
|
||||
|
||||
describe("logger", () => {
|
||||
it("writes JSON logs using provided sink", () => {
|
||||
const sink = vi.fn<(line: string) => void>();
|
||||
const logger = createLogger({
|
||||
name: "unit",
|
||||
sink,
|
||||
now: () => new Date("2026-01-01T00:00:00.000Z")
|
||||
});
|
||||
|
||||
logger.info("ready", { port: 3000 });
|
||||
|
||||
expect(sink).toHaveBeenCalledTimes(1);
|
||||
expect(JSON.parse(sink.mock.calls[0][0] as string)).toEqual({
|
||||
timestamp: "2026-01-01T00:00:00.000Z",
|
||||
level: "info",
|
||||
logger: "unit",
|
||||
message: "ready",
|
||||
details: { port: 3000 }
|
||||
});
|
||||
});
|
||||
|
||||
it("defaults to stderr and never writes to stdout", () => {
|
||||
const stderrWrite = vi
|
||||
.spyOn(process.stderr, "write")
|
||||
.mockImplementation(() => true);
|
||||
const stdoutWrite = vi
|
||||
.spyOn(process.stdout, "write")
|
||||
.mockImplementation(() => true);
|
||||
|
||||
const logger = createLogger({ name: "unit" });
|
||||
logger.error("failed");
|
||||
|
||||
expect(stderrWrite).toHaveBeenCalledTimes(1);
|
||||
expect(stdoutWrite).not.toHaveBeenCalled();
|
||||
|
||||
stderrWrite.mockRestore();
|
||||
stdoutWrite.mockRestore();
|
||||
});
|
||||
});
|
||||
|
||||
describe("errors", () => {
|
||||
it("normalizes unknown values into Error", () => {
|
||||
expect(asError("boom")).toBeInstanceOf(Error);
|
||||
expect(asError("boom").message).toBe("boom");
|
||||
expect(asError(123).message).toBe("Unknown error");
|
||||
});
|
||||
|
||||
it("exposes consistent payloads", () => {
|
||||
const appError = new AppError("Bad input", "BAD_INPUT");
|
||||
|
||||
expect(isAppError(appError)).toBe(true);
|
||||
expect(toErrorPayload(appError)).toEqual({
|
||||
name: "AppError",
|
||||
message: "Bad input",
|
||||
code: "BAD_INPUT"
|
||||
});
|
||||
|
||||
expect(toErrorPayload(new Error("Oops"))).toEqual({
|
||||
name: "Error",
|
||||
message: "Oops"
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,81 @@
|
||||
import { readFile } from "node:fs/promises";
|
||||
import { dirname, resolve } from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
import {
|
||||
type CapabilityRegistration,
|
||||
EXAMPLE_ECHO_TOOL_NAME,
|
||||
EXAMPLE_SUMMARY_PROMPT_NAME,
|
||||
TEMPLATE_STATUS_RESOURCE_URI,
|
||||
} from "../src/capabilities/index.js";
|
||||
import { createMcpCore } from "../src/core/index.js";
|
||||
|
||||
type McpServerInternals = {
|
||||
_registeredTools: Record<string, unknown>;
|
||||
_registeredResources: Record<string, unknown>;
|
||||
_registeredPrompts: Record<string, unknown>;
|
||||
};
|
||||
|
||||
describe("MCP core factory", () => {
|
||||
it("wires example capabilities through the shared core path", () => {
|
||||
const core = createMcpCore();
|
||||
const serverInternals = core.server as unknown as McpServerInternals;
|
||||
|
||||
expect(core.registry.tools.map((entry) => entry.name)).toEqual([
|
||||
EXAMPLE_ECHO_TOOL_NAME,
|
||||
]);
|
||||
expect(core.registry.resources.map((entry) => entry.name)).toEqual([
|
||||
TEMPLATE_STATUS_RESOURCE_URI,
|
||||
]);
|
||||
expect(core.registry.prompts.map((entry) => entry.name)).toEqual([
|
||||
EXAMPLE_SUMMARY_PROMPT_NAME,
|
||||
]);
|
||||
|
||||
expect(Object.keys(serverInternals._registeredTools)).toEqual([
|
||||
EXAMPLE_ECHO_TOOL_NAME,
|
||||
]);
|
||||
expect(Object.keys(serverInternals._registeredResources)).toEqual([
|
||||
TEMPLATE_STATUS_RESOURCE_URI,
|
||||
]);
|
||||
expect(Object.keys(serverInternals._registeredPrompts)).toEqual([
|
||||
EXAMPLE_SUMMARY_PROMPT_NAME,
|
||||
]);
|
||||
});
|
||||
|
||||
it("creates an unconnected core that does not require transport wiring", () => {
|
||||
const core = createMcpCore();
|
||||
|
||||
expect(core.server.isConnected()).toBe(false);
|
||||
});
|
||||
|
||||
it("supports explicit registry composition and server capability wiring", () => {
|
||||
const registerCustom: CapabilityRegistration = (registry) => {
|
||||
registry.tools.push({
|
||||
name: "custom.tool",
|
||||
kind: "tool",
|
||||
description: "Custom test capability",
|
||||
});
|
||||
};
|
||||
const registerServerCapabilities = vi.fn<(server: unknown) => void>();
|
||||
|
||||
const core = createMcpCore({
|
||||
capabilityRegistrations: [registerCustom],
|
||||
registerServerCapabilities,
|
||||
});
|
||||
|
||||
expect(core.registry.tools.map((entry) => entry.name)).toEqual(["custom.tool"]);
|
||||
expect(core.registry.resources).toEqual([]);
|
||||
expect(core.registry.prompts).toEqual([]);
|
||||
expect(registerServerCapabilities).toHaveBeenCalledTimes(1);
|
||||
expect(registerServerCapabilities).toHaveBeenCalledWith(core.server);
|
||||
});
|
||||
|
||||
it("does not import stdio/http transport wrappers in shared core", async () => {
|
||||
const here = dirname(fileURLToPath(import.meta.url));
|
||||
const coreModulePath = resolve(here, "../src/core/mcp-core.ts");
|
||||
const source = await readFile(coreModulePath, "utf8");
|
||||
|
||||
expect(source).not.toMatch(/server\/(stdio|sse|streamableHttp)\.js/);
|
||||
expect(source).not.toMatch(/(?:express|hono)/);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,8 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import * as template from "../src/index.js";
|
||||
|
||||
describe("template package smoke test", () => {
|
||||
it("exposes the MCP core factory from public entrypoint", () => {
|
||||
expect(typeof template.createMcpCore).toBe("function");
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,123 @@
|
||||
import { readFile } from "node:fs/promises";
|
||||
import { dirname, resolve } from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
|
||||
import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { EXAMPLE_ECHO_TOOL_NAME } from "../src/capabilities/index.js";
|
||||
|
||||
const testsDir = dirname(fileURLToPath(import.meta.url));
|
||||
const projectRoot = resolve(testsDir, "..");
|
||||
const stdioEntryPath = resolve(projectRoot, "src/stdio.ts");
|
||||
const packageJsonPath = resolve(projectRoot, "package.json");
|
||||
|
||||
type StdioCommandConfig = {
|
||||
mcp?: {
|
||||
stdio?: {
|
||||
command?: string;
|
||||
args?: string[];
|
||||
};
|
||||
};
|
||||
scripts?: Record<string, string>;
|
||||
};
|
||||
|
||||
async function readStdioCommandConfig(): Promise<{ command: string; args: string[] }> {
|
||||
const packageJsonRaw = await readFile(packageJsonPath, "utf8");
|
||||
const packageJson = JSON.parse(packageJsonRaw) as StdioCommandConfig;
|
||||
|
||||
return {
|
||||
command: packageJson.mcp?.stdio?.command ?? "node",
|
||||
args: packageJson.mcp?.stdio?.args ?? [],
|
||||
};
|
||||
}
|
||||
|
||||
async function createConnectedStdioClient(): Promise<{
|
||||
client: Client;
|
||||
transport: StdioClientTransport;
|
||||
}> {
|
||||
const stdioCommand = await readStdioCommandConfig();
|
||||
const transport = new StdioClientTransport({
|
||||
command: stdioCommand.command,
|
||||
args: stdioCommand.args,
|
||||
cwd: projectRoot,
|
||||
stderr: "pipe",
|
||||
});
|
||||
const client = new Client(
|
||||
{ name: "stdio-test-client", version: "0.0.0" },
|
||||
{ capabilities: {} },
|
||||
);
|
||||
|
||||
await client.connect(transport);
|
||||
return { client, transport };
|
||||
}
|
||||
|
||||
describe("stdio entrypoint", () => {
|
||||
it("starts over stdio and keeps startup diagnostics on stderr", async () => {
|
||||
const stdioCommand = await readStdioCommandConfig();
|
||||
const transport = new StdioClientTransport({
|
||||
command: stdioCommand.command,
|
||||
args: stdioCommand.args,
|
||||
cwd: projectRoot,
|
||||
stderr: "pipe",
|
||||
});
|
||||
const stderrMessages: string[] = [];
|
||||
const stderr = transport.stderr;
|
||||
stderr?.on("data", (chunk) => {
|
||||
stderrMessages.push(chunk.toString());
|
||||
});
|
||||
const client = new Client(
|
||||
{ name: "stdio-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);
|
||||
expect(stderrMessages.join("")).toContain("MCP stdio server started");
|
||||
|
||||
await client.close();
|
||||
await transport.close();
|
||||
});
|
||||
|
||||
it("rejects invalid tool calls over stdio transport", async () => {
|
||||
const { client, transport } = await createConnectedStdioClient();
|
||||
|
||||
const missingToolResult = await client.callTool({
|
||||
name: "template.missing-tool",
|
||||
arguments: {},
|
||||
});
|
||||
const invalidInputResult = await client.callTool({
|
||||
name: EXAMPLE_ECHO_TOOL_NAME,
|
||||
arguments: { uppercase: true },
|
||||
});
|
||||
|
||||
expect(missingToolResult.isError).toBe(true);
|
||||
expect(JSON.stringify(missingToolResult.content)).toMatch(/not found|unknown/i);
|
||||
expect(invalidInputResult.isError).toBe(true);
|
||||
expect(JSON.stringify(invalidInputResult.content)).toMatch(/message|required|invalid/i);
|
||||
|
||||
await client.close();
|
||||
await transport.close();
|
||||
});
|
||||
|
||||
it("publishes a non-npm stdio launch command for MCP clients", async () => {
|
||||
const packageJsonRaw = await readFile(packageJsonPath, "utf8");
|
||||
const packageJson = JSON.parse(packageJsonRaw) as StdioCommandConfig;
|
||||
|
||||
expect(packageJson.mcp?.stdio?.command).not.toMatch(/^npm|^npx/);
|
||||
expect(packageJson.scripts?.["start:stdio"]).toBeUndefined();
|
||||
expect(packageJson.mcp?.stdio?.command).toBe("node");
|
||||
expect(packageJson.mcp?.stdio?.args).toEqual([
|
||||
"./node_modules/tsx/dist/cli.mjs",
|
||||
"src/stdio.ts",
|
||||
]);
|
||||
});
|
||||
|
||||
it("does not write non-protocol logs to stdout from source entry", async () => {
|
||||
const source = await readFile(stdioEntryPath, "utf8");
|
||||
|
||||
expect(source).not.toMatch(/console\.log\(/);
|
||||
expect(source).not.toMatch(/process\.stdout\.write\(/);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user