Repository navigation
Limits and Security
Most size, count and time limits of the SDK live in mcp.Limits, with a default and one documented overflow behavior. The gRPC transport, the TLS code and the authorization server have their own limits. A server gets its limits from Server.Options.limits. A client gets them from Client.Options.limits. This page lists them with the security defaults of the transports. See Threat Model for the reasons.
Applies to: zig-sdk 0.1.0. The rate limits, the Unix socket, WebSocket, the skills, the icons, the regular expressions, the typed gRPC messages and the authorization server: zig-sdk 0.2.0. The checks of the URI template limits: the same release. The HTTP time limits, the JSON depth limit, the filter size limit and listen_ack_timeout: zig-sdk 0.3.0. The limits of the HTTP client for each SSE event, the proxy and the remembered resets of HTTP/2: zig-sdk 0.4.0.
| Limit | Default | On overflow |
|---|---|---|
json_max_depth |
64 |
-32700 with a null id. The message object is level 1. The server checks the depth before the parse, without recursion. A value above 200 counts as 200. On the client, the request fails with error.InvalidResponse. |
max_in_flight_requests |
256 | The reader waits. On gRPC: REFUSED_STREAM. |
max_listen_subscriptions |
1024 | -32603 |
max_resource_subscription_uris |
1024 | -32603 |
max_filter_bytes |
64 KiB |
-32603. The server counts the notifications filter of subscriptions/listen as compact JSON. |
max_progress_rate_per_s |
50 per second for each request | The server does not send the notification. The client does not give it to on_progress. The client applies the limit before it parses the notification, when the method has no escape sequence. |
request_timeout |
60 s | The timeout of a client request without RequestOptions.timeout. The client cancels the request with error.Timeout. A listen stream has no default timeout. The memory link (memory.ClientLink) does not obey it. |
max_total_timeout |
600 s | The upper limit of a request timeout without RequestOptions.max_total_timeout. The client cancels the request with error.Timeout. Progress does not extend a timeout. |
shutdown_grace |
2 s | The stdio client escalates from stdin close to SIGTERM to SIGKILL. It waits for the exit of the process in the same steps at close, before a restart and after the server ends its output. At a shutdown, the HTTP server cancels a busy connection after this time. |
max_lost_stream_retries |
3 | The client does not re-issue the lost request again. |
listen_ack_timeout |
10 s | The client cancels a listen stream without its acknowledgment and returns error.Timeout. Zero disables the limit. Before 0.3.0, the field was http.listen_ack_timeout. The memory link does not obey it. |
request_state_ttl |
600 s | The server rejects the sealed request state with -32602. |
page_size |
100 | The server divides lists into pages. |
max_auto_pages |
64 | The client stops an automatic list refresh, for example the tools/list refresh after -32020. |
completion_max_values |
100 | The server sends the first values and hasMore. |
mrtr_max_rounds_client |
10 | error.TooManyRounds |
| Limit | Default | On overflow |
|---|---|---|
rate_limits.tool_calls |
Off |
-31429 with data.retryAfterMs. On HTTP with a JSON response, also status 429 with Retry-After. |
rate_limits.tool_calls_total |
Off | The same as tool_calls. The call does not use a token of its caller. |
rate_limits.log_messages |
Off | The server drops the message and counts it. Before the next message that the limit admits, the server sends one summary with the count. |
rate_limits.max_callers |
4096 | The server forgets the bucket that it used least recently. |
Each limit is a token bucket with count tokens in each period and at most burst tokens. The caller of a request is the principal: the issuer, the subject and the client of the token. Without a principal, the caller is the IP address of the client on HTTP, gRPC and WebSocket. All IPv6 addresses of one /64 network are one caller. On stdio and on a Unix socket, the caller is the connection.
ToolDef.rate_limit gives one tool its own bucket for each caller. Server.rateLimitStats gives the counters.
The specification defines no error code for a rate limit, and it reserves the codes from -32000 to -32099. Thus the SDK uses -31429. Behind a reverse proxy, all clients without a token have the address of the proxy and share one bucket. Use authorization in this configuration.
| Limit | Default | On overflow |
|---|---|---|
stdio.max_line_bytes |
16 MiB | The server drops the line and logs a warning. On the client, the request with the top-level "id" of the line fails with error.InvalidResponse. The client then sends notifications/cancelled for the request. |
stdio.read_buffer |
64 KiB | For a longer line, the reader uses a buffer that grows. |
| Limit | Default | On overflow |
|---|---|---|
unix_socket.max_connections |
64 | The server closes the new connection at once. |
The Unix socket transport also uses the stdio limits for each line and max_in_flight_requests for each connection. A socket path must fit into the sockaddr_un structure of the target, unix.max_path_len. A longer path gives error.NameTooLong. The path of the directory of the socket plus 14 bytes must also fit, for the private directory of bind. See Transport Unix Socket.
| Limit | Default | On overflow |
|---|---|---|
websocket.max_message_bytes |
4 MiB | Close with 1009. |
websocket.max_frame_bytes |
4 MiB | Close with 1009. |
websocket.max_connections |
256 | The server closes the new connection at once. |
websocket.max_in_flight_requests |
256 for each connection | The error -32603. The connection stays open. |
websocket.ping_interval |
30 s | A ping when no frame came for this time. |
websocket.idle_timeout |
90 s | Close with 1001. |
websocket.handshake_timeout |
10 s | The server closes the connection without a response. |
The upgrade request obeys http.max_head_bytes. The server checks Origin and Host against loopback names by default, and it checks the access token at the upgrade. It closes the connection with 1008 when the token expires. See Transport WebSocket.
| Limit | Default | On overflow |
|---|---|---|
http.max_body_bytes |
4 MiB | HTTP 413 |
http.max_head_bytes |
16 KiB | HTTP 431 |
http.max_connections |
256 | The accept loop waits. |
http.idle_timeout |
60 s | A keep-alive connection without a new request closes without a response. A body that does not arrive in this time after its head gets HTTP 408. |
http.head_timeout |
10 s | HTTP 408, and the connection closes. The time starts at the first byte of the head. For the first request of a connection, it starts at the accept and includes the TLS handshake. |
http.sse_keepalive |
15 s | A comment line keeps the stream open. |
http.max_redirect_hops |
3 | The icon fetcher gives error.TooManyRedirects. |
The HTTP server binds 127.0.0.1 by default and validates Origin and Host against loopback names. It answers GET and DELETE with 405. It requires a bearer token when the auth option is set. reuse_address is off. When a client closes the connection, the server cancels the request and closes the connection.
Zero disables http.idle_timeout or http.head_timeout. At a shutdown, the server closes a connection that waits for a request at once.
The HTTP client transport and its SSE parser have their own limits:
| Limit | Default | On overflow |
|---|---|---|
HttpClient.Options.max_response_bytes |
4 MiB | One application/json body or one SSE event. The transport logs a warning, and the request fails with error.InvalidResponse. The client does not send the request again. An SSE response has no limit of its total size. |
sse.Parser.max_event_bytes |
4 MiB |
feed returns error.EventTooLarge. The bytes are the line that the parser reads, the data, the event type and the last event ID. The HTTP client transport sets the limit to max_response_bytes. |
sse.Parser.max_id_bytes |
256 bytes | The parser ignores the id field. |
The HTTP client transport and the OAuth client connect directly by default. They use a proxy only when the application gives an environment or a proxy URL in the option proxy. Through a proxy, they ask for a CONNECT tunnel to the server. The tunnel carries the TLS handshake and the requests. Thus for an https URL, the proxy sees only the host and the port of the server.
- The user and the password of a proxy URL go to the proxy only in the
CONNECTrequest. They are in aproxy-authorizationheader with the Basic scheme. - The log lines show only the host and the port of the proxy. For a proxy URL that the client cannot use, the warning names the source of the URL, but not its value.
- A proxy from the environment never gets a loopback host. The client does not resolve names for this check or for the
no_proxymatch. - For an
httpURL, the client ignoreshttp_proxyandHTTP_PROXYwhenREQUEST_METHODhas a value. TheProxyheader of a request to a web server can set these variables. - The SDK has no TLS to the proxy and no
socks5proxy. Another proxy URL giveserror.InvalidProxy.
See Transport Streamable HTTP and Authorization.
| Limit | Default | On overflow |
|---|---|---|
| Concurrent streams | max_in_flight_requests |
REFUSED_STREAM |
| Frame size | 16384 octets | FRAME_SIZE_ERROR |
| Header list | 8 KiB | ENHANCE_YOUR_CALM |
| Header block | 32 KiB | Connection error |
| Message | 4 MiB | RESOURCE_EXHAUSTED |
| Window per stream | 1 MiB | The peer waits. |
| Window per connection | 4 MiB | The peer waits. |
| Typed message nesting |
typed_limits.max_depth (64) |
INVALID_ARGUMENT |
| Typed message elements |
typed_limits.max_elements (262144) |
INVALID_ARGUMENT |
| Peer streams with a reset that a server connection remembers |
http2.Connection.remembered_resets (64) |
A header block on an older reset stream is a connection error PROTOCOL_ERROR. |
A stream that the connection refuses for its headers does not count against the concurrent streams after the reset. After a complete response, the server resets a stream with NO_ERROR when the request did not end. The client then keeps the response. After a size error, the HPACK decoder reads the rest of the header block to keep the dynamic table in sync. See Transport gRPC.
| Limit | Default | On overflow |
|---|---|---|
schema.max_depth |
32 | The compiler rejects the schema, or the instance is invalid. |
schema.max_subschemas |
4096 | The server rejects the schema at registration. |
schema.max_ref_hops |
64 | The instance is invalid. |
schema.max_errors |
32 | The report stops at the limit. |
schema.eval_budget |
100000 | The instance is invalid. |
schema.max_pattern_bytes |
4096 | The server rejects the schema at registration. |
schema.max_regex_states |
4096 | The server rejects the schema at registration. |
schema.eval_budget also counts the sets of evaluated locations for unevaluatedProperties and unevaluatedItems, one unit for each 64 locations. schema.max_regex_states counts the instructions of one compiled regular expression. The match time is linear in the input length multiplied by this size. See JSON Schema Support.
| Limit | Default | On overflow |
|---|---|---|
skills.max_files |
512 | The server rejects the skill at registration with error.TooManySkillFiles. The client rejects the entry. |
skills.max_bytes |
16 MiB | The server rejects the skill at registration with error.SkillTooLarge. The client rejects the entry. |
The defaults are the limits of the Skills extension. A host must accept skills up to these limits, so the client uses the larger of its value and the limit of the extension. The server applies the value that the application sets. See Skills Extension.
| Limit | Default | On overflow |
|---|---|---|
icon.max_bytes |
1 MiB |
error.TooLarge. The fetcher stops the read at the limit. |
icon.max_dimension |
4096 pixels | error.DimensionsTooLarge |
icon.timeout |
10 s |
error.Timeout. The time includes the redirects. A data: URI has no timeout. |
The icon fetcher also uses http.max_redirect_hops. See the icons section of the Client Guide.
| Limit | Default | On overflow |
|---|---|---|
uri_template.max_template_bytes |
64 KiB | The registration fails with error.InvalidUriTemplate. Version 0.1.0 did not check this limit. |
uri_template.max_expressions |
256 | The registration fails with error.InvalidUriTemplate. |
uri_template.max_uri_bytes |
64 KiB | A longer URI matches no template. resources/read gives the error for an unknown resource. Version 0.1.0 did not check this limit. |
| Limit | Default | On overflow |
|---|---|---|
limits.max_body_bytes |
64 KiB | HTTP 413 with invalid_request
|
limits.max_parameters |
64 | HTTP 400 with invalid_request
|
limits.max_parameter_bytes |
8 KiB | HTTP 400 with invalid_request
|
limits.max_scopes |
64 | invalid_scope |
limits.max_state_bytes |
1024 bytes | The server redirects with invalid_request. |
memory_limits.max_clients |
10000 | The store removes the expired records. When no record expired, it refuses the new record with error.StoreFull. |
memory_limits.max_codes |
10000 | The same as max_clients. |
memory_limits.max_refresh_tokens |
100000 | The same as max_clients. |
memory_limits.max_jti |
100000 | The same as max_clients. |
AuthorizationServer.Options has the fields limits and memory_limits. The memory_limits apply to the MemoryStore. See Authorization Server.
| Limit | Value | On overflow |
|---|---|---|
| Handshake message | 16 KiB on the server. On the client, 84 KiB: 64 KiB for the certificate chain, 16 KiB for an OCSP response and 4 KiB. |
decode_error alert |
| Certificate chain | 8 certificates, 64 KiB |
bad_certificate alert |
| Plaintext compatibility records | 4 |
unexpected_message alert |
| Records per key | 2^24 | The keys rotate with KeyUpdate. |
| HelloRetryRequest cookie | 8 KiB on the client |
illegal_parameter alert |
| Client options | ALPN names of 1 to 255 bytes, at most 1024 bytes of ALPN names, 16 cipher suites and 16 groups |
error.TlsInvalidOptions before the client sends a message |
- The server seals the request state of multi round-trip requests with AES-256-GCM and binds it to the method, the name and the arguments. With authorization, it also binds the state to the issuer, the subject and the client id of the token.
- The server validates tool arguments against the input schema before the handler runs.
- The rate limits of tool calls and log messages are off. We recommend that a server on a network sets
rate_limits.tool_calls. - The server rejects a control character other than tab and a byte that is not ASCII in the raw values of
Mcp-Method,Mcp-NameandMcp-Param-*. - The client never opens a URL from a URL elicitation itself. It gives the URL to the application. It refuses a URL that is not a valid absolute URL.
- The client validates a form answer against
requestedSchema, and the messages of a sampling request against the tool result rules. - The client cache serves a private result only for the credential that got it.
- The authorization clients refuse
httpmetadata, registration, authorization and token endpoints unlessallow_httpis set. Aredirect_urimust behttpsor a loopbackhttpaddress. - The OAuth client sends pre-registered credentials only to the authorization server of their issuer.
- The client verifies server certificates against a trust policy. The policy
no_verificationexists for tests only. - The HTTP client, the WebSocket client and the authorization clients refuse a request head with CR, LF or another control character. They also refuse such a URL, and an access token that is not a
token68value. A value from a peer thus cannot add a header. - The HTTP client and the OAuth client connect directly unless the application gives a proxy. The credentials of a proxy URL go only to the proxy, and the log lines show only its host and port.
- With
ca_bundle, the OAuth client trusts only the CA certificates of the bundle, also on Windows. - The client router logs a dropped frame with only the reason, the request id and the error code of an error response. The HTTP client writes no control character of a header of the server to the log.
- A key of
RequestOptions.metacannot replace a_metakey that the SDK owns. Such a key giveserror.InvalidMeta, and the client sends nothing. - The HPACK encoder never indexes the
authorizationmetadata. - The client accepts icons only from
httpsURLs on the origin of the server and fromdata:URIs. It fetches them without cookies and without anAuthorizationheader. GIF, WebP and SVG are off by default. - The JSON Schema regular expressions run in linear time. A pattern cannot make the validator run for an exponential time.
- The WebSocket server checks
OriginandHostas the HTTP server does, and closes a connection when its access token expires. - The Unix socket server sets the mode
0600on its socket file (on Windows, an access control list for the owner only) and never replaces a file that is not a socket. - The Unix socket server refuses a directory where other accounts can add, delete or rename entries. It moves the socket to its path only after it applies the protection.
- An RSA key verifies each signature before the signature goes out.
- The client verifies each skill file against the size and the digest of the skill entry.
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