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

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" (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 sends 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"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"`.