Component — MCP server (skeleton)
The buildable MCP server that ships in Phase 1. Exposes one tool (ping) over stdio, validated and ready for Phase-2 onwards to layer business tools on top of.
Responsibilities
- Boot on either
stdio(local owner) orhttp(Phase 5+, remote MCP). Selection viaMCP_TRANSPORT. - Build a fresh
Serverfrom the tool registry. - Dispatch
tools/listandtools/callrequests through a single chokepoint where Phase-4 will insert scope checks, grant checks, and audit logging.
Public interfaces
- src/index.ts — entry point; selects transport.
- src/server/mcp.ts —
buildMcpServer(transport)factory. - src/transport/stdio.ts —
runStdio()runner. - src/tools/_registry.ts —
ToolDefinition,toolRegistry,buildToolIndex.
Tool registry pattern
Every tool exports a ToolDefinition:
import { z } from 'zod';
import type { ToolDefinition } from './_registry.js';
const Input = z.object({ foo: z.string() }).strict();
export const myTool: ToolDefinition<typeof Input> = {
name: 'my_tool',
description: 'one-sentence summary',
scope: 'tools:my_tool', // null for ping; required for everything else from Phase 4
inputSchema: Input,
async handler(input, ctx) {
// ctx.requestId, ctx.transport, ctx.auth (Phase 4+)
return { ok: true };
},
};…and registers itself by being added to the toolRegistry array in _registry.ts.
Why the indirection: from Phase 4 onwards every tool invocation must pass through scope + grant + rate-limit + audit hooks. The registry pattern means those hooks live in one place (buildMcpServer’s dispatcher) — not in every tool handler.
stdio mode
- Reads JSON-RPC from stdin, writes to stdout.
- Logs go to stderr (src/config/logger.ts) — stdout is reserved for framing.
- An
authcontext is not synthesised in Phase 1; that happens in Phase 4 when stdio injects the owner identity.
Configuration
All env vars validated by src/config/env.ts. Only that module reads process.env; the custom ESLint rule local/no-process-env-outside-config enforces it.
Tests
- tests/unit/tools/ping.test.ts — handler returns the documented shape; strict zod rejects unknown fields.
- tests/unit/tools/registry.test.ts — duplicate names + missing fields throw; the production registry builds cleanly.
- tests/unit/config/env.test.ts — env validation: missing required, malformed URL, unknown enum, empty-string coercion, multiple-errors-aggregation.