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.0.1 (unreleased).
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. It uses increasing integer ids.
| 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()
|
| Streamable HTTP and HTTPS |
mcp.transport.HttpClient.init(io, gpa, .{ .url = ... }) then .transport()
|
The stdio client demuxes responses by id, and notifications by progress token and subscription id. It sends notifications/cancelled when a request times out or is cancelled. close() closes the standard input of the process and waits for it. kill() terminates it at once.
The HTTP client sends one POST per request3. 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 option.
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, including 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 rawInputRequiredResultinstead of running the hooks. -
allow_task: return theCreateTaskResultof the Tasks extension inResponse.taskinstead of waiting for the task. -
retry: what to do when the stream is lost 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 task was cancelled.
listen(arena, filter, options) opens the stream. The on_notification callback receives the acknowledgement 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.
- 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, re-sending a listen request after a reconnection.
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