Request ownership and cleanup
Copy MarkdownResource requests have additive caller-owned handles:
{:ok, listing} = Client.start_list_resources(client, cursor: "next-page")
{:ok, templates} = Client.start_list_resource_templates(client)
{:ok, reading} = Client.start_read_resource(client, "file:///example.txt")
{:ok, response} = Client.await_resource_request(listing, 1_000)
{:ok, report} = Client.cancel_resource_request(reading, "host_cancelled")The owner PID and client process incarnation are fixed. Registration is
acknowledged before dispatch, with registration_timeout: 5_000 by default;
:timeout remains the finite end-to-end operation budget. List cursor, request
metadata and progress options retain their existing protocol-era behavior.
Actual resources/list, resources/templates/list and resources/read methods
retain their Client.Response envelopes, pagination and ordered text/blob data.
The logical handle follows MRTR continuations across fresh wire IDs. Existing
synchronous resource APIs and the public Client.ToolCall contract are unchanged.
An await timeout observes no result and leaves the operation active. A later
await can receive its result once. Cancellation settles only this owner's
operation and its dispatch/resolver workers and timers. It reports local
settlement separately from transport acceptance and cancellation-notification
delivery; remote: :unknown never promises remote execution rollback. Owner
death follows the same local settlement path without closing the shared client.
Owned connections
Hosts that own a finite activation can opt into one_shot: true when starting
the Client tree. Both child specifications and the outer Client specification
are temporary in this mode, so a failure cannot launch a replacement executable
under the same activation. Other clients retain their existing restart policy.
Client.close_connection(supervisor, timeout: 5_000) collects a transport report,
settles owned client workers, then observes supervisor termination using one
remaining budget. Its result keeps these obligations separate and is confirmed
only when all are confirmed. transport.close(pid, timeout: milliseconds) and
transport.cleanup_status(pid) provide retained version-1 reports. Reports move
from phase: :active through :closing to :closed; pending work and missing
evidence cannot confirm cleanup. Transport reports remain inspectable until the
owning host tears down the connection. Repeated transport close observes the
stored outcome and does not repeat cleanup. A later close against an already
dead tree without a retained report is unconfirmed.
Client.close/1 and transport.shutdown/1 remain best-effort APIs. Process DOWN
alone is insufficient evidence of external resource cleanup. A Session host
must retain the explicit result before final process teardown when it needs to
report that result later.
Verified stdio
Build this optional capability explicitly on native Linux or macOS:
mix backplane.mcp.stdio.build
Use {:stdio, command: executable, args: args, cleanup: :verified} for an owned
connection, together with one_shot: true and a host activation_id. Normal
compilation and ordinary direct stdio need no C compiler. Verified activation
fails explicitly before target launch if its helper is missing or unsupported;
it never falls back to an unobservable direct launch. Hex ships the C11 source
and explicit build task; a target-native executable is assembled into gateway
OTP releases and Docker images.
The guardian is the target's actual parent. It relays typed bounded packet4 data
frames, closes target stdin, allows finite EOF grace, then sends TERM and KILL
only while that exact direct child is still unreaped. Confirmation requires the
correlated receipt with actual waitpid/pipe-close facts and the guardian's
successful Port :exit_status. A nonzero target exit can still confirm released
resources. Lost receipts, helper loss, premature Port closure and deadline expiry
retain partial facts and remain unconfirmed.
The declared scope is the direct child, guardian and guardian-owned local pipes. Detached descendants, inherited endpoints held by descendants, remote work and other side effects are outside that scope. Cleanup does not wait indefinitely for a descendant to close stdout. It provides no process-tree guarantee.
HTTP resources
HTTP uses http_fetch ~> 0.21.17 through the neutral private HTTP boundary.
There is no required Finch/Mint dependency or package-owned Finch pool. Buffered
requests, legacy SSE, Streamable HTTP, modern streams, JWKS and introspection use
the same Fetch construction and preserve the existing protocol/authentication
policy. Manual redirects retain the maximum three 307 hops, relative Location,
method/body and origin checks before configured headers are sent.
request_timeout maps to the total Fetch request deadline; receive_timeout to
stream inactivity, including existing :infinity support; connect_timeout to
connection establishment. Enclosing operation deadlines bound applicable finite
timeouts. SSE reconnection options remain in the MCP coordinator. Supported
Fetch options and rejected legacy pool options are documented by HTTP.
Each allocation is registered before Fetch begins. The original Promise's
request-completion handle is handed off before headers/body await, and every
redirect hop remains tracked. Close waits on actual HTTP.RequestCompletion
barriers and owned workers. Missing or unsupported evidence remains unconfirmed.
During a live connection, a completed request handle is compacted only after
its original request-completion barrier returns :ok and its owning worker's
actual DOWN is observed. Active workers retain all redirect hops. Missing,
pending, unsupported or lost evidence remains individually tracked. The most
recent 64 confirmed resource receipts remain inspectable; older confirmed
receipts become activation-scoped aggregate resources with IDs
{activation_id, "confirmed_history:<scope>"} and
evidence.aggregate: true and an exact evidence.confirmed_count per scope.
These aggregates preserve confirmed history without retaining old request
handles or worker processes. Resource counts include aggregate counts rather
than simply counting report entries.
A single supervised worker processes completion checks or close barriers at a
time. Owner loss uses the same sequential abort queue under one finite deadline,
not one process per historical request. Monitor and per-worker request indexes
avoid rescanning completed history. Close freezes both recent and aggregate
evidence, and late completion cannot improve the retained result. Cleanup-worker
resource IDs use {activation_id, task_reference}, preserving the same accepted
identifier shape as ordinary worker resources.
Only owned request resources are aborted; shared pools and other MCP clients remain available. A remote session DELETE is a separate best-effort delivery fact and cannot prove local resource release or remote rollback.