176 lines
8.2 KiB
Markdown
176 lines
8.2 KiB
Markdown
# 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<MCPToolResult>
|
|
invokeToolStream(name: string, args: unknown, signal?: AbortSignal): AsyncIterable<MCPContentItem>
|
|
readResource(uri: string, signal?: AbortSignal): Promise<MCPResourceContent>
|
|
getPromptMessages(name: string, args?: Record<string, string>, signal?: AbortSignal): Promise<MCPPromptMessage[]>
|
|
getInfo(): MCPServerInfo
|
|
start(transport: Transport): Promise<void>
|
|
stop(): Promise<void>
|
|
setAuthorizer(fn: (request: MCPAuthRequest) => Promise<boolean>): void
|
|
}
|
|
|
|
// Client
|
|
class MCPClient {
|
|
constructor(config: MCPClientConfig)
|
|
connect(transport: Transport): Promise<void>
|
|
disconnect(): Promise<void>
|
|
refreshTools(): Promise<void>
|
|
refreshResources(): Promise<void>
|
|
refreshPrompts(): Promise<void>
|
|
callTool(name: string, args: unknown, signal?: AbortSignal): Promise<MCPToolResult>
|
|
callToolStream(name: string, args: unknown, signal?: AbortSignal): AsyncIterable<MCPContentItem>
|
|
readResource(uri: string, signal?: AbortSignal): Promise<MCPResourceContent>
|
|
getPrompt(name: string, args?: Record<string, string>, signal?: AbortSignal): Promise<MCPPromptMessage[]>
|
|
tools(): RemoteTool[]
|
|
resources(): RemoteResource[]
|
|
prompts(): RemotePrompt[]
|
|
}
|
|
|
|
// Transport interface
|
|
interface Transport {
|
|
send(message: MCPMessage): Promise<void>
|
|
onMessage(handler: (message: MCPMessage) => void): () => void
|
|
close(): Promise<void>
|
|
}
|
|
|
|
// 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<MCPServerConfig>
|
|
const MCPClientConfigSchema: z.ZodSchema<MCPClientConfig>
|
|
const MCPToolDefinitionSchema: z.ZodSchema<MCPToolDefinition>
|
|
const MCPResourceDefinitionSchema: z.ZodSchema<MCPResourceDefinition>
|
|
const MCPPromptDefinitionSchema: z.ZodSchema<MCPPromptDefinition>
|
|
const MCPToolResultSchema: z.ZodSchema<MCPToolResult>
|
|
const MCPContentItemSchema: z.ZodSchema<MCPContentItem>
|
|
const MCPResourceContentSchema: z.ZodSchema<MCPResourceContent>
|
|
const MCPPromptMessageSchema: z.ZodSchema<MCPPromptMessage>
|
|
|
|
// 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.
|