Documentation — MCP support reference
Reference · Protocols

MCP support reference

This page lists exactly what agentd's MCP client supports: the protocol revisions, how a connection is negotiated, the capabilities it uses and the ones it refuses, its timeouts and retry schedules, every MCP log event, the MCP Events limits, agentd's own tools, and the known limits. It is for integrators building servers that agentd connects to and for operators reading its logs. The concepts are in MCP: tools and context; the configuration task is Connect MCP servers.

Revisions

agentd speaks two MCP revisions over Streamable HTTP and picks one per server. The SDK underneath is rmcp, the official Rust SDK.

2026-07-282025-11-25
First requestserver/discoverinitialize, then notifications/initialized
SessionnoneMcp-Session-Id from the initialize answer, sent on every request; a lost one is re-dialed
Per-request _metaio.modelcontextprotocol/protocolVersion, …/clientInfo, …/clientCapabilities on every requestnone; agreed once in the handshake
Headers the client setsMCP-Protocol-Version, Mcp-Method, Mcp-Name on a call, read or prompt, Mcp-Param-* for annotated tool argumentsMCP-Protocol-Version
Tool argument headerstools are listed once on connect, so every call, a worker's included, carries its Mcp-Param-* headersnot used
Server pushthe subscriptions/listen stream onlya GET event stream
Resource subscriptionsone listen per connection, its filter widened and narrowedresources/subscribe and resources/unsubscribe per URI
list_changeda listen that asks for it, held from connecton the GET stream
Cancellationclosing the request's streamnotifications/cancelled
Server-to-client requestsinside the call's answer (input_required), answered, then called againrequests on the stream, answered
Response cacheoffoff

A server that lists only revisions older than 2025-11-25 is reached through initialize at the newest revision it lists that the SDK knows.

server/discover results that leave out resultType, ttlMs or cacheScope are accepted and read as complete, 0 and private. An instructions member is not read.

Negotiation

Every connection probes with server/discover. What the probe gets decides the next step:

The probe gotagentd
a -32022 whose data.supported names 2026-07-28asks again at that revision
a -32022, or a DiscoverResult, listing only older revisionssends initialize at the newest of them it knows
a -32022 listing nothingsends initialize at 2025-11-25
a -32020 or -32021 (a modern server refusing agentd's request), a 401, a server never reached, or a version list with nothing agentd can offerfails the connect, naming the refusal
anything else: -32601, a 403, another 4xx or 5xx with or without a body, {}, a 202, silence until the connection's timeoutsends initialize at 2025-11-25
  • The probe is bounded by the connection's timeout. A fallback that is refused too names both refusals, and no request is sent twice.
  • A re-dial, and the dial of a turn worker or subagent, start the way the connection they follow reached the server.
  • A refused initialize is followed by server/discover when the server answered with a JSON-RPC error or a 4xx other than 401. A 5xx, silence or an unreachable server is not re-probed.

What agentd says about itself:

ConnectionclientInfoClient capabilities
The supervisor's (startup, reload, re-dial, a server joining)name: agentd, version: the agentd version, title: the instance namenone
A turn worker's or subagent'sname: agentd, versionelicitation

Wire examples

The probe of a supervisor connection, and a server that speaks 2026-07-28:

// agentd → server
// headers: MCP-Protocol-Version: 2026-07-28, Mcp-Method: server/discover
{"jsonrpc":"2.0","id":0,"method":"server/discover","params":{"_meta":{
  "io.modelcontextprotocol/protocolVersion":"2026-07-28",
  "io.modelcontextprotocol/clientInfo":{"name":"agentd","version":"<agentd version>","title":"<instance>"},
  "io.modelcontextprotocol/clientCapabilities":{}
}}}
// server → agentd
{"jsonrpc":"2.0","id":0,"result":{
  "resultType":"complete",
  "supportedVersions":["2026-07-28"],
  "capabilities":{"resources":{"subscribe":true,"listChanged":true},"tools":{"listChanged":true}},
  "ttlMs":0,"cacheScope":"private",
  "_meta":{"io.modelcontextprotocol/serverInfo":{"name":"<server>","version":"<server version>"}}
}}

The handshake sent to a server that predates server/discover:

// agentd → server (no MCP-Protocol-Version header on this request)
{"jsonrpc":"2.0","id":1,"method":"initialize","params":{
  "protocolVersion":"2025-11-25",
  "capabilities":{},
  "clientInfo":{"name":"agentd","version":"<agentd version>","title":"<instance>"}
}}
// server → agentd, with the header Mcp-Session-Id: <session>
{"jsonrpc":"2.0","id":1,"result":{"protocolVersion":"2025-11-25","capabilities":{"tools":{}},
  "serverInfo":{"name":"<server>","version":"<server version>"}}}
// agentd → server
{"jsonrpc":"2.0","method":"notifications/initialized"}

A tool call and its two kinds of failure:

// agentd → server
{"jsonrpc":"2.0","id":2,"method":"tools/call","params":{
  "name":"get_weather","arguments":{"location":"NYC"},
  "_meta":{"agent/run_id":"<run id>","agent/instance":"<instance>","traceparent":"<w3c traceparent>"}}}
// server → agentd: the tool ran and failed (a successful JSON-RPC response)
{"jsonrpc":"2.0","id":2,"result":{"content":[{"type":"text","text":"unknown city"}],"isError":true}}
// server → agentd: a protocol fault
{"jsonrpc":"2.0","id":2,"error":{"code":-32602,"message":"unknown tool: get_weather"}}

Capabilities

FeatureSupportHow agentd uses it
tools/listyesEvery page, following nextCursor to the end; each page is bounded by the timeout
tools/callyesTurns, subagents, mcp.tool and tool steps, mapped contracts
notifications/tools/list_changedyesLogged as mcp.tools_changed; the supervisor's catalogue is rebuilt at the next reload; a subagent session re-lists at its next turn boundary
resources/listyesA subagent's resource note (at most 50 URIs, first server wins); the mcp.resource step's list
resources/readyesNotify-then-read, the instruction, resource.read in a subagent, the mcp.resource step's read
resources/templates/listyesThe mcp.resource step's templates; a template is not subscribable
resources/subscribe, subscriptions/listenyessubscribe starts, wait {on: resource}, the instruction; concrete URIs only, and only when the server advertises resources.subscribe
notifications/resources/list_changedreceivedNot acted on
prompts/list, prompts/getyesSkill discovery; the mcp.resource step's prompt
completion/completeyesThe mcp.resource step's complete
ElicitationyesOn worker and subagent connections: an ask_human gate, answered within 300 s or cancel
Sampling, roots, tasksnoNot declared; a server request for them is dropped
notifications/message, notifications/progressreceivedNot acted on
Response cache (ttlMs, cacheScope)offEvery read and list goes to the server
MCP Events (SEP-3415, draft)yes, at 2026-07-28 onlyBindings and grants; the server declares io.modelcontextprotocol/events, or a top-level events capability

agentd refuses at load any configured header the client sets itself: MCP-Protocol-Version, Mcp-Method, Mcp-Name, every Mcp-Param-*, Mcp-Session-Id and Last-Event-ID, in mcp.servers[].headers and in an MCP service's headers.

Request metadata

Every tools/call carries keys in _meta that let a server correlate and deduplicate:

KeySent on
agent/run_idevery call
agent/instanceevery call
traceparentevery call made with an active trace (W3C trace context)
agent/ctxcalls made during a conversation turn
agent/idempotency_keycalls from a turn, from an mcp.tool step (stable across the step's retries, or its idempotency.value), and from the supervisor's tool registry
agent/attempt, agent/runcalls from an mcp.tool step
agent/acting_for, agent/labelscalls from the supervisor's tool registry (a workflow tool step, a mapped contract): the principal the work is for, or the instance's autonomous identity

Timeouts

BoundValueApplies to
Connection timeoutmcp.servers[].timeout, else mcp.default_timeout, default 60 seach silence on a supervisor connection: the handshake, tools/list, registry-routed calls
Child connection timeout60 s, fixedeach silence on a turn worker's or subagent's connection
Turn tool callthe turn's remaining deadline, at most 600 sthe whole of one call the model makes
mcp.tool stepthe step's timeout, else limits.step_timeout, default 600 sthe whole of the call
Management call2 sa subscribe, an unsubscribe or a re-dial handshake made from the supervisor's loop
Listen idle10 min, never less than the connection timeouta quiet listen is closed and opened again (mcp.listen.ended, reason: idle)
Elicitation300 sa person answering a server's question; then cancel
Answer POST5 s, set by the SDKsending agentd's answer to a server request
events/* request30 seach MCP Events request

A timeout bounds silence: a call that streams progress is not cut off by the connection timeout. A call abandoned at its bound holds up no other call on the connection.

At shutdown agentd closes its connections. There is no server process to signal, and the close counts inside lifecycle.drain_timeout (default 25 s). MCP Events subscriptions are not released at shutdown; the next start arms them again from their stored cursors.

Retry schedules

WhatSchedule
Dialing a server that was down at startup1 s after the first failure, doubling to 60 s
Re-dialing a server that lost its session1 s, doubling to 30 s; one server per loop pass
Asking again for a failed subscription1 s, doubling to 30 s
Listening again after a listen ended or was unavailable250 ms, doubling to 30 s
MCP Events: a failed request (transport, 5xx, timeout)1 s, doubling to 5 min, with ±10% jitter
MCP Events: -32025 ResourceExhaustedthe same, up to 15 min
MCP Events: listing a server's event types againevery 15 min, after a reload, or on its list_changed
MCP Events: pollingthe server's nextPollMs, at least 1 s, 30 s when the server names none, at most 15 min, with ±10% jitter

A poll asks for at most 50 occurrences. While the server says hasMore and each page held something new, agentd polls again at once, ten times in a row at most.

Log events

Every event is one NDJSON line on stderr. Fields beyond the common envelope are listed. Events and metrics reference documents the envelope.

Connections

EventLevelFieldsWhen
mcp.connectinfoserver, protocol, tools, reasonA connection is made. tools is the advertised count at startup and on joining. reason is joined, reload or session_lost when it is not the first dial
mcp.connect.failwarnserver, err, reasonA dial failed. Also counted in agent_mcp_connect_failures_total
mcp.disconnectinfo, warnserver, reasonA connection was closed: reload or reload refused (info), or session_lost (warn)
mcp.joinedinfoserverA server that was down at startup joined; its subscriptions and bindings are restored
mcp.join.refusedwarnserver, errorsA joining server answered, but the catalogue with its tools could not be built; it is dialed again later
mcp.tool.droppedwarnserver, tool, reasonThe SDK left a tool out of a 2026-07-28 listing because of an invalid x-mcp-header annotation
mcp.tools_changedinfoserver, noteThe server said its tool list changed
mcp.tools_refreshedinfotoolsA subagent session re-listed its tools; tools is the new catalogue size
mcp.tools_refresh_failedwarnerrA subagent's re-list failed; its current catalogue stays
mcp.unsubscribedinfoserver, uri, reasonA subscription nothing wants any more was released (reason: unowned)
mcp.unsubscribe.failwarnserver, uri, reason, errReleasing it failed; it is tried again later

Listens (2026-07-28)

EventLevelFieldsWhen
mcp.listen.endedinfoserver, reason: idle, detail, retry_msA quiet listen reached its idle limit and is reopened
mcp.listen.endedwarnserver, reason, retry_msA listen ended for any other reason: the server closed it, or the client fell behind
mcp.listen.resumedinfoserverA listen is held again
mcp.listen.narrowedwarnserver, droppedThe server acknowledged a listen without some URIs; they are asked for again
mcp.listen.unavailablewarnserver, reason, retry_msA listen failed; logged once, retried on the listen schedule
mcp.listen.flags_refusedwarnserver, reasonThe server refused the list_changed flags; the listen continues without them

A listen the server refuses outright (-32601 or -32602) is logged once and not asked for again; the connection works without its list_changed.

Subscriptions

EventLevelFieldsWhen
start.subscribe.armedinfoworkflow, node, server, uri, reasonA subscribe start is subscribed
start.subscribe.unsupportederrorworkflow, node, server, uri, errThe server offers no subscriptions; the start can never fire
start.subscribe.failwarnworkflow, node, server, uri, errA subscribe failed in a way that may pass; asked again on the backoff
start.subscribe.no_serverwarnworkflow, node, serverThe start's server is not connected
wait.resubscribedinfoserver, uri, reasonA parked wait {on: resource} is subscribed again
wait.resubscribe.failwarnserver, uri, reason, errSubscribing it again failed in a way that may pass; retried
wait.resubscribe.failerrorserver, uri, reason, err, failed or parkedThe waits failed, or stay parked because the server is configured but not connected
instruction.subscribe.unsupportedinfoserver, uriThe instruction's server offers no subscriptions; the instruction is followed by its refresh alone
instruction.subscribe.failwarnserver, uri, errSubscribing the instruction failed; retried
instruction.eventinfoserver, event_typeAn MCP Events occurrence re-read the instruction

Skills

EventLevelFieldsWhen
skills.discoveredinfoserver, count, skillsSkills found on a source
skills.source.unavailablewarnserverA skills.sources server is not connected

MCP Events

EventLevelFieldsWhen
mcp.events.listedinfoserver, typesA server's event types were listed
mcp.events.list.failwarnserver, err, retry_msListing failed; retried
mcp.events.list_changedinfoserverThe server said its event types changed; listed again
mcp.events.armedinfoserver, event_type, mode, cursor_presentA binding is polling, or its webhook subscription is live
mcp.events.arm.failwarn, errorserver, event_type, err, retry_msArming a binding failed; retried
mcp.events.refusederrorserver, event_type, reason, detailThe binding cannot work; see refusals
mcp.events.poll.failwarn, errorserver, event_type, code, err, retry_msA poll failed; retried. Error level for -32020
mcp.events.pausedwarnserver, event_type, cause, detailIntake is held: cause is pressure, rate or store_degraded; the cursor is kept
mcp.events.resumedinfoserver, event_type, causeA paused binding resumed
mcp.events.gapwarnserver, event_type, causeA loss the wire shows: truncated, no_replay or schema_changed
mcp.events.rejectedwarnserver, event_type, event_id, causeAn occurrence was not appended: oversize or malformed; the cursor moves past it
mcp.events.schema_mismatchwarnserver, event_type, event_idAppended with schema_ok: false
mcp.events.subscribedinfoserver, event_type, owner, reason, subscriptionA runtime subscription was made; also audited
mcp.events.unsubscribedinfoserver, event_type, owner, reason, subscriptionA runtime subscription was released; also audited
mcp.events.removedinfoserver, event_type, reasonA binding was removed and its records deleted
mcp.events.terminatederrorserver, event_type, codeThe server ended a webhook subscription: -32024 refuses the binding, -32023 or -32026 lists the server again, anything else re-arms after a backoff
mcp.events.delivery_suspendedwarnserver, event_type, last_error, activeThe server reported failed deliveries; re-armed from the stored cursor
mcp.events.throttledinfoserver, event_type, retry_after_msThe server is throttling deliveries
mcp.events.mode_changedwarnserver, event_type, from, to, causeA binding moved to the next delivery mode the operator listed, until a reload or a re-dial decides afresh
mcp.events.callback_failederrorserver, event_type, reason, urlThe server could not use the webhook URL; after five in a row the next mode is tried
mcp.events.hook.deniedwarnserver, event_type, reasonA webhook delivery failed its signature check; answered 401
mcp.events.hook.unsavedwarnerrThe stream head was not saved; the delivery is answered 503
mcp.events.index.failedwarnsubscriptions, errThe record of runtime subscriptions could not be saved

Payloads are never logged; lines carry event_id, and text from the server is stripped of control characters and cut at 200 bytes.

MCP Events reference

Limits

LimitValue
Bindings per server64
Bindings and subscriptions per instance256
Live runtime subscriptions per grant (max)1 to 64, default 8
A tool-made subscription's lifetimeat most 30 d, default 1 d
Runtime subscription arguments, serialized4 KiB
One occurrence, or one webhook body256 KiB, and at most store.max_value_bytes
An eventId1 KiB
A stored cursor4 KiB
Duplicate detectionids seen in the last 35 min, at most 4096 per binding
Listing20 pages or 1000 types; past that, list_too_large
Webhook lease (ttl)default 1 h, refreshed at 80% of what the server grants
A webhook refusal still outstandingafter 5 min the binding is paused, keeping its cursor
events/* requests4 worker threads of their own, never the main loop

MCP Events refusals

mcp.events.refused is logged once per cause, at error level. Nothing is sent where nothing may be, and the refusal holds until a later listing, a re-dial or a reload shows the cause has cleared.

reasonWhen
revisionThe connection reached 2025-11-25; no events/* request is sent
no_capabilityThe server does not declare the events extension; no events/* request is sent
method_not_foundevents/list answered -32601; a gateway may not pass Events through
unknown_eventThe server's listing does not name the type
no_common_modeNo listed delivery mode is one the type offers and agentd supports
invalid_argumentsThe arguments fail the type's input schema, or the server answered -32602
forbiddenThe server answered -32024: the credential may not subscribe
not_supportedThe server answered -32026 for the delivery mode
list_too_largeThe listing ran past 20 pages or 1000 types

Stream event

FieldValue
idmcp/<server>/<type>/<eventId>, or mcp/<server>/<type>/sha256:<hash> when longer than 200 bytes
stream, subjectthe binding's into
sourcemcp:<server>
data.server, data.eventthe server and the event type
data.event_id, data.occurred_atthe server's eventId and timestamp
data.schema_okwhether the payload passed the type's payload schema
data.subscriptionthe binding's key, assigned by agentd
data.payloadthe server's data, verbatim

Webhook delivery answers

Routes are <webhooks.url>/hooks/_mcp/<binding>. A delivery's Standard Webhooks signature is checked before its body is used: all four headers, a timestamp within 5 minutes, and the HMAC over the raw body.

StatusWhen
200The occurrence is durably appended or is a duplicate, or a gap or terminated envelope was taken
401The signature check failed
405The method is not POST
410No binding at this path, a body that is not a JSON object, or a malformed occurrence; final
413The body is over 256 KiB; answered before it is read; final
429Pressure, or the binding's rate is spent; with Retry-After
503Not armed yet, shutting down, a degraded store, or the append was not saved; with Retry-After

Metrics

MetricTypeLabels
agent_mcp_connect_failures_totalcounterserver
agent_mcp_events_totalcounterserver, event, mode, outcome (appended, duplicate, rejected, schema_mismatch, refused_pressure)
agent_mcp_events_requests_totalcounterserver, method, outcome (ok, refused, failed)
agent_mcp_event_gaps_totalcounterserver, event, cause
agent_mcp_events_dedup_evicted_totalcounterserver, event
agent_mcp_event_lag_msgaugeserver, event: append time minus the server's timestamp
agent_subscriptions_activegaugethe kind="event" series, by mode

Events and metrics reference lists every metric.

agentd's own tools

These tools are built into agentd and act on the agent, never on the world. agentd --capabilities prints the list as internal_tools. "Root" is the instance's own conversation, "workflow" a workflow step, and "subagent" a subagent's loop; the grant is the default, which configuration can narrow.

ToolDefault grantDoes
instruction.readroot, workflow, subagentRead the current instruction
instruction.subscriberootSubscribe to the instruction resource, or switch to another URI
subagent.run, .send, .status, .await, .list, .kill, .retireroot, workflowSpawn, steer, wait for, list, stop and retire subagents
memory.get, .set, .list, .push, .shift, .pop, .deleteroot, workflow, subagentDurable memory, including queue operations
artifact.create, .get, .delete, .listroot, workflow, subagentArtifacts delivered with the task
message.sendroot, workflow, subagentDeliver a message into one of the instance's own conversations
events.list, .subscribe, .unsubscribe, .subscriptionsroot, workflowRuntime MCP Events subscriptions; offered only when a grant sets tools: true
workflow.run, .cancel, .waitroot, workflowStart, cancel and wait for runs
workflow.list, .status, .signalroot, workflow, subagentRead runs and send signals
workflow.create, .update, .delete, .pause, .resumerootDefine workflows at runtime, pause and resume runs
plan.create, .get, .update, .clearroot, workflow, subagentThe conversation's working plan
ask_humanroot, workflow, subagentAsk a person and wait for a schema-checked answer
sleep, awaitroot, workflow, subagentWait for a duration, or until a condition holds
context.compactroot, workflow, subagentSummarize older messages in the context
thinkroot, workflow, subagentOne structured reasoning call with no tools
decideroot, workflow, subagentTyped questions answered with calibrated confidence
skills.list, .load, .unloadroot, workflow, subagentThe skills catalogue and the loaded set
finishroot, subagentEnd the current unit of work with a status
statuseveryone, A2A callers includedThe instance's runs, subagents, conversations, budget and store
knowledge.search, .get, .listroot, workflow, subagentA contract: implemented by knowledge.server or a mapping
search.query, .fetchroot, workflow, subagentA contract: implemented by search.server or a mapping
code.runroot, workflowA contract: implemented only by a mapping to a sandbox server
execroot, workflow, subagentA contract: a mapping, or a local command runner with the exec build feature and security.exec.enabled

A subagent's loop also gets resource.read when its servers expose resources. It reads one resource by URI.

Known limits

  • Revision choice. The probe alone chooses the revision. A server that answers server/discover but serves subscriptions/listen in a shape the client cannot read holds no listens, though the same server reached through initialize might have. No setting makes agentd start with initialize.
  • Probe cost. Dialing a server that predates server/discover costs one extra request. A server that answers the probe only after the connection's timeout is reached through initialize after that wait.
  • Revision changes. A server that changes revision is probed again only when it refuses initialize, or when a reload dials it afresh.
  • Dropped tools. At 2026-07-28 the SDK leaves out of a listing any tool whose x-mcp-header annotation it rejects, a nested one for instance. agentd logs each as mcp.tool.dropped.
  • Message order. When a server sends a request ahead of a tools/list answer on the same stream, the SDK may miss the tools' header schemas, and an annotated call lacks its Mcp-Param-* headers until the next list. A notification ahead of the answer is handled.
  • Interactive reads. A resources/read answered with input_required fails.
  • Answer bound. The POST that carries agentd's answer to a server request is bounded at 5 s by the SDK.
  • Listens do not resume. A reopened listen starts from the moment it reopens. agentd reads every held URI again after reopening, so state converges, but changes inside one gap are coalesced and a change reverted inside the gap is not delivered.
  • Catalogue refresh. The supervisor's tool catalogue is rebuilt at a reload, not when a server says its tools changed.
  • Child timeouts. A turn worker's and a subagent's connections use a fixed 60 s timeout; a per-server timeout does not reach them.
  • Tags are per server. The union of every key under tags applies to every tool of the server.
  • MCP Events is a draft. Its methods, error codes and capability key may change. Push delivery is not implemented; poll and webhook are.
  • Invisible losses. Two losses do not show on the wire: a server that omits its delivery status and gives up deliveries that never reached agentd, and anything missed on a type without replay.
  • Replicas. Each instance holds its own Events subscriptions, so replicas need distinct webhooks.url values.

Next steps