Files
2026-05-27 23:29:53 +01:00

327 lines
29 KiB
Markdown

<!-- markdownlint-disable MD024 -->
# Capability Spec Delta: mcp (mcp)
**Package**: `@suaveplan/mcp`
**Status**: PROPOSED
> Every requirement below carries a `**Verified by:**` line at authoring time.
## ADDED Requirements
### Requirement: MCP_CODES registry via defineCodes
The package SHALL publish a stable `MCP_CODES` registry built with `defineCodes("MCP", […])` containing the leaves `PROTOCOL_INVALID`, `METHOD_NOT_FOUND`, `INVALID_PARAMS`, `INTERNAL_ERROR`, `UNAUTHORIZED`, `REQUEST_CANCELLED`, `TIMEOUT`, `TRANSPORT_CLOSED`, `TRANSPORT_ERROR`, `NOT_INITIALIZED`, `ALREADY_CONNECTED`, `DUPLICATE_REGISTRATION`, `NOT_FOUND`, `PROTOCOL_VERSION_MISMATCH`, and `HANDLER_FAILED`. The package SHALL export `MCPError`, `MCPTransportError`, and `MCPProtocolError` classes extending `SuaveplanError<MCPCode>` with public constructors that delegate to `super`. Every registered code MUST be reachable from a real call site exercised by a test.
#### Scenario: codes use the MCP namespace
- **WHEN** code consumers inspect `MCP_CODES.UNAUTHORIZED`
- **THEN** the value is the string `"MCP.UNAUTHORIZED"` (per `defineCodes` output convention)
#### Scenario: every code is thrown by at least one call site
- **WHEN** the test suite runs to completion
- **THEN** the per-code reachability harness records at least one throw / report carrying each registered code
**Verified by:** `src/error-codes/error-codes.test.ts::"namespaces every code under MCP."` AND `src/error-codes/error-codes.test.ts::"exposes the expected leaf names"` AND `src/error-codes/error-codes.test.ts::"carries the supplied code, message, cause, and context"` AND `src/error-codes/error-codes.test.ts::"every registered code is reachable from a real call site"`.
---
### Requirement: Zod schemas are the single source of truth for every wire type
The package SHALL define a Zod schema for every type that crosses the JSON-RPC wire: `JsonRpcRequestSchema`, `JsonRpcResponseSchema`, `JsonRpcNotificationSchema`, `JsonRpcMessageSchema` (discriminated union), `InitializeParamsSchema`, `InitializeResultSchema`, `ServerCapabilitiesSchema`, `ClientCapabilitiesSchema`, `ImplementationSchema`, `ToolSchema`, `ToolCallParamsSchema`, `ToolCallResultSchema`, `ContentItemSchema` (text/image/resource union), `ResourceSchema`, `ResourceTemplateSchema`, `ResourceContentSchema`, `ResourceReadParamsSchema`, `ResourceSubscribeParamsSchema`, `PromptSchema`, `PromptMessageSchema`, `PromptGetParamsSchema`, `PromptGetResultSchema`, `CompletionRequestSchema`, `CompletionResultSchema`, `SamplingMessageSchema`, `CreateMessageParamsSchema`, `CreateMessageResultSchema`, `RootSchema`, `LoggingLevelSchema`, `LoggingMessageParamsSchema`, `ProgressNotificationParamsSchema`, `CancelledNotificationParamsSchema`. Every public TypeScript type for a wire shape MUST be derived via `z.infer<>` from one of these schemas. No hand-rolled interface MAY substitute for a wire-type schema.
#### Scenario: every wire schema parses canonical sample payloads
- **WHEN** the test calls `.parse(sample)` on each exported schema with a canonical example
- **THEN** parsing succeeds and the inferred type matches expectations
#### Scenario: invalid payloads are rejected
- **WHEN** the test calls `.safeParse(brokenSample)` on a schema
- **THEN** `success === false` and the error path identifies the offending field
**Verified by:** `src/schemas/schemas.test.ts::"every exported schema parses its canonical example"` AND `src/schemas/schemas.test.ts::"rejects malformed JSON-RPC envelopes"` AND `src/schemas/schemas.test.ts::"discriminates request vs response vs notification"` AND `src/schemas/schemas.test.ts::"derives TypeScript types via z.infer"`.
---
### Requirement: Transport interface with three reference implementations
The package SHALL export a `Transport` interface with methods `send(message: JsonRpcMessage): Promise<void>`, `onMessage(handler: (message: JsonRpcMessage) => void): () => void`, and `close(): Promise<void>`, plus a `closed: boolean` getter. The package SHALL ship three reference implementations: `InMemoryTransport.pair()` returning two paired transports for in-process bridges; `createStdioTransport({ readable, writable })` framing newline-delimited JSON over injectable `AsyncIterable<Uint8Array>` and a writable handle; `createHttpSseTransport({ endpoint, fetchImpl, eventSourceImpl })` posting requests to `endpoint` via injected `fetch` and consuming server-pushed messages via the injected `EventSource` constructor. Every transport MUST raise `MCPTransportError(TRANSPORT_CLOSED)` if `send` is called after `close`.
#### Scenario: in-memory pair round-trips a message
- **WHEN** transport A's `send(message)` is called
- **THEN** transport B's registered `onMessage` handler observes the same message
#### Scenario: stdio transport frames newline-delimited JSON
- **WHEN** writable receives a frame
- **THEN** the frame is `JSON.stringify(message) + "\n"` and the reading side parses each line as a separate JSON-RPC message
#### Scenario: http+sse transport posts via fetchImpl and receives via eventSourceImpl
- **WHEN** the client `send`s a request
- **THEN** the injected `fetchImpl` is invoked with the endpoint URL and the JSON-stringified body; messages arriving on the injected `EventSource` are dispatched to `onMessage`
#### Scenario: every transport refuses send after close
- **WHEN** `close()` resolves and `send(message)` is invoked
- **THEN** the call rejects with `MCPTransportError(TRANSPORT_CLOSED)`
**Verified by:** `src/transport/in-memory/in-memory.test.ts::"round-trips a message between paired transports"` AND `src/transport/in-memory/in-memory.test.ts::"raises TRANSPORT_CLOSED after close"` AND `src/transport/stdio/stdio.test.ts::"frames newline-delimited JSON on send"` AND `src/transport/stdio/stdio.test.ts::"parses newline-delimited JSON from readable"` AND `src/transport/stdio/stdio.test.ts::"raises TRANSPORT_CLOSED after close"` AND `src/transport/http-sse/http-sse.test.ts::"posts requests via fetchImpl"` AND `src/transport/http-sse/http-sse.test.ts::"dispatches messages from eventSourceImpl"` AND `src/transport/http-sse/http-sse.test.ts::"raises TRANSPORT_CLOSED after close"`.
---
### Requirement: JSON-RPC dispatcher correlates requests, notifications, and cancellations
The package SHALL ship `JsonRpcDispatcher` providing: id allocation via injectable `IdGenerator`; pending-request map keyed by `JsonRpcId`; `request(method, params, options?)` returning `Promise<unknown>` that resolves on matching response and rejects on `error` envelope, `AbortSignal` abort (mapped to `MCPError(REQUEST_CANCELLED)`), or `timeoutMs` elapse (mapped to `MCPError(TIMEOUT)`); `notify(method, params)` that sends a notification (no id); `registerHandler(method, fn)` and `registerNotificationListener(method, fn)`; cancellation that on incoming `notifications/cancelled` aborts the matching in-flight handler invocation. The dispatcher MUST reject pending requests with `MCPTransportError(TRANSPORT_CLOSED)` when the underlying transport closes. It MUST reject unknown methods with JSON-RPC code `-32601` mapped to `MCPError(METHOD_NOT_FOUND)`. It MUST reject invalid params with `-32602` mapped to `MCPError(INVALID_PARAMS)`.
#### Scenario: a request is correlated with its response
- **WHEN** `request("ping")` is issued
- **THEN** the dispatcher writes a JSON-RPC envelope with a fresh id and resolves the returned promise when a response with the matching id arrives
#### Scenario: abort sends notifications/cancelled and rejects the pending request
- **WHEN** the caller aborts the supplied `AbortSignal` while a request is in flight
- **THEN** the dispatcher sends `notifications/cancelled` with the request id AND rejects the pending promise with `MCPError(REQUEST_CANCELLED)`
#### Scenario: incoming cancellation aborts the handler
- **WHEN** the dispatcher receives `notifications/cancelled` for an in-flight handler invocation
- **THEN** the handler's `AbortSignal` is aborted
#### Scenario: timeout rejects with TIMEOUT
- **WHEN** `request(method, params, { timeoutMs: 1 })` is issued and no response arrives within 1 ms (per injected clock+timers)
- **THEN** the promise rejects with `MCPError(TIMEOUT)`
#### Scenario: transport closure rejects every pending request
- **WHEN** the underlying transport closes with one or more pending requests
- **THEN** each pending promise rejects with `MCPTransportError(TRANSPORT_CLOSED)`
#### Scenario: unknown method yields -32601
- **WHEN** the dispatcher receives a request whose method has no registered handler
- **THEN** the dispatcher responds with JSON-RPC error code `-32601` and an `MCPError(METHOD_NOT_FOUND)`-shaped payload
#### Scenario: invalid params yield -32602
- **WHEN** the registered Zod schema for a method rejects the params
- **THEN** the dispatcher responds with JSON-RPC error code `-32602` and an `MCPError(INVALID_PARAMS)`-shaped payload carrying the Zod issue path
**Verified by:** `src/jsonrpc/dispatcher/dispatcher.test.ts::"correlates a request with its response"` AND `src/jsonrpc/dispatcher/dispatcher.test.ts::"aborts in-flight request and emits notifications/cancelled"` AND `src/jsonrpc/dispatcher/dispatcher.test.ts::"aborts handler invocation on incoming notifications/cancelled"` AND `src/jsonrpc/dispatcher/dispatcher.test.ts::"rejects with TIMEOUT after timeoutMs"` AND `src/jsonrpc/dispatcher/dispatcher.test.ts::"rejects pending requests on transport close"` AND `src/jsonrpc/dispatcher/dispatcher.test.ts::"responds with -32601 for unknown method"` AND `src/jsonrpc/dispatcher/dispatcher.test.ts::"responds with -32602 for Zod-rejected params"`.
---
### Requirement: MCPServer registers and exposes tools, resources, and prompts
The package SHALL ship `MCPServer` with `registerTool(definition)`, `unregisterTool(name)`, `registerResource(definition)`, `unregisterResource(uri)`, `registerPrompt(definition)`, `unregisterPrompt(name)`, `registerRoot(root)`, `unregisterRoot(uri)`, `listTools()`, `listResources()`, `listResourceTemplates()`, `listPrompts()`, `listRoots()`, `invokeTool(name, params, options?)`, `readResource(uri, options?)`, `getPromptMessages(name, args?, options?)`, `setAuthorizer(fn)`, `setSamplingProvider(fn)`, `setCompletionProvider(fn)`, `setLogLevel(level)`, `markResourceUpdated(uri)`, `start(transport)`, `stop()`, and a `getInfo()` summary. Duplicate registration MUST throw `MCPError(DUPLICATE_REGISTRATION)`. Lookup miss MUST throw `MCPError(NOT_FOUND)`. `invokeTool` MUST validate `params` against the tool's declared `inputSchema` (when present as a Zod schema) and reject with `MCPError(INVALID_PARAMS)` on failure. Handler throws MUST be wrapped into `MCPToolResult.isError === true` content carrying the failure message. `start` MUST throw `MCPError(ALREADY_CONNECTED)` if called twice without an intervening `stop`.
#### Scenario: register and list a tool
- **WHEN** `registerTool({ name: "echo", description, inputSchema, handler })` is called and then `listTools()` is queried
- **THEN** the returned list contains the tool
#### Scenario: duplicate registration is rejected
- **WHEN** `registerTool` is called twice with the same name
- **THEN** the second call throws `MCPError(DUPLICATE_REGISTRATION)`
#### Scenario: invoke validates params against Zod inputSchema
- **WHEN** `invokeTool("echo", invalidParams)` is called and `inputSchema` rejects `invalidParams`
- **THEN** the call rejects with `MCPError(INVALID_PARAMS)` and `notifications/cancelled` is NOT emitted
#### Scenario: handler throws produce an error tool result
- **WHEN** the tool handler throws
- **THEN** `invokeTool` resolves with `{ content: [{ type: "text", text: "Error: …" }], isError: true }`
#### Scenario: start twice raises ALREADY_CONNECTED
- **WHEN** `start(transport)` is called a second time without `stop()`
- **THEN** the second call rejects with `MCPError(ALREADY_CONNECTED)`
#### Scenario: authorize hook gates the call
- **WHEN** `setAuthorizer(req => req.method !== "tools/call")` is set and a `tools/call` arrives
- **THEN** the dispatcher responds with `-32001` mapped to `MCPError(UNAUTHORIZED)`
#### Scenario: AbortSignal propagates to the handler
- **WHEN** `invokeTool("slow", params, { signal })` is called and the signal is aborted
- **THEN** the handler's `AbortSignal` fires and the call rejects with `MCPError(REQUEST_CANCELLED)`
#### Scenario: progress callback emits notifications/progress
- **WHEN** the handler invokes `progress(token, 50, 100)`
- **THEN** the dispatcher sends `notifications/progress` with `{ progressToken: token, progress: 50, total: 100 }`
#### Scenario: markResourceUpdated fans out notifications/resources/updated
- **WHEN** a client has subscribed to URI `mem://x` and the server calls `markResourceUpdated("mem://x")`
- **THEN** the dispatcher sends `notifications/resources/updated` with `{ uri: "mem://x" }`
#### Scenario: getInfo summarises registrations
- **WHEN** `getInfo()` is queried after registering 2 tools, 1 resource, and 1 prompt
- **THEN** the result reports `tools: 2`, `resources: 1`, `prompts: 1`, `running` reflecting the lifecycle
**Verified by:** `src/server/mcp-server/mcp-server.test.ts::"registers and lists a tool"` AND `src/server/mcp-server/mcp-server.test.ts::"rejects duplicate tool registration"` AND `src/server/mcp-server/mcp-server.test.ts::"validates params against Zod inputSchema"` AND `src/server/mcp-server/mcp-server.test.ts::"wraps handler errors into isError result"` AND `src/server/mcp-server/mcp-server.test.ts::"raises ALREADY_CONNECTED on second start"` AND `src/server/mcp-server/mcp-server.test.ts::"authorize hook rejects calls"` AND `src/server/mcp-server/mcp-server.test.ts::"AbortSignal aborts handler and rejects with REQUEST_CANCELLED"` AND `src/server/mcp-server/mcp-server.test.ts::"progress callback emits notifications/progress"` AND `src/server/mcp-server/mcp-server.test.ts::"markResourceUpdated fans out notifications/resources/updated"` AND `src/server/mcp-server/mcp-server.test.ts::"getInfo summarises registrations"` AND `src/server/mcp-server/mcp-server.test.ts::"resource and prompt registration mirrors tool behaviour"`.
---
### Requirement: MCPClient performs the MCP initialize handshake and exposes the full method surface
The package SHALL ship `MCPClient` with `connect(transport)`, `disconnect()`, `initialized` boolean getter, `refreshTools()`, `refreshResources()`, `refreshResourceTemplates()`, `refreshPrompts()`, `refreshRoots()`, `callTool(name, params, options?)`, `readResource(uri, options?)`, `subscribeResource(uri)`, `unsubscribeResource(uri)`, `getPrompt(name, args?, options?)`, `complete(request, options?)`, `setLogLevel(level)`, `setSamplingProvider(fn)`, `setRootsProvider(fn)`, and accessors `tools()`, `resources()`, `resourceTemplates()`, `prompts()`, `roots()`. `connect(transport)` MUST: open the transport, send `initialize` with declared `ClientCapabilities`, await the `InitializeResult`, emit `notifications/initialized`, populate the initial tool/resource/prompt/root caches via `tools/list` / `resources/list` / `prompts/list` / `roots/list`. Calling any other method before `connect` MUST throw `MCPError(NOT_INITIALIZED)`. `connect` called twice without `disconnect` MUST throw `MCPError(ALREADY_CONNECTED)`. Protocol version mismatch on the `initialize` result MUST throw `MCPError(PROTOCOL_VERSION_MISMATCH)`.
#### Scenario: connect performs the MCP initialize handshake
- **WHEN** `connect(transport)` is called against a paired server
- **THEN** the server observes `initialize` then `notifications/initialized` and `client.initialized === true` resolves
#### Scenario: methods before connect raise NOT_INITIALIZED
- **WHEN** `callTool` is invoked before `connect`
- **THEN** the call rejects with `MCPError(NOT_INITIALIZED)`
#### Scenario: connect twice raises ALREADY_CONNECTED
- **WHEN** `connect(transport)` is called a second time without `disconnect`
- **THEN** the second call rejects with `MCPError(ALREADY_CONNECTED)`
#### Scenario: protocol version mismatch raises PROTOCOL_VERSION_MISMATCH
- **WHEN** the server's `InitializeResult.protocolVersion` is not in the client's accepted set
- **THEN** `connect` rejects with `MCPError(PROTOCOL_VERSION_MISMATCH)` and the transport is closed
#### Scenario: callTool round-trips with the server
- **WHEN** the client calls `callTool("echo", { msg: "hi" })`
- **THEN** the server's registered handler executes and the client receives the tool's `MCPToolResult`
#### Scenario: progress notifications are forwarded to onProgress
- **WHEN** `callTool` is invoked with `{ onProgress }` and the server emits `notifications/progress`
- **THEN** `onProgress` is invoked with `{ progress, total }`
#### Scenario: AbortSignal cancels via notifications/cancelled
- **WHEN** the caller aborts the supplied signal mid-call
- **THEN** the server observes `notifications/cancelled` and the client promise rejects with `MCPError(REQUEST_CANCELLED)`
#### Scenario: subscribeResource updates the cache on notifications/resources/updated
- **WHEN** the client has subscribed to a URI and the server emits `notifications/resources/updated`
- **THEN** the client refreshes the resource and emits the `resource:updated` event
#### Scenario: refreshes update the cached lists
- **WHEN** the client calls `refreshTools()`
- **THEN** `tools()` returns the freshly listed remote tools
#### Scenario: sampling provider is reachable via createMessage
- **WHEN** the server invokes `sampling/createMessage` and the client has a registered sampling provider
- **THEN** the provider is invoked with the request params and its return value is sent back as the response
#### Scenario: roots provider is reachable via roots/list
- **WHEN** the server invokes `roots/list` and the client has a registered roots provider
- **THEN** the provider is invoked and its return value is sent back as the response
**Verified by:** `src/client/mcp-client/mcp-client.test.ts::"connect performs the MCP initialize handshake"` AND `src/client/mcp-client/mcp-client.test.ts::"methods before connect raise NOT_INITIALIZED"` AND `src/client/mcp-client/mcp-client.test.ts::"connect twice raises ALREADY_CONNECTED"` AND `src/client/mcp-client/mcp-client.test.ts::"protocol version mismatch raises PROTOCOL_VERSION_MISMATCH"` AND `src/client/mcp-client/mcp-client.test.ts::"callTool round-trips with the server"` AND `src/client/mcp-client/mcp-client.test.ts::"forwards progress notifications to onProgress"` AND `src/client/mcp-client/mcp-client.test.ts::"AbortSignal cancels via notifications/cancelled"` AND `src/client/mcp-client/mcp-client.test.ts::"subscribeResource updates cache on notifications/resources/updated"` AND `src/client/mcp-client/mcp-client.test.ts::"refreshes update the cached lists"` AND `src/client/mcp-client/mcp-client.test.ts::"sampling provider responds to createMessage"` AND `src/client/mcp-client/mcp-client.test.ts::"roots provider responds to roots/list"` AND `src/client/mcp-client/mcp-client.test.ts::"reads resources and gets prompts"` AND `src/client/mcp-client/mcp-client.test.ts::"completes via completion/complete"` AND `src/client/mcp-client/mcp-client.test.ts::"setLogLevel emits logging/setLevel"`.
---
### Requirement: Telemetry meter records every request and notification
The package SHALL register an OTel meter at name `@suaveplan/mcp` exposing `mcp_request_total` counter (tagged `direction`, `method`, `status`), `mcp_notification_total` counter (tagged `direction`, `method`), and `mcp_request_duration_ms` histogram (tagged `direction`, `method`). Every dispatcher request MUST increment the counter and record the histogram. Every notification (sent or received) MUST increment the notification counter.
#### Scenario: request counter increments per request
- **WHEN** a request is issued and completes successfully
- **THEN** `mcp_request_total{direction="client",method="…",status="success"}` increases by 1
#### Scenario: error and cancelled outcomes are tagged
- **WHEN** a request fails or is cancelled
- **THEN** the counter increment carries `status="error"` or `status="cancelled"` accordingly
#### Scenario: notification counter increments per notification
- **WHEN** a notification is sent
- **THEN** `mcp_notification_total{direction="…",method="…"}` increases by 1
**Verified by:** `src/telemetry/telemetry.test.ts::"records mcp_request_total per request"` AND `src/telemetry/telemetry.test.ts::"tags error and cancelled outcomes"` AND `src/telemetry/telemetry.test.ts::"records mcp_notification_total per notification"` AND `src/telemetry/telemetry.test.ts::"records mcp_request_duration_ms histogram"`.
---
### Requirement: Lifecycle events are emitted via EventBus
The package SHALL expose `events: EventBus<MCPEventMap>` from both `MCPServer` and `MCPClient`, emitting `connection:opened` (payload `{ side, dispatcherId }`), `connection:closed` (payload `{ side, dispatcherId, reason? }`), `request:received` (payload `{ side, method, id }`), `request:completed` (payload `{ side, method, id, durationMs, outcome }`), and `notification:emitted` (payload `{ side, method, params }`).
#### Scenario: connect/disconnect emits open/close events
- **WHEN** the client connects then disconnects
- **THEN** subscribers observe `connection:opened` followed by `connection:closed`
#### Scenario: a request emits received and completed
- **WHEN** the server handles a request to completion
- **THEN** subscribers observe `request:received` followed by `request:completed` carrying the correct `durationMs` and `outcome`
**Verified by:** `src/events/events.test.ts::"emits connection:opened and connection:closed"` AND `src/events/events.test.ts::"emits request:received and request:completed"` AND `src/events/events.test.ts::"emits notification:emitted"`.
---
### Requirement: Full end-to-end handshake walks the entire MCP method surface
The package SHALL include an end-to-end test that pairs a real `MCPServer` and `MCPClient` over `InMemoryTransport.pair()` and exercises in order: `initialize`, `notifications/initialized`, `tools/list`, `tools/call` (with progress + abort variants), `resources/list`, `resources/templates/list`, `resources/read`, `resources/subscribe`, server-side `markResourceUpdated`, `resources/unsubscribe`, `prompts/list`, `prompts/get`, `completion/complete`, `sampling/createMessage`, `roots/list`, `logging/setLevel`, `ping`, and `disconnect`. Every method MUST round-trip successfully.
#### Scenario: every MCP method completes the round-trip
- **WHEN** the e2e test runs to completion
- **THEN** every assertion passes and no JSON-RPC error envelope appears
**Verified by:** `src/e2e/handshake.test.ts::"walks the full MCP method surface end-to-end"` AND `src/e2e/handshake.test.ts::"progress and cancellation propagate over the wire"` AND `src/e2e/handshake.test.ts::"resource subscriptions deliver updates"`.
---
### Requirement: Universal-tier purity
The package SHALL be universal-tier: zero browser-only DOM imports, zero Node-only built-in imports (no `node:fs`, `node:net`, `node:process`, `node:stream`, etc.), zero React. All runtime dependencies that touch I/O (streams, fetch, EventSource) MUST be passed in via the public API. The `package.json` MUST declare `"sideEffects": false` and MUST NOT declare an `engines.node` floor that contradicts universal targets.
#### Scenario: source tree contains no Node built-in imports
- **WHEN** the test harness greps the source tree
- **THEN** zero matches for `from "node:` or `require("node:`
#### Scenario: source tree contains no DOM globals
- **WHEN** the test harness greps the source tree
- **THEN** zero direct references to `document`, `window`, or `navigator`
**Verified by:** `src/e2e/handshake.test.ts::"source tree contains no Node built-in imports"` AND `src/e2e/handshake.test.ts::"source tree contains no DOM globals"`.
---
### Requirement: Edge-case branches reach 100% coverage at the package gate
The package SHALL hold 100% statement / branch / function / line coverage at the publish gate (`bunx turbo test:coverage --filter=@suaveplan/mcp`). Every defensive branch, error path, default-parameter, optional-field, and provider-fallback in `mcp-client.ts`, `mcp-server.ts`, `dispatcher.ts`, `transport/{stdio,in-memory,http-sse}.ts` MUST be reachable from a real call site exercised by the test suite — no `/* istanbul ignore */` or `// biome-ignore` suppressions, no dead defensive code left behind. Dead branches that no caller can reach MUST be deleted, not silenced.
#### Scenario: defensive transport-error and abort-race paths are reachable
- **WHEN** the dispatcher's send-side rejection, handler-throw-race-with-abort, malformed-cancellation/progress params, double-close, and non-Error handler throws are exercised
- **THEN** every corresponding branch is hit and the coverage gate stays at 100%
#### Scenario: client and server provider fall-backs and optional-field branches are reachable
- **WHEN** `MCPClient` / `MCPServer` are exercised with missing providers, omitted optional descriptions / mimeTypes / arguments, undefined `_meta`, root-level Zod errors, and no-arg call-tool / get-prompt invocations
- **THEN** every corresponding branch is hit and the coverage gate stays at 100%
**Verified by:** `src/e2e/dispatcher-edge-cases.test.ts::"transport send throwing maps to MCPTransportError"` AND `src/e2e/dispatcher-edge-cases.test.ts::"unregisterHandler removes a registered method"` AND `src/e2e/dispatcher-edge-cases.test.ts::"onNotification reuses an existing listener set"` AND `src/e2e/dispatcher-edge-cases.test.ts::"request send rejection cleans up pending entry"` AND `src/e2e/dispatcher-edge-cases.test.ts::"request send non-Error rejection still cleans up"` AND `src/e2e/dispatcher-edge-cases.test.ts::"handler abort race rejects without responding"` AND `src/e2e/dispatcher-edge-cases.test.ts::"error response codes round-trip through mapMcpCodeToJsonRpc"` AND `src/e2e/dispatcher-edge-cases.test.ts::"raw JSON-RPC wire codes map through mapJsonRpcCode"` AND `src/e2e/dispatcher-internals-edge-cases.test.ts::"dispatcher stringifies numeric ids distinctly from strings"` AND `src/e2e/dispatcher-internals-edge-cases.test.ts::"dispatcher close is idempotent over double-close"` AND `src/e2e/dispatcher-internals-edge-cases.test.ts::"dispatcher schema-validated request without params uses default {}"` AND `src/e2e/dispatcher-internals-edge-cases.test.ts::"dispatcher sendProgress omits total when undefined"` AND `src/e2e/dispatcher-internals-edge-cases.test.ts::"dispatcher silently drops unparseable wire messages"` AND `src/e2e/dispatcher-internals-edge-cases.test.ts::"CancelledNotificationParamsSchema parse failure is skipped silently"` AND `src/e2e/dispatcher-internals-edge-cases.test.ts::"setProgressToken with already-tagged pending entry skips it"` AND `src/e2e/dispatcher-internals-edge-cases.test.ts::"setProgressToken is no-op when method has no matching pending entry"` AND `src/e2e/dispatcher-internals-edge-cases.test.ts::"handler throwing a non-Error stringifies via String(err)"` AND `src/e2e/server-edge-cases.test.ts::"server without sampling/completion provider falls back"` AND `src/e2e/server-edge-cases.test.ts::"tool with paramsSchema validates over the dispatcher"` AND `src/e2e/server-edge-cases.test.ts::"server invokeTool with paramsSchema replaces params with parsed data"` AND `src/e2e/server-edge-cases.test.ts::"tool handler returning a ContentItem array is normalised"` AND `src/e2e/server-edge-cases.test.ts::"list helpers surface optional description/mimeType/arguments"` AND `src/e2e/server-edge-cases.test.ts::"markResourceUpdated before start is a silent no-op"` AND `src/e2e/server-edge-cases.test.ts::"server invokeTool wraps non-Error handler throws into isError"` AND `src/e2e/server-edge-cases.test.ts::"server start wires injected clock and idGenerator"` AND `src/e2e/server-wire-edge-cases.test.ts::"tools/call without arguments uses empty-object schema input"` AND `src/e2e/server-wire-edge-cases.test.ts::"tools/call without _meta has undefined progressToken"` AND `src/e2e/server-wire-edge-cases.test.ts::"createSamplingMessage and listClientRoots forward signal"` AND `src/e2e/server-wire-edge-cases.test.ts::"emitLog without logger argument omits the logger field"` AND `src/e2e/server-wire-edge-cases.test.ts::"authorize hook accepting the call passes the throw branch"` AND `src/e2e/server-wire-edge-cases.test.ts::"invokeTool handler progress and abort-listener fire"` AND `src/e2e/server-wire-edge-cases.test.ts::"Zod root-level rejection formats as \`(root)\` in error message"` AND `src/e2e/client-edge-cases.test.ts::"client malformed initialize result raises PROTOCOL_INVALID"` AND `src/e2e/client-edge-cases.test.ts::"client completion provider serves wire-side completion/complete"` AND `src/e2e/client-edge-cases.test.ts::"client completion fallback returns empty values without provider"` AND `src/e2e/client-edge-cases.test.ts::"client constructor wires injected clock and idGenerator"` AND `src/e2e/client-edge-cases.test.ts::"client disconnect with reason emits closed-with-reason event"` AND `src/e2e/client-edge-cases.test.ts::"client getPrompt without args uses default empty object"` AND `src/e2e/client-edge-cases.test.ts::"client request honours an explicit zero timeoutMs option"` AND `src/e2e/client-edge-cases.test.ts::"client list-changed catch handlers swallow refresh failures"` AND `src/e2e/client-edge-cases.test.ts::"client ping handler responds when peer requests it"` AND `src/e2e/client-edge-cases.test.ts::"callTool with no args uses the default empty object"`.