Skip to Content
WhatsApp MCPComponentsMcp Server

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) or http (Phase 5+, remote MCP). Selection via MCP_TRANSPORT.
  • Build a fresh Server from the tool registry.
  • Dispatch tools/list and tools/call requests through a single chokepoint where Phase-4 will insert scope checks, grant checks, and audit logging.

Public interfaces

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 auth context 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