# OpenSpec Change: enterprise-mcp - **Change ID**: enterprise-mcp - **Package**: `@suaveplan/mcp` - **Category**: devtools / tooling - **Target path**: `packages/devtools/tooling/mcp` - **Status**: PROPOSED - **Date**: 2026-05-25 - **Reference spec**: `/mnt/s/web/suaveplan/enterprise/mcp.md` - **Design**: ./design.md - **Spec delta**: ./specs/mcp/spec.md --- ## Summary Deliver `@suaveplan/mcp` on the genesis branch from scratch at enterprise quality. The package provides a Model Context Protocol (MCP) server and client implementation. `MCPServer` lets host apps register named tools, URI-addressed resources, and prompt templates; `MCPClient` mirrors the surface from the consumer side. Genesis ships the existing scaffold (42 tests, high coverage, Zod configs) plus: real transport implementations (stdio, SSE, WebSocket), `AbortSignal` cancellation, streaming tool results (`invokeToolStream` / `callToolStream`), a tool/resource ACL hook, all DTOs derived from Zod schemas, OTel spans per invocation, and an E2E test wiring server + client over stdio. `"private": true` is removed; the package is published to Verdaccio. --- ## Current-state audit (genesis) Does not exist on genesis — fresh build (the main-branch package was a non-published scaffold; genesis rewrites it cleanly per the enterprise spec). --- ## Available workspace deps on genesis | Package | Role | |---|---| | `@suaveplan/error` (`workspace:^`) | `MCPError` / `MCPToolError` / `MCPTransportError` hierarchy | | `@suaveplan/types` (`workspace:^`) | `LoggerLike` | | `@suaveplan/events` (`workspace:^`) | Resource-change notifications | | `@suaveplan/logger` (`workspace:^`) | Structured logs | | `zod` (`catalog:forms`) | Single source of truth for all wire types | | `@opentelemetry/api` (`catalog:observability`) | `mcp.*` spans per invocation | | `tslib` (`catalog:build`) | Runtime helpers | --- ## Public API surface (from main + genesis additions) ```ts // Server class MCPServer { constructor(config: MCPServerConfig) registerTool(definition: MCPToolDefinition): void unregisterTool(name: string): void registerResource(definition: MCPResourceDefinition): void unregisterResource(uri: string): void registerPrompt(definition: MCPPromptDefinition): void unregisterPrompt(name: string): void invokeTool(name: string, args: unknown, signal?: AbortSignal): Promise invokeToolStream(name: string, args: unknown, signal?: AbortSignal): AsyncIterable readResource(uri: string, signal?: AbortSignal): Promise getPromptMessages(name: string, args?: Record, signal?: AbortSignal): Promise getInfo(): MCPServerInfo start(transport: Transport): Promise stop(): Promise setAuthorizer(fn: (request: MCPAuthRequest) => Promise): void } // Client class MCPClient { constructor(config: MCPClientConfig) connect(transport: Transport): Promise disconnect(): Promise refreshTools(): Promise refreshResources(): Promise refreshPrompts(): Promise callTool(name: string, args: unknown, signal?: AbortSignal): Promise callToolStream(name: string, args: unknown, signal?: AbortSignal): AsyncIterable readResource(uri: string, signal?: AbortSignal): Promise getPrompt(name: string, args?: Record, signal?: AbortSignal): Promise tools(): RemoteTool[] resources(): RemoteResource[] prompts(): RemotePrompt[] } // Transport interface interface Transport { send(message: MCPMessage): Promise onMessage(handler: (message: MCPMessage) => void): () => void close(): Promise } // Concrete transports (sub-path exports) function createStdioTransport(): Transport // Node process.stdin/stdout function createSseTransport(url: string): Transport function createWebSocketTransport(url: string): Transport // Zod schemas (all wire types) const MCPServerConfigSchema: z.ZodSchema const MCPClientConfigSchema: z.ZodSchema const MCPToolDefinitionSchema: z.ZodSchema const MCPResourceDefinitionSchema: z.ZodSchema const MCPPromptDefinitionSchema: z.ZodSchema const MCPToolResultSchema: z.ZodSchema const MCPContentItemSchema: z.ZodSchema const MCPResourceContentSchema: z.ZodSchema const MCPPromptMessageSchema: z.ZodSchema // Errors class MCPError extends SuaveplanError { } class MCPToolError extends MCPError { } class MCPTransportError extends MCPError { } function registerMCPErrorCodes(): void ``` --- ## File layout ```text packages/devtools/tooling/mcp/ package.json tsconfig.json vitest.config.ts src/ index.ts error-codes.ts types.ts # Zod schemas + z.infer types for all wire types constants/index.ts server/ mcp-server/ mcp-server.ts / .md / .types.ts / .test.ts / index.ts client/ mcp-client/ mcp-client.ts / .md / .types.ts / .test.ts / index.ts transport/ transport/ transport.ts / transport.md / transport.types.ts / index.ts stdio/ stdio.ts / stdio.md / stdio.test.ts / index.ts sse/ sse.ts / sse.md / sse.test.ts / index.ts websocket/ websocket.ts / websocket.md / websocket.test.ts / index.ts tests/ e2e/ stdio-handshake.test.ts ``` --- ## Impact Downstream: AI agent integration, tool registration for LLM workflows. The package is the MCP foundation for the entire platform. **Prerequisites on Verdaccio**: `@suaveplan/error`, `@suaveplan/types`, `@suaveplan/events`, `@suaveplan/logger`, `@suaveplan/opentelemetry`. ### Affected dependents None. `@suaveplan/mcp` is currently unreferenced by every other genesis package (per `MANIFEST.md`). ### Deviations from upstream 1. Upstream is a scaffold with no JSON-RPC framing, no real transport, no abort, no progress, no authorize, no observability. Genesis ships a full implementation of every MCP 2024-11-05 host-side method. 2. Upstream's wire DTOs are hand-rolled interfaces; only the two config objects are Zod. Genesis derives every wire type from a Zod schema (`MCPServerConfigSchema`, `JsonRpcRequestSchema`, `MCPToolCallParamsSchema`, etc.) and `z.infer`s the TypeScript type. 3. Upstream declared a `stdio | sse | websocket` transport union but shipped no transport. Genesis ships a `Transport` interface plus three concrete adapters: `InMemoryTransport.pair()` (universal, used by tests and in-process bridges), `createStdioTransport({ readable, writable })` (universal — accepts injectable byte streams so the same code runs on Node, Bun, Deno, Edge, browser), `createHttpSseTransport({ endpoint, fetchImpl, eventSourceImpl })` (universal — accepts injectable `fetch` + `EventSource` constructor). 4. WebSocket transport is intentionally deferred to a sibling change once `@suaveplan/network-websockets` lands. The `Transport` interface is shaped so a WebSocket adapter is drop-in. 5. The package gains a stable `MCP_CODES` registry built via `defineCodes("MCP", […])` and a `MCPError` / `MCPProtocolError` / `MCPTransportError` hierarchy. 6. Every public method on `MCPServer` / `MCPClient` accepts an `AbortSignal`; cancellation surfaces over the wire as `notifications/cancelled`. 7. Progress notifications: `MCPServer.invokeTool` invokes the handler with a `progress(token, value, total?)` callback that forwards `notifications/progress` to the client. 8. Authorize hook: `MCPServer.setAuthorizer(fn)` gates every incoming method; rejection yields a JSON-RPC `-32001` error mapped to `MCP.UNAUTHORIZED`. 9. Resource subscriptions: server pushes `notifications/resources/updated` to every subscribed client when a resource is marked dirty. ### Out of scope (explicitly deferred to follow-up changes, not silently dropped) - **WebSocket transport** — requires `@suaveplan/network-websockets`, not yet migrated. - **React renderers for `MCPContentItem`** — belong in a separate `react-mcp` package under `packages/ui/react/`. - **`suaveplan-mcp` CLI binary** — belongs in a `tooling/mcp-cli` sibling. Each deferral is justified above; this is not silent omission per AGENTS §2b.