Repository navigation
Client Guide
This page describes the client library: mcp.Client, the transports, the hooks and the request options. The client obeys the client part of the specification1.
Applies to: MCP 2026-07-28, Zig 0.16.0, zig-sdk 0.1.0.
var client: mcp.Client = .init(gpa, io, .{
.info = .{ .name = "cli", .version = "0.1.0" },
.capabilities = .{ .elicitation = .{}, .roots = .{} },
.hooks = .{ .elicit_form = onForm, .list_roots = onRoots },
});
defer client.deinit();
client.connect(transport);The client stamps _meta on every request: the protocol version, the client info, the client capabilities and a progress token2. Its integer ids increase by one for each request.
| Transport | How to get it |
|---|---|
| In-memory link to a server in the same process | mcp.transport.memory.ClientLink.init(io, gpa, &server).transport() |
| stdio with a child process |
mcp.transport.stdio.Client.spawn(io, gpa, .{ .argv = ... }) then .transport()
|
| Unix domain socket |
mcp.transport.unix.Client.connect(io, gpa, .{ .path = ... }) then .transport(). See Transport Unix Socket. |
| Streamable HTTP and HTTPS |
mcp.transport.HttpClient.init(io, gpa, .{ .url = ... }) then .transport(). For https, tls = .{ .trust = ..., .identity = ... } sets the trust policy and a client certificate. The default is the system trust store. See TLS and Certificates. |
The stdio client demuxes responses by id, and notifications by progress token and subscription id. It sends notifications/cancelled when the timeout of a request expires or when the application cancels it. close() closes the standard input of the process and waits for it. kill() terminates it at once.
The HTTP client opens one connection and sends one POST per request3. HTTPS runs on the SDK TLS client. It mirrors the method, the name or URI, and the annotated parameters into the Mcp-* headers with the base64 sentinel where needed4. It learns x-mcp-header annotations from tools/list and removes tools with invalid annotations from the list. See Authorization for the auth and auth_provider options.
Every method takes an arena that owns the result:
const tools = try client.listTools(arena, null, .{});
const result = try client.callTool(arena, "add", .{ .a = 2, .b = 3 }, .{ .timeout = .fromSeconds(30) });
const resource = try client.readResource(arena, "file:///readme", .{});
const prompt = try client.getPrompt(arena, "review", .{ .lang = "zig" }, .{});
const raw = try client.requestAs(arena, MyResult, "server/discover", params, .{});RequestOptions has:
-
timeout: the deadline for the whole request, with every multi round-trip round. -
cancel: a cancel token that stops the request. -
on_progress,on_logandon_notification: callbacks for the notifications on the request stream. -
allow_input_required: return the rawInputRequiredResult. The client does not call the hooks. -
allow_task: return theCreateTaskResultof the Tasks extension inResponse.task. The client does not wait for the task. -
cache_mode:defaultserves a cached result when the server gave it a lifetime,bypassnever touches the cache,refreshstores the fresh result. The cache is off unlessOptions.cache.enabledis set. A list-changed notification on a listen stream drops the cached lists, andinvalidateCache()drops everything. -
retry: what to do when the client loses the stream before a response byte arrived.autore-issues idempotent methods with a new id,neverdoes not, andforcere-issues every method. The limit islimits.max_lost_stream_retries(3). -
diagnostics: receives the JSON-RPC error and other findings.
A JSON-RPC error becomes error.Rpc with the error in diagnostics.rpc_error. When the server rejects the protocol version with -32022 and lists ours as supported, the client retries once5.
When a result says input_required, the client runs one hook per input request6. Then it sends the request again with a new id, the answers and the unchanged requestState. The client rejects an input kind it did not declare. The limit is ten rounds.
The hooks are:
elicit_form(ctx, params) !ElicitResult-
elicit_url(ctx, params) !ElicitResult: show the URL to the user. The SDK never opens it. sample(ctx, params) !CreateMessageResult-
list_roots(ctx) ![]const Root: every root must be afile://URI.
A client that declares the extension io.modelcontextprotocol/tasks in capabilities.extensions can receive a task instead of a tool result. See Tasks Extension. callTool then polls the task, answers its input requests with the hooks and returns the tool result. The other methods give full control:
switch (try client.callToolOrTask(arena, "slow_compute", .{ .seconds = 5 }, .{})) {
.complete => |result| use(result),
.task => |task| {
const done = try client.awaitTask(arena, task.taskId, .{ .timeout = .fromSeconds(60) });
use(try mcp.Client.taskResult(arena, done));
},
}getTask, updateTask and cancelTask send one request each. awaitTask returns error.Rpc with the task error in the diagnostics when the task failed, and error.TaskCancelled when the server canceled the task.
The client has methods for two more extensions:
- Skills:
listSkills,getSkill,readDirectoryandreadSkillFile.readSkillFileverifies the size, the digest and the frontmatter of each file against the skill entry. See Skills Extension. - Apps: declare the extension with
mcp.protocol.apps.declare. ThentoolUigives the UI metadata of a tool, andreadUiResourcereads its view. See Apps Extension.
listen(arena, filter, options) opens the stream. The on_notification callback receives the acknowledgment and every event. The call returns when the server closes the stream, or with error.Canceled when the cancel token fires.
A lost stream ends the call with error.Closed. Pass .retry = .force to open the stream again with a new id, for example after the stdio client restarted the server process7.
A server can attach icons to Implementation, Tool, Resource, ResourceTemplate, ResourceLink and Prompt. The client treats the icon metadata and the icon bytes as data from a source that it does not trust8. mcp.icons has the rules, and mcp.Client applies them with the client options.
var client: mcp.Client = .init(gpa, io, .{
.info = .{ .name = "cli", .version = "0.1.0" },
.icons = .{ .formats = .{ .svg = true } },
.hooks = .{ .icon_decoder = sanitizeIcon, .userdata = &app },
});
const icon = client.selectIcon(tool.icons, .{ .size = 32, .theme = .dark }, server_url) orelse return;
const image = try client.fetchIcon(arena, icon, .{ .server_url = server_url });
show(image.format, image.bytes);The client obeys these rules:
- The
srcis anhttpsURL or adata:URI9. The client rejects each other scheme, for examplehttp:,javascript:,file:,ftp:andws:, witherror.UnsupportedScheme. AnhttpsURL with user information giveserror.InvalidUri. - An
httpsicon must have the origin of the MCP endpoint10.server_urlgives that origin. The origin includes the scheme, so anhttpendpoint never matches anhttpsicon. Without a server URL, for example on stdio, onlydata:icons and the trusted origins pass. -
Policy.trusted_originsadds other origins.Policy.origins = .anydisables the origin rule.Policy.origins = .noneaccepts onlydata:icons.Policy.allow_data = falserejectsdata:icons. - The fetch sends one
GETrequest for each redirect step, with only the headershost,connection,acceptandaccept-encoding. It sends no cookie and noAuthorizationheader. It sends a client certificate only when thetlsoption offetchIconhas an identity. - A redirect must stay on the origin of the icon URL and on
https. The client follows at mostlimits.http.max_redirect_hops(3) redirects. After that, it giveserror.TooManyRedirects. - The magic bytes set the format. The media type of the response or of the
data:URI applies first. ThemimeTypeof the icon applies when that type is absent or generic. A mismatch giveserror.MimeMismatch, and an unknown format giveserror.UnknownFormat. - PNG and JPEG are on by default and need no decoder. GIF, WebP and SVG are off by default. They pass only when the policy turns them on and the
icon_decoderhook is set. An SVG image can contain script. - The client reads the width and the height from the PNG, JPEG, GIF and WebP header. A value above
limits.icon.max_dimensiongiveserror.DimensionsTooLarge.
The icon_decoder hook receives each checked image, also PNG and JPEG. It can decode, clean or convert the image. It returns an error to reject the image, and the client then gives error.DecoderFailed. The client gives the result of the hook to the caller.
selectIcon ranks the candidates by theme first, then by size, then by their order. For the theme, an equal theme wins over an icon without a theme, and that icon wins over another theme. For the size, an exact size wins. Then an icon with sizes = ["any"] or without sizes, then the smallest larger size, then the largest smaller size. The function uses only the declared mimeType for the format rule. Without an icon_decoder, it removes GIF, WebP and SVG icons.
fetchIcon returns an icons.Image with the format, the bytes and the dimensions. The dimensions are null for SVG. The errors are in icons.Error, for example OriginNotAllowed, RedirectRefused, MimeMismatch, TooLarge and Timeout. The functions mcp.icons.checkSource, decodeData, inspect, select and fetch give the same rules without a client. See Limits and Security for the limits.
- MCP-CLIENT-2026-07-28: Client features overview.
- MCP-SEP-2575: Per-request metadata.
- MCP-HTTP-2026-07-28: Streamable HTTP transport.
- MCP-SEP-2243: HTTP headers derived from the message.
- MCP-VERSIONING-2026-07-28: Version negotiation.
- MCP-SEP-2322: Multi round-trip requests.
- MCP-SUBSCRIPTIONS-2026-07-28: Subscriptions, and a new listen request after a reconnection.
- MCP-BASIC-2026-07-28: Icons, the MIME types and their security considerations.
-
RFC2397: The
dataURL scheme. - RFC6454: The origin of a URI.
zig-sdk uses the Apache License 2.0. The wiki uses the project profile of ASD-STE100 Simplified Technical English. The Bibliography lists the sources.
zig-sdk
- Home
- Getting Started
- Concepts
- Server Guide
- Client Guide
- Transports
- Transport stdio
- Transport Streamable HTTP
- Transport gRPC
- Transport Unix Socket
- Transport WebSocket
- TLS and Certificates
- Deploy Reverse Proxy
- Authorization
- Authorization Server
- JSON Schema Support
- Resource Templates
- Tasks Extension
- Skills Extension
- Apps Extension
- Conformance
- Examples
- API Reference
- Interoperability
- Threat Model
- Limits and Security
- Deprecated Features
- Roadmap
- Release Notes
- Versioning
- Contributing
- FAQ
Style and words