29 KiB
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"(perdefineCodesoutput 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 === falseand 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
onMessagehandler 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
sends a request - THEN the injected
fetchImplis invoked with the endpoint URL and the JSON-stringified body; messages arriving on the injectedEventSourceare dispatched toonMessage
Scenario: every transport refuses send after close
- WHEN
close()resolves andsend(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
AbortSignalwhile a request is in flight - THEN the dispatcher sends
notifications/cancelledwith the request id AND rejects the pending promise withMCPError(REQUEST_CANCELLED)
Scenario: incoming cancellation aborts the handler
- WHEN the dispatcher receives
notifications/cancelledfor an in-flight handler invocation - THEN the handler's
AbortSignalis 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
-32601and anMCPError(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
-32602and anMCPError(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 thenlistTools()is queried - THEN the returned list contains the tool
Scenario: duplicate registration is rejected
- WHEN
registerToolis 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 andinputSchemarejectsinvalidParams - THEN the call rejects with
MCPError(INVALID_PARAMS)andnotifications/cancelledis NOT emitted
Scenario: handler throws produce an error tool result
- WHEN the tool handler throws
- THEN
invokeToolresolves with{ content: [{ type: "text", text: "Error: …" }], isError: true }
Scenario: start twice raises ALREADY_CONNECTED
- WHEN
start(transport)is called a second time withoutstop() - 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 atools/callarrives - THEN the dispatcher responds with
-32001mapped toMCPError(UNAUTHORIZED)
Scenario: AbortSignal propagates to the handler
- WHEN
invokeTool("slow", params, { signal })is called and the signal is aborted - THEN the handler's
AbortSignalfires and the call rejects withMCPError(REQUEST_CANCELLED)
Scenario: progress callback emits notifications/progress
- WHEN the handler invokes
progress(token, 50, 100) - THEN the dispatcher sends
notifications/progresswith{ progressToken: token, progress: 50, total: 100 }
Scenario: markResourceUpdated fans out notifications/resources/updated
- WHEN a client has subscribed to URI
mem://xand the server callsmarkResourceUpdated("mem://x") - THEN the dispatcher sends
notifications/resources/updatedwith{ 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,runningreflecting 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
initializethennotifications/initializedandclient.initialized === trueresolves
Scenario: methods before connect raise NOT_INITIALIZED
- WHEN
callToolis invoked beforeconnect - THEN the call rejects with
MCPError(NOT_INITIALIZED)
Scenario: connect twice raises ALREADY_CONNECTED
- WHEN
connect(transport)is called a second time withoutdisconnect - THEN the second call rejects with
MCPError(ALREADY_CONNECTED)
Scenario: protocol version mismatch raises PROTOCOL_VERSION_MISMATCH
- WHEN the server's
InitializeResult.protocolVersionis not in the client's accepted set - THEN
connectrejects withMCPError(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
callToolis invoked with{ onProgress }and the server emitsnotifications/progress - THEN
onProgressis invoked with{ progress, total }
Scenario: AbortSignal cancels via notifications/cancelled
- WHEN the caller aborts the supplied signal mid-call
- THEN the server observes
notifications/cancelledand the client promise rejects withMCPError(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:updatedevent
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/createMessageand 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/listand 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"orstatus="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:openedfollowed byconnection:closed
Scenario: a request emits received and completed
- WHEN the server handles a request to completion
- THEN subscribers observe
request:receivedfollowed byrequest:completedcarrying the correctdurationMsandoutcome
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:orrequire("node:
Scenario: source tree contains no DOM globals
- WHEN the test harness greps the source tree
- THEN zero direct references to
document,window, ornavigator
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/MCPServerare 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"ANDsrc/e2e/client-edge-cases.test.ts::"client malformed initialize result raises PROTOCOL_INVALID"ANDsrc/e2e/client-edge-cases.test.ts::"client completion provider serves wire-side completion/complete"ANDsrc/e2e/client-edge-cases.test.ts::"client completion fallback returns empty values without provider"ANDsrc/e2e/client-edge-cases.test.ts::"client constructor wires injected clock and idGenerator"ANDsrc/e2e/client-edge-cases.test.ts::"client disconnect with reason emits closed-with-reason event"ANDsrc/e2e/client-edge-cases.test.ts::"client getPrompt without args uses default empty object"ANDsrc/e2e/client-edge-cases.test.ts::"client request honours an explicit zero timeoutMs option"ANDsrc/e2e/client-edge-cases.test.ts::"client list-changed catch handlers swallow refresh failures"ANDsrc/e2e/client-edge-cases.test.ts::"client ping handler responds when peer requests it"ANDsrc/e2e/client-edge-cases.test.ts::"callTool with no args uses the default empty object"`.