Construct over an explicit ClientTransport. The client installs its inbound dispatcher (and, for the HTTP transport, the per-message header / cancelled-response callbacks) on the transport.
Record a tool's inputSchema (keyed by tool name) so the draft client can mirror its x-mcp-header-annotated arguments into Mcp-Param-* headers on a later tools/call. Normally populated automatically by listTools; exposed so callers that obtain a Tool descriptor by other means (e.g. a cached tools/list result, or a notifications/tools/list_changed refresh) can register it too. A non-object schema is ignored.
tools/call. Per-request progressToken / logLevel / onProgress are carried in opts (see RequestOptions).
Typed-arguments convenience: serialize the struct args to its JSON wire shape via vibe's serializeToJson and forward to the Json-arguments callTool. Lets callers pass a strongly typed parameter struct instead of hand-building a Json object.
Convenience overload for the dominant single-callback case: route this call's progress to onProgress (a unique token is minted) without padding the leading RequestOptions fields.
Typed-arguments twin of the progress-callback callTool convenience overload: serialize the struct args and route this call's progress to onProgress.
tools/call for a tool whose descriptor (and therefore outputSchema) is known — typically one returned by listTools. When the client has output- schema validation enabled (see enableOutputSchemaValidation) and tool carries an outputSchema, the returned structuredContent is validated against it: per the spec, "Clients SHOULD validate structured results against this schema." A non-conforming result raises a clear McpException rather than being accepted silently.
Cancel an in-flight request by sending notifications/cancelled for requestId (basic/utilities/cancellation: "Either side can send a cancellation notification ... to indicate that a previously-issued request should be terminated"). After this call, any response the server still sends for requestId is ignored, per "The sender of the cancellation notification SHOULD ignore any response to the request that arrives afterward". reason is an optional free-form explanation included in the notification when non-empty.
Release the underlying transport (stdio terminates the subprocess; HTTP stops any background streams).
completion/complete — request autocompletion suggestions for an argument of a prompt or resource template, per server/utilities/completion §"Requesting Completions". reference identifies what is being completed (use CompletionReference.forPrompt / forResource); argumentName and argumentValue are the argument being filled in and its partial value. context, when non-null, supplies previously-resolved argument values ({name: value}) so the server can give context-aware completions. Per-request progressToken / logLevel / onProgress are carried in opts (see RequestOptions).
Connect to a server whose protocol era is unknown, per the transport backward-compatibility rules. Probes server/discover first: - success → modern server; switch to the newest mutually-supported version (stateless draft mode if that version uses per-request _meta, otherwise an initialize handshake for that stable version); - Method not found (-32601) → legacy server; fall back to the initialize handshake; - UnsupportedProtocolVersionError (-32004) → modern server; pick from the advertised supported list rather than falling back. Returns the negotiated protocol version. Throws if there is no mutually supported version, or on any other error.
server/discover (draft): fetch the server's supported versions, capabilities, and identity.
The capabilities actually advertised on the wire: capabilities augmented from the installed handlers when autoAdvertiseCapabilities is true. Installing onSampling advertises sampling, onElicitation advertises elicitation (defaulting to the form submode unless a submode is already declared), and onListRoots advertises roots. Explicit flags already set on capabilities are never cleared. When autoAdvertiseCapabilities is false, capabilities is returned verbatim. Drives both the initialize handshake and the draft per-request _meta.
Switch to the stateless modern (>= 2026-07-28) protocol: no initialize handshake; every request carries _meta (protocolVersion / clientInfo / clientCapabilities) and the standard Mcp-Method / Mcp-Name / MCP-Protocol-Version headers. Call discover() for up-front version selection, or just issue requests.
Opt in to validating tool results against the tool's outputSchema when calling callTool(Tool, ...). Off by default; existing call sites are unaffected.
prompts/get. Per-request progressToken / logLevel / onProgress are carried in opts (see RequestOptions).
Typed-arguments convenience: serialize the struct args to its JSON wire shape via vibe's serializeToJson and forward to the Json-arguments getPrompt. Mirrors the typed callTool(T) so callers can pass a strongly typed prompt-argument struct instead of hand-building a Json object.
ClientProtocol.headersFor: compute the protocol-derived request headers for an outgoing message, which the transport pulls through the ClientProtocol seam. For a draft client this is the MCP-Protocol-Version header plus the standard Mcp-Method / Mcp-Name headers and any Mcp-Param-* mirrored tool arguments (draft basic/transports). For a stable client it is just MCP-Protocol-Version after initialize. Called with Json.undefined (no message — e.g. the GET server stream) it returns only the version header. Keeps the draft-header logic and the tool inputSchema cache in the client rather than the transport.
Perform the initialize handshake and send notifications/initialized.
Test seam: run injectModernMeta from a unittest so the per-request draft _meta (including the logging opt-in) can be asserted without a live server. Production code never calls this.
ClientProtocol.isCancelled: the transport consults this through the ClientProtocol seam to drop a late response for a request the client has cancelled (basic/utilities/cancellation). The id is evicted from the cancelled-id set once its late response has been observed and dropped here, so the set does not grow without bound over a long-lived client; an id whose late response never arrives is reclaimed by the size backstop in cancel.
Whether a response with JSON-RPC id id belongs to a request this client has cancelled (and so should be ignored per basic/utilities/cancellation). Exposed for tests; cheap membership check on the cancelled-id set.
prompts/list, auto-paginated. Returns the drained ListPromptsResult: prompts aggregates every page, nextCursor is null, and cache carries the first page's parsed freshness hint.
resources/templates/list, auto-paginated. Returns the drained ListResourceTemplatesResult (URI templates clients can expand and resources/read). resourceTemplates aggregates every page, nextCursor is null, and cache carries the first page's parsed freshness hint.
resources/list, auto-paginated. Returns the drained ListResourcesResult: resources aggregates every page, nextCursor is null, and cache carries the first page's parsed freshness hint.
tools/list, following pagination cursors to completion. Returns the drained ListToolsResult: tools aggregates every page's items, nextCursor is null, and cache carries the first page's parsed draft CacheableResult freshness hint (if any).
Emit notifications/roots/list_changed, informing the server that this client's set of roots has changed. Per client/roots §Root List Changes, a client that advertises the roots listChanged capability MUST send this notification whenever its roots change. Call this after updating the roots returned by onListRoots (or after setRoots).
ping — returns when the server acknowledges.
The protocol version negotiated with the server (valid after initialize).
resources/read. Per-request progressToken / logLevel / onProgress are carried in opts (see RequestOptions).
Register the elicitationIds announced by a URLElicitationRequiredError (-32042) so a subsequent notifications/elicitation/complete correlates and is forwarded. error is the JSON-RPC error object; the URL-mode elicitations live under error.data.elicitations[], each an ElicitRequestURLParams carrying an elicitationId (2025-11-25 / draft schema URLElicitationRequiredError). Ids already tracked are left as-is so an in-flight completion state is not reset; malformed entries are skipped.
Send an arbitrary client-originated JSON-RPC notification to the server (no reply expected). This is the public entry point for client→server notifications such as notifications/roots/list_changed; the lifecycle's notifications/initialized is sent automatically by initialize.
The server capabilities advertised at connect time. Populated by both the stable initialize handshake and the stateless draft server/discover path; default-constructed before either has run.
The server's identity (name/version) advertised at connect time. Populated by both the initialize handshake and draft discovery; default-constructed before either has run.
The optional server instructions advertised at connect time (null when the server sent none, or before connecting). Populated by both the initialize handshake and draft discovery.
Attach an OAuth bearer access token, sent as `Authorization: Bearer <token>` on every subsequent request (HTTP transport). Pass an empty string to clear it; a no-op over stdio.
Test seam: set the id cancel() treats as the (uncancellable) initialize request, without driving the live initialize handshake.
Set the minimum severity of notifications/message the server should emit to this client.
an empty level clears the draft opt-in.
Register the client's filesystem roots using the typed Root API, mirroring the typed result types the SDK provides for tools, resources and prompts. This installs an onListRoots handler that answers roots/list with a properly-shaped {roots: [{uri, name}]} envelope, so callers need not hand-construct the raw JSON. Each uri MUST be a file:// URI per client/roots §Data Types.
Open the standalone server->client stream (HTTP GET SSE), so the server can deliver sampling / elicitation / roots requests and notifications outside of any request response. A no-op on stdio (and tolerated as a no-op when an HTTP server does not offer the stream).
resources/subscribe / resources/unsubscribe.
Open a draft subscriptions/listen notification stream (draft basic/utilities/subscriptions). This replaces the removed resources/subscribe RPC and the standalone HTTP GET notification endpoint: it POSTs subscriptions/listen with a {notifications:{...}} filter (built from filter), the server upgrades the response to a long-lived text/event-stream, and this client reads it on a background task — delivering the leading notifications/subscriptions/acknowledged and every subsequent opted-in change notification to onNotification (and onProgress for progress). Returns a SubscriptionStream handle; call its cancel()/close() to stop listening and close the stream.
Build the completion/complete request params. Separated from complete so the param shaping can be unit-tested without a live server.
Build the prompts/get params, optionally attaching a progress token.
Build the resources/read params, optionally attaching a progress token.
Build the subscriptions/listen params, nesting the filter under params.notifications exactly as the draft spec's SubscriptionFilter requires (boolean list-changed flags emitted only when set; resourceSubscriptions as a string array of URIs). Separated so the param shaping can be unit-tested without a live server.
Build the tools/call params, optionally attaching a progress token. Separated so the param shaping (including _meta.progressToken) can be unit-tested without a live server.
Build the tools/call params with any gathered MRTR (SEP-2322) input responses attached as the top-level params.inputResponses map, and the opaque requestState echoed back as params.requestState. Per SEP-2322 these are RequestParams fields, NOT _meta entries. With no responses and no requestState this is identical to the plain buildToolCallParams. Separated as a package static so the resubmission param shaping can be unit-tested without a live server.
Filter out any tool whose inputSchema has an invalid x-mcp-header annotation, per the draft requirement that a client MUST exclude such tools from tools/list (server/tools #x-mcp-header). Each excluded tool is reported via logWarn (tool name + the validation reason). Valid tools pass through unchanged and in order. Separated as a pure static helper so the exclusion can be unit-tested without a live server; listTools calls it only for a draft session (the feature is draft-only and HTTP-transport-specific).
Build a client over the Streamable HTTP transport at url.
Compute the Mcp-Param-* headers to emit for a tools/call, given the tool's inputSchema and the call arguments. Uses the path-aware mcp.protocol.modern.paramHeaders, which discovers every valid x-mcp-header annotation at *any* nesting depth (not only top-level properties), and descends each annotation's path into the arguments object: for each path segment it indexes into the current Json object; if any intermediate node is absent, null, or not an object, the header is skipped (emitting none). When the full path resolves to a present, non-null primitive (string / int / bigInt / bool — number/float is already excluded by header validation), the value is encoded with encodeHeaderValue and emitted under ParamHeader.header. This preserves the spec's absent/null omission semantics (draft basic/transports mirroring table). Array-item paths (which cross an items schema) are not mirrored: a single repeated header name cannot unambiguously represent per-element values, so they are skipped — only the well-defined object-nesting case is handled. Separated as a pure static helper so the mirroring can be unit-tested without a live server.
Resolve exeName to an absolute path next to the running executable (buildPath(dirName(thisExePath()), exeName)). When that path does not exist but a .exe-suffixed sibling does, the suffixed path is returned (the Windows / bare-name fallback). Separated from spawnSibling so the path-resolution can be unit-tested without actually spawning a subprocess.
Launch an MCP server as a subprocess and build a client over its stdin/stdout (stderr inherited for logging). command is the command line (command[0] is the executable). The returned client is NOT yet initialized — call initialize() (or ping() for a stateless probe). close() runs the MCP stdio shutdown sequence on the subprocess.
Launch an MCP server binary that ships *next to this executable* and build a client over its stdin/stdout. Resolves exeName against the running program's own directory (dirName(thisExePath())) — the common case of a host bundling a sibling helper server — then spawns [resolvedPath] ~ extraArgs. On Windows (or whenever the bare resolved path does not exist) a .exe suffix is tried as a fallback. As with spawn, the returned client is NOT yet initialized — call initialize() (or ping()).
Build a client over the stdio transport, exchanging newline-delimited JSON-RPC over the supplied readLine/writeLine channel (symmetric to mcp.transport.stdio.serveStdio). readLine returns the next server line (without its terminator) or null at end-of-input; writeLine emits one message line (the sink appends the terminator).
Validate a CallToolResult's structuredContent against tool's outputSchema, independent of the opt-in flag. Returns an empty string when it conforms (including when the tool has no output schema or the result has no structured content), otherwise a description of the first violation. Exposed so callers can validate explicitly without enabling automatic validation.
Attach MRTR (SEP-2322) input responses to a request as the top-level params.inputResponses map (id -> bare client result). An empty responses list returns params unchanged. Exposed so callers can attach answers to a hand-built params object.
Echo the server's opaque MRTR (SEP-2322) requestState back on a retried request as the top-level params.requestState field. The client MUST NOT inspect or modify the value, and MUST NOT include one when the server sent none — so an empty requestState returns params unchanged.
When true (the default), the capabilities advertised at initialize (and in every draft per-request _meta) are derived from which handlers are installed: onSampling implies sampling, onElicitation implies elicitation (form submode), onListRoots implies roots. Anything already set on capabilities is preserved (e.g. submodes, listChanged, tasks), so this only ever adds the presence flags a handler implies and never clears an explicit advertisement. Set to false to advertise exactly capabilities and nothing more (the explicit-override escape hatch).
Capabilities this client advertises at initialize. Treated as a baseline: unless autoAdvertiseCapabilities is disabled, the capabilities actually sent are this value augmented from the installed handlers (see effectiveCapabilities), so installing onSampling/onElicitation/ onListRoots auto-advertises sampling/elicitation/roots.
This client's identity.
Handler for elicitation/create; receives the typed ElicitParams and returns the typed ElicitResult. Null => unsupported.
Handler for roots/list; returns the typed ListRootsResult. Null => unsupported. (roots/list carries no meaningful params, so the handler takes none; prefer setRoots for the common static-roots case.)
Typed observer for notifications/message (server/utilities/logging).
Observer for inbound notifications (progress, message, resource updates).
Test seam: when set, notify routes the built notification message here instead of POSTing it, so the public notification API can be exercised without a live server. Production code never sets this.
Typed observer for notifications/progress (basic/utilities/progress).
Test seam: when set, rpc routes the (method, params) pair here and returns the delegate's result payload instead of POSTing to a server, so paginating request methods can be exercised without a live server. Production code never sets this.
Handler for sampling/createMessage; receives the typed CreateMessageRequest params and returns the typed CreateMessageResult. Null => unsupported (the client answers roots/list-style with Method not found). The SDK validates the request's tool-result message constraints (client/sampling §Error Handling) before invoking this.
The protocol-derived headers for an outgoing message: the MCP-Protocol-Version header plus, for a draft client, the standard Mcp-Method / Mcp-Name headers and any Mcp-Param-* mirrored tool arguments. Called with Json.undefined (no message — e.g. the GET server stream) it returns only the version header. Never includes Accept / Content-Type / Authorization / Mcp-Session-Id / Last-Event-ID — those are the transport's own.
Whether a response with the given JSON-RPC id belongs to a request the client has cancelled (basic/utilities/cancellation): such a response is dropped rather than returned.
A Model Context Protocol client, transport-agnostic.
Speaks pure JSON-RPC + protocol logic over a ClientTransport (Streamable HTTP via McpClient.http/McpClient.spawn, or stdio via McpClient.stdio). Drives the lifecycle (initialize + notifications/initialized) and the server features (tools, resources, prompts, completion, logging, subscriptions) with auto-pagination. Server->client requests received on an inbound stream (sampling / elicitation / roots) are dispatched to the user-supplied handlers and answered via the transport.