Repository navigation
Releases: ChristianPresley/zig-sdk
Release list
v0.5.2
v0.5.1
Changed
- A cancel of
serveof the HTTP, gRPC and Unix socket servers now sets the stop flag ofshutdown. The WebSocket server and the authorization server already did this. After the cancel, the accept loop closes the wake connection of the stop and ends. A new call ofserveon the same server then accepts no connections, and it returns only atshutdownor at a cancel. Before, the accept loop could serve the wake connection as a client connection, and a newserveaccepted connections again. - After a cancel of
serve, the connections of the HTTP server obey the stop flag, as aftershutdown. A connection then answers no request after its current request.shutdownwakes the connections, but the cancel does not. Thus a connection that waits can see the stop flag 60 seconds or less after the cancel. Before, the connections served requests until the client closed them or untilidle_timeout.
Fixed
-
The HTTP, gRPC and Unix socket servers stop their accept loop with
util.wake.cancelAcceptLooporutil.wake.cancelUnixAcceptLoop. Thus the cancel usually comes before the wake connection, while the accept waits. Before, the wake connection came first, and on Windows the cancel then frequently came during the accept of that connection. In Zig 0.16.0, std then giveserror.Canceledfor the complete accept, and a Debug build writes a stack trace oferror.Unexpected(INVALID_PARAMETER). The change removes these traces from the test runs of the SDK. Under load, the wake connection can still come first, and a trace is still possible. -
On a POSIX system,
util.wake.wakeIpconnects with a socket that does not block, and it waits for the connect inpollfor 100 ms or less. A signal during the wait does not start a second connect. Before,wakeIpused the connect of std, which blocks and connects again after a signal. In Zig 0.16.0, std can send its cancel signal late, to a task that already saw its cancel. An example is a task that stops a server after a cancel. On macOS, the second connect then gotEISCONN, and a Debug build stopped with a panic.cancelAcceptLoopuseswakeIp, and thus the stop of the WebSocket server and of the authorization server. The stop of the HTTP and gRPC servers also useswakeIp.
v0.5.0
Added
secret_service.Options.timeoutandKeychainTokenStorage.Options.timeout: the time limit for each answer of the D-Bus bus and of the Secret Service. The default isdbus.default_timeout, 25 seconds, the same as in the reference implementation of D-Bus. A service that does not answer in time giveserror.KeychainUnavailable. Before, the Secret Service backend waited without a limit, for example for a service that stopped.secret_service.Options.prompt_timeoutandKeychainTokenStorage.Options.prompt_timeout: the time limit for the answer of the user to a prompt of the Secret Service. The default issecret_service.default_prompt_timeout, 5 minutes. A prompt without an answer in time giveserror.KeychainLocked. Null waits without a limit, as before.
Changed
- Breaking:
dbus.Connection.openhas a fifth parameter, the time limit for each answer of the bus. Null waits without a limit, as before. The connect and the authentication ofopentogether obey this limit, and eachConnection.send,Connection.callandConnection.receiveobeys it too.Connection.waitSignalhas a parameter for the time limit of its wait. At the limit, these functions return the new errordbus.Error.Timeout, and the connection is then not usable. - A wait of the D-Bus client with a time limit runs in its own task, and at the limit that task gets a cancel. A cancel stops a blocked read on each system. On Windows, a shutdown of the socket does not stop a blocked read. When the
Iocannot run a task concurrently, the client can wait without a limit. The first such wait of the process writes a message to the log of the scopemcp_dbus.
Fixed
- Correction to the notes of 0.4.0: the proxy sees only the host and the port of the server for an
httpsURL. For anhttpURL, the tunnel has no TLS, thus the proxy can read the requests. The doc comments oftransport.proxyandHttpClient.Options.proxynow say this. - On Windows, the fake D-Bus server of the keychain tests serves each connection in its own task. A read on a Unix socket of Windows can stay blocked after the client closes the connection. Before, the server then never accepted the next connection, and a test hung.
- Correction to the notes of 0.4.0: the warning of the scope
mcp_routerfor an error response with a null id also has the error code. That code is the code of the error response. - The D-Bus client connects to an abstract address (
unix:abstract=) with a socket that does not block. While the queue of the bus is full, the client tries again after a short pause, until the time limit or a cancel. Before, this connect waited without a limit, and a cancel did not stop it. - On Windows, the Unix socket transport sees each close of the peer. The Windows driver of Unix sockets can lose the close of the peer when a receive starts at about the same time. Before, the server then kept the connection open until shutdown, and the connection kept its place in
limits.unix_socket.max_connections. The client did not see the end of the connection, and a request waited until its timeout. Now each read of the server and of the client first waits in a poll of the socket, until data comes or the connection ends. Each poll stops after one second and starts again.
v0.4.0
Added
RequestOptions.log_level: the log level of one request. It replacesOptions.log_levelof the client. The client also sends it whenOptions.log_levelis null.RequestOptions.meta: more_metaentries for one request, for exampletraceparent,tracestate,baggageor a key with a vendor prefix. Before the client sends the request, it checks each key. A key thatmeta.validateKeyrefuses, or a key that the SDK owns, gives the new errorerror.InvalidMetaofClient.RequestError. The client sends nothing then. The client accepts the keys of extensions underio.modelcontextprotocol/. The entries of the SDK always have priority.meta.sdk_owned_request_keysandmeta.isSdkOwnedRequestKey: the request keys that the SDK owns. They are the protocol version, the client information, the client capabilities, the log level, the subscription id and the progress token.RequestOptions.inline_notifications: the reader task of the transport callson_progress,on_logandon_notificationof the request at once, before it routes the next frame. Thus a notification gets to its callback before the response of another request that the server wrote after it. An example is anotifications/tools/list_changedevent of a listen stream and thetools/callresult that caused it. The stdio, Unix socket and WebSocket transports obey the option, and the memory link, HTTP and gRPC ignore it. A callback must only copy, serialize and write the data, and must not send a request through the same client.Transport.Exchange.inline_notificationsgives the option to the transports.stdio.Client.SpawnOptions.on_notificationandstdio.Client.SpawnOptions.userdata: the callback of the stdio client for each notification without a progress token and without a subscription id. An example is a log message of the server.spawnsets the callback before the reader task starts.sse.Parser.max_event_bytes(4 MiB): the maximum bytes of the event that the SSE parser reads, across all calls offeed. The bytes are the line that the parser reads with its field name, the data and the event type of the event, and the last event ID. The events and comments before it do not count.sse.Parser.max_id_bytes(256): the maximum bytes of an event ID. The SSE parser ignores anidfield with a longer value.line_framer.TopLevelScanner: finds the top-level members "id", "method", "result" and "error" of a JSON object in parts, with fixed memory.line_framer.Framer.droppedhas the members of the last line that the framer dropped for its length or for its UTF-8. The framer reads the whole line for it, also the part after the limit.stdio.Client.exitStatus: the exit status of the last server process that ended, asstd.process.Child.Term. The client reaps a process after it ends its output, before a restart, and incloseandkill. When a process ends its output and no restart is left, the client logs a warning in the scopemcp_stdiowith the status.Client.Diagnostics.http_status: the HTTP status of the last response to a request, also when the request failed. The Streamable HTTP and gRPC transports give it, and the WebSocket transport gives the status of an upgrade that the server refused. Thus a caller can tell a wrong path, for example status 404, from a response that is not valid. Both giveerror.InvalidResponse.OAuthClient.Options.ca_bundle: the CA certificates that the OAuth client trusts for its https requests. These are the metadata, registration and token requests, and the authorization request ofheadless_redirect. Null uses the CA store of the system, as before. Thus a private CA or the CA of a test server works without a change to the CA store of the system. The HTTP client transport can use the same bundle intls.trust.auth.common.Fetcher.ca_bundle: the CA bundle of the fetcher. With a bundle, the fetcher sends each request on a new connection ofhttp1.Connection. There, the TLS client of the SDK checks the certificates against the bundle only, at the time of the request. Thus the servers must offer TLS 1.3. The fetcher does not give the bundle tostd.http.Client. On Windows, the TLS client of std also accepts a certificate chain that the certificate store of the system trusts.HttpClient.Options.proxyandOAuthClient.Options.proxy: the HTTP proxy of the client connections, with the new moduletransport.proxy. The client asks the proxy for aCONNECTtunnel to the server, also for anhttpURL. The tunnel carries the TLS handshake and the requests, thus the proxy sees only the host and the port..explicitsets a proxy URL, and.nonedisables the proxy.- By default, the proxy comes from the environment that the application gives, for example
.{ .environment = init.environ_map }. The default environment is null, thus the clients connect directly, as before. AnhttpsURL takes the first ofhttps_proxy,HTTPS_PROXY,all_proxyandALL_PROXYwith a value. AnhttpURL takes the first ofhttp_proxy,HTTP_PROXY,all_proxyandALL_PROXY. WhenREQUEST_METHODhas a value, the client ignoreshttp_proxyandHTTP_PROXY, because theProxyheader of a request to a web server can set them. transport.proxy.bypasses: the match ofno_proxyandNO_PROXY.*matches each host. A name matches the same host and each host under it, with or without a dot or*.at the start. An IP address matches the same address, and an address with a prefix length, such as10.0.0.0/8, matches the range. An entry with a port matches only that port. The client does not resolve names. A proxy from the environment never gets a loopback host:localhost, a name under.localhost, an address in 127.0.0.0/8 and ::1.- The user and the password of a proxy URL go to the proxy only in the
CONNECTrequest, in aproxy-authorizationheader with the Basic scheme. The log lines show only the host and the port of the proxy. The SDK has no TLS to the proxy and nosocks5proxy. A proxy URL that does not usehttpgives the new errorerror.InvalidProxyofHttpClient.InitError. - When the proxy refuses the tunnel, the new log scope
mcp_proxyhas a warning with the proxy, the target and the status.http1.Connection.openThroughandauth.common.Fetcher.fetchthen returnerror.ProxyRefused. The HTTP client transport giveserror.WriteFailed, and the client returnserror.TransportFailed. auth.common.Fetcher.proxy: the proxy of the OAuth fetcher. Through a proxy, the TLS client speaks to the server through the tunnel, with the same trust as a direct request. Withca_bundle, that is the TLS client of the SDK. Without it, the TLS client of std uses the CA store of the system. The fetcher does not use the proxy fields ofstd.http.Client. In Zig 0.16.0, that client sends anhttpsrequest through aCONNECTtunnel without TLS.http1.Connection.openThroughandhttp1.TunnelError: a connection through aCONNECTtunnel.http1.writeRequestandhttp1.RequestError: the request ofConnection.sendfor another writer.Connection.SendErroris now the same error set asRequestError.http2.Connection.Stream.stopWaits: stops the waits on a stream without a reset. Each wait on the stream then giveserror.Canceledwhen it must block. Use it, notFuture.cancel, to stop a task that waits on the stream. In Zig 0.16.0, the condition of std can lose a cancel that comes together with a wake-up. The task then waits until the stream gets a reset or the connection ends.client.CallStream: the stream of a gRPC call that runs in its own task. The tunnel and typed channels reset the stream withCallStream.stopto stop the call.util.loopback.connectandutil.loopback.listen: a connect to a loopback address and a listen on it, with at most five tries aftererror.Unexpectedorerror.AddressUnavailable. On Windows, Zig 0.16.0 gives two errors that can go away aserror.Unexpected: a connect to a server with many connections in TIME_WAIT (ADDRESS_ALREADY_EXISTS), and a socket when no local port is free (TOO_MANY_ADDRESSES). On Windows, a refused connect is alsoerror.Unexpected, thus useconnectonly for a server that listens. The tests of the SDK use these functions for their own sockets.
Changed
- Breaking: the client parses each notification of a request into scratch memory and resets that memory after the frame. Thus the
methodandparamsarguments ofRequestOptions.on_notificationandHooks.on_notificationare valid only during the call. Before, they stayed valid until the caller freed the request arena. A callback that keeps a part of them must copy it. The parameters ofon_progressandon_logwere already valid only during the call. - The request arena does not hold the notifications of the request. Thus a long
subscriptions/listenstream does not make the request arena larger. Before, each event added approximately 1900 bytes to the request arena on all transports. - The client applies the progress rate limit before it parses a notification, when the method has no escape sequence. Thus it does not parse a progress notification over
limits.max_progress_rate_per_s. - Breaking: the fields
on_notificationanduserdataofstdio.Clientare gone. Use the spawn options with the same names. The reader task starts inspawnand read the fields one time for each child process. Thus a callback that the caller set afterspawnraced with the reader task. When the reader task was first, it dropped these notifications until a restart, also the log messages of the server. - Breaking: over the memory link, the callback of the acknowledgment of a listen stream runs while the server holds the lock of the stream. Thus the callback must not publish an event of the same server, for example with
Server.setToolEnabled. The callbacks of the events already had this rule. Before, the server sent the acknowledgm...
v0.3.0
Added
limits.listen_ack_timeout(10 s): the client waits this time for the acknowledgment of asubscriptions/listenstream. Then it cancels the stream, and the request returnserror.Timeout. Zero disables the limit. All client transports obey it throughTransport.Exchange.first_frame_timeout.json.checkDepth,json.parseTreeMaxDepthandjsonrpc.Message.parseMaxDepthcheck the nesting depth of a JSON text before the parse, without recursion.
Changed
json.parseTreeandjsonrpc.Message.parsereject a text that nests deeper than 256 levels (json.max_tree_depth) or 200 levels (json.max_message_depth).std.json.Stringifycannot write more than 256 levels in a safe build mode.- The HTTP server supervises each connection in a second task. At the shutdown, it ends a connection that waits for a request at once, and a busy connection after
limits.shutdown_grace. Before, the shutdown waited until each peer closed its connection.
Removed
- Breaking:
Limits.cancel_notify_timeoutandLimits.max_step_up_attempts. No code used them. Each authorization client has its ownmax_step_up_attemptsoption. - Breaking:
Limits.http.listen_ack_timeout. It is nowLimits.listen_ack_timeout, because all transports obey it.
Fixed
- The Streamable HTTP server obeys
limits.http.head_timeoutandlimits.http.idle_timeout. A request head that does not arrive inhead_timeout(10 s, with the TLS handshake for the first request) gets HTTP 408. A body that does not arrive inidle_timeout(60 s) after its head also gets HTTP 408. A keep-alive connection without a new request foridle_timeoutcloses without a response. Before, a slow client could keep one of thehttp.max_connectionsconnections without a time limit. - The servers of all transports obey
limits.json_max_depth(64). A message that nests deeper gets-32700with a null id. The client fails a request whose response nests deeper witherror.InvalidResponse. Before, the server parsed a message of any depth, andstd.json.Stringifypanics in a safe build mode when the server serializes a part of more than 256 levels, for example the parameters of a task. - The server refuses a
subscriptions/listenfilter larger thanlimits.max_filter_bytes(64 KiB) as compact JSON with-32603. - The HTTP client transport gives each SSE event to the request when its bytes arrive. Before, it held back an event until the next bytes came, thus a listen stream got its acknowledgment and its events late, with the next keep-alive comment.
- The HTTP/2 connection of the gRPC transport answers a frame larger than the maximum frame size with
GOAWAYandFRAME_SIZE_ERROR(RFC 9113 section 4.2). Before, it sentPROTOCOL_ERROR. APING,PRIORITY,RST_STREAM,SETTINGSorWINDOW_UPDATEframe with a wrong length also givesFRAME_SIZE_ERROR. - Correction to the notes of 0.2.0: only the server checks
includeContextagainst the client capabilitysampling.context. The client does not. The client validates elicitation URLs, form answers and sampling messages with tool results, as the notes say.
v0.2.0
Added
- Regular expression engine for the JSON Schema keywords
patternandpatternProperties(mcp.schema.regex). It is a Pike VM over code points with the ECMA-262 syntax, and the match time is linear in the input length. Backreferences, lookaround and most Unicode property escapes giveerror.UnsupportedRegex. The limits arelimits.schema.max_pattern_bytesandlimits.schema.max_regex_states. A derived schema accepts.patternon string fields. - RSA certificate keys in the TLS server and the TLS client: PKCS#1 and PKCS#8 keys of 2048 to 4096 bits sign with RSA-PSS (
rsa_pss_rsae_sha256,sha384,sha512). The private operation uses the Chinese remainder theorem, and the key verifies each signature before it goes out. - The post-quantum hybrid key exchange group
X25519MLKEM768(RFC 10024) in the TLS server and the TLS client. It is the first group of the default order. The client also sends an X25519 share, so a server without the hybrid group needs no HelloRetryRequest. - Unix socket transport (
mcp.transport.unix) with the stdio framing: a server that accepts many peers on a socket file and a client transport. The limit islimits.unix_socket.max_connections. The exampleunix_serverand theclient_cli unixmode use it. - On Windows the Unix socket server gives the socket file a protected access control list that allows access only to the user of the process. This is the Windows form of the mode
0600, and Windows checks the list when a client connects. Amodewith group or other bits keeps the inherited list. A failure givesBindError.AccessControlFailed. - The Unix socket server refuses a directory where other accounts can add, delete or rename entries, because such an account can put its own socket at the path (
BindError.DirectoryNotPrivate). On POSIX systems it also checks the parent directories. Whenmodegives the group access, the group can also write to the directory.mcp.transport.unix.createPrivateDirectorymakes a safe directory: mode0700, or on Windows an access control list that its files inherit. The exampleunix_serveruses it. - The Unix socket server creates the socket in a new private directory, sets the mode or the access control list, and then moves the socket to its path. Thus no client can connect before the socket has its protection.
- Client icon rules (
mcp.icons,Client.fetchIcon,Client.selectIcon): onlyhttpsanddata:icons, the same origin as the server unless the policy trusts other origins, no credentials, magic byte checks, PNG and JPEG by default, other formats through theicon_decoderhook, and the limitslimits.icon. - OAuth client credentials extension (
mcp.auth.ClientCredentials) with client secrets andprivate_key_jwtassertions, token caching and renewal before expiry. - Enterprise-managed authorization extension: the client (
mcp.auth.EnterpriseClient) exchanges an identity assertion for an ID-JAG at the enterprise identity provider and the ID-JAG for an access token.mcp.auth.IdJagValidatorchecks ID-JAGs for an application with its own authorization server. mcp.auth.Providerfor the HTTP client transport (auth_provider), andServer.Options.authorization_extensionsto advertise the authorization extensions.- JWT signing keys (
mcp.auth.jwt.SigningKey) for ES256, ES384, EdDSA, RS256 and PS256, JWK set parsing, and ES384 and EdDSA verification. - Skills extension (
io.modelcontextprotocol/skills): the server optionskills,addSkill,addDynamicSkill,skills/list,skills/getandresources/directory/read, and the client methodslistSkills,getSkill,readDirectoryandreadSkillFilewith manifest checks. - MCP Apps extension (
io.modelcontextprotocol/ui): the server optionapps, UI resources withaddUiResource, tool UI metadata withToolDef.ui, and the client accessorstoolUiandreadUiResource. - Requirement matrix:
zig build extract-requirementsfinds the 733 normative sentences of the vendored specification pages,docs/spec/requirement_tests.zonmaps each one to tests or to a reason, andzig build spec-matrixchecks the mapping and rendersdocs/generated/conformance-matrix.md. The CI checks both files. 127 new tests cover requirements that had no test. - Documentation site:
zig build sitebuilds a landing page and the API reference of the modulesmcpandmcp_grpc. The workflowdocs.ymldeploys it to GitHub Pages frommain. zig build lint-docshas the options--strict,--format,--rule,--string-literalsand--wiki-dir, and the warning rules of the project profile. The project dictionary has all its lists.- Full JSON Schema 2020-12 support:
unevaluatedProperties,unevaluatedItems,$idin subschemas with base URI resolution,$dynamicRefand$dynamicAnchor. The content keywords are annotations. The draft 2020-12 files of the JSON Schema Test Suite run with the tests. x-mcp-headerannotations on nested properties. The client and the server refuse a tool with an annotation that a chain ofpropertieskeywords does not reach, and the client logs the refusal.- The client refreshes
tools/listand retries a tool call one time after error-32020. - The client validates
structuredContentagainst the output schema and reportsDiagnostics.structured_content_invalid. - The client and the server validate elicitation URLs, form answers against
requestedSchema, sampling messages with tool results, andincludeContextagainst the client capability. - The HTTP server cancels a request when the client disconnects. Progress notifications have a rate limit of
limits.max_progress_rate_per_son both sides. RequestOptions.max_total_timeout. A request without a timeout getslimits.request_timeout(60 s).OAuthClient.Options.application_type,OAuthClient.lastFailure,ResourceServer.scope_hierarchyandPrincipal.issuer.- DPoP (RFC 9449) for the DPoP extension of MCP (
mcp.auth.dpop).DpopProvermakes a proof for each request and keeps the nonces of each server. The optiondpopofOAuthClient,ClientCredentials,EnterpriseClientandWorkloadIdentityrequests DPoP-bound tokens. The HTTP client transport sendsAuthorization: DPoPwith a new proof and answersuse_dpop_nonce.OAuthClientsendsdpop_jktand can register withdpop_bound_access_tokens. ResourceServer.dpop(DpopPolicy) accepts DPoP-bound tokens: the checks of RFC 9449 section 4.3 with a window of 5 minutes, thecnf.jktbinding, optional nonces without state (dpop.NonceIssuer) and an optional replay check. The server refuses a DPoP-bound token with the Bearer scheme.ResourceServer.authorizeRequesttakes theDPoPheaders.Principal.confirmationhas thejkt.- Workload identity federation (
mcp.auth.WorkloadIdentity): a workload JWT from a value, a file or a callback goes to the token endpoint as a JWT bearer grant without client registration. The client does not send a refused JWT again.WorkloadJwtValidatorandKeyDiscovery(OpenID Connect Discovery) check workload JWTs for an application with its own authorization server. Server.Options.authorization_extensionshasdpopandworkload_identity. The conformance client passesauth/dpop,auth/dpop-nonceandauth/wif-jwt-bearer.- The CI job
conformanceruns each scenario that the suite does not score alone, with the per-check baselineconformance/expected-failures-unscored.yml. A failure of these scenarios now makes the job fail. - Rate limits for each caller (
limits.rate_limits): a token bucket for the tool calls of each caller, one for all tool calls together, one for the log messages of each caller, andToolDef.rate_limitfor one tool. The caller is the principal, else the IP address (an IPv6 /64 network), else the connection. A refused tool call gets error-31429withdata.retryAfterMs, and on HTTP with a JSON response also status 429 withRetry-After. The server drops log messages over the limit and later sends one summary with the count. The limits are off by default. The table of callers has at mostmax_callersentries.Server.rateLimitStatsgives the counters. - The TLS cipher suites
TLS_AEGIS_128L_SHA256andTLS_AEGIS_256_SHA512as an option of the TLS client and server (tls.suites.default_suites_with_aegis). They are off by default.HttpClient.TlsSetup.cipher_suitesselects them for the HTTP client. - Record padding in the TLS client and server (RFC 8446 section 5.4): the option
paddingwith the policiesnone,blockandrandom, andConnection.setPadding. Padding stops at an inner plaintext of 2^14 bytes.HttpClient.TlsSetup.paddingsets it for the HTTP client. - The TLS server option
retry_cookiesends a HelloRetryRequest with a random cookie and requires the cookie in the second ClientHello (RFC 8446 section 4.2.2). - A fuzz target for the TLS record layer: records with each padding policy, input in pieces of any size, line, short and buffered reads, and one changed byte.
OAuthClientrefreshes the access token with the refresh token before it expires (refresh_margin_seconds) and after a401challenge. It keeps a rotated refresh token, never asks for a wider scope, sends a DPoP proof for a DPoP-bound grant, and runs the authorization code flow again when the server refuses the refresh token.- Token storage for
OAuthClient(storage,storage_identity): the registration and the tokens of each issuer, resource and client go to amcp.auth.TokenStorage, so a new process needs no new sign-in.MemoryTokenStoragekeeps the records in memory.FileTokenStorageencrypts each record with AES-256-GCM and a key of the application, in a private directory.KeychainTokenStorageuses the keychain of the host: the Credential Manager on Windows, Keychain Services on macOS (loaded at run time) and the Secret Service on the D-Bus bus of the user on Linux, with a D-Bus client in the SDK. The example `cl...
v0.1.0
Added
- Protocol types for MCP specification revision 2026-07-28.
- JSON-RPC envelope parsing and writers.
- Server engine with tools, resources, resource templates, prompts, completion, pagination, multi round-trip requests and subscriptions.
- stdio transport for servers.
- Streamable HTTP transport for servers with SSE responses and header mirroring.
- Client (
mcp.Client) with typed requests, progress and log callbacks, the multi round-trip driver over application hooks, and three client transports: in-memory, stdio with process management, and Streamable HTTP with header mirroring. - OAuth 2.1 authorization client (
mcp.auth.OAuthClient): protected resource and authorization server metadata discovery, pre-registered, client ID metadata document and dynamic registration, PKCE S256,resourceindicators,issvalidation, scope selection with step-up limits, and per-issuer credentials. The HTTP client transport answers 401 and 403 challenges with it. - Resource server helpers (
mcp.auth.ResourceServer,mcp.auth.JwtVerifier): protected resource metadata, bearer token checks withWWW-Authenticatechallenges, JWT verification with HS256, ES256, RS256 and PS256, and the principal in the request context. The HTTP server serves them with theauthoption. - In-tree TLS 1.3 server (
mcp.tls) with ECDSA P-256, P-384 and Ed25519 certificates, X25519, P-256 and P-384 key exchange, HelloRetryRequest, ALPN and server name indication. The HTTP transport serves HTTPS with thetlsoption. - Tasks extension for servers (
mcp.tasks): thetasksserver option,task_supportper tool, the.start_taskoutcome,tasks/get,tasks/updateandtasks/cancel, input requests inside a task, cancellation and the-32021gate. - Tasks extension for clients:
callToolwaits for a task and answers its input requests with the hooks;callToolOrTask,getTask,updateTask,cancelTaskandawaitTaskgive full control. The HTTP client mirrors the task id intoMcp-Name. - stdio client process management: process groups on POSIX and a job object on Windows, shutdown escalation (
SIGTERM, thenSIGKILL) afterlimits.shutdown_grace, and restarts of a crashed server process withmax_restarts. - Client result cache (
Client.Options.cache,RequestOptions.cache_mode): results with a positivettlMsare served again until they expire, a list-changed notification arrives, orinvalidateCacheruns. - Client retry of requests whose stream was lost before a response byte:
RequestOptions.retry(auto,never,force) bounded bylimits.max_lost_stream_retries.forcereopens a listen stream after a restart. - In-tree TLS 1.3 client (
mcp.tls.connect): chain validation with a CA set, the std bundle, a pinned leaf or a self-signed policy; host name and IP address checks; CA constraints; ALPN; HelloRetryRequest; client certificates. The server can ask for client certificates (client_auth,client_trust). - The Streamable HTTP client runs on an SDK-owned HTTP/1.1 connection (
mcp.transport.http1) and uses the SDK TLS client forhttps(HttpClient.Options.tls). - gRPC transport in the module
mcp_grpc: a JSON-RPC tunnel over HTTP/2 (proto/mcp_zig_transport_v1.proto) with its own protobuf wire format, HPACK with the RFC 7541 tables, an HTTP/2 connection with flow control, and the gRPC server (mcp_grpc.Server) and client (mcp_grpc.Channel) transports. Metadata mirrors the HTTP headers; errors before the first message travel in the trailers withmcp-error-codeandmcp-error-bin. - Conformance fixture server and a CI job that runs the official conformance suite. The fixture server serves gRPC with
--grpc-port; the CI jobgrpc-interopcalls it from a Nodehttp2peer and fromcurlover HTTP/2. - Comptime JSON Schema derivation from Zig types.
- JSON Schema 2020-12 subset validator. Tool arguments are checked against the input schema and structured output against the output schema.
- Build steps
test,fmt,examples,docs,lint-docs,census,commit-policy,conformance-server,conformance-client,gen-bibliography,gen-dictionary,bench,check-version,changelog-section. - Benchmarks (
zig build bench) for request dispatch, HPACK decoding and the TLS handshake, with the baseline indocs/generated/bench.mdand a nightly smoke run. - Fuzz targets for every parser (
zig build test --fuzz), run nightly. A certificate precheck now rejects malformed peer certificates before the std parser reads them; the first fuzz run found that the std parser reads out of bounds on truncated input. - CI workflow with a GitHub-native Zig installation step, a consumer build through
b.dependency, a nightly workflow (wiki lint, link check, ReleaseSafe matrix) and a release workflow that verifies the signed tag and publishes the changelog section.