pub struct ControlClient { /* private fields */ }Expand description
A connected mcpmesh-local/1 client: the framed stream + the server’s Hello.
Implementations§
Source§impl ControlClient
impl ControlClient
pub fn hello(&self) -> &Hello
Sourcepub async fn request(&mut self, request: Request) -> Result<Value, ClientError>
pub async fn request(&mut self, request: Request) -> Result<Value, ClientError>
Issue a typed request; return the JSON-RPC result (or ClientError::Api on a
JSON-RPC error).
Sourcepub async fn request_value(
&mut self,
request: &Value,
) -> Result<Value, ClientError>
pub async fn request_value( &mut self, request: &Value, ) -> Result<Value, ClientError>
Issue a RAW request frame — the escape hatch for methods outside the typed
Request surface (the daemon-internal shutdown, third-party
{"method":..,"params":{}} shapes the dispatcher tolerates). Returns the JSON-RPC
result value (or ClientError::Api on a JSON-RPC error).
Sourcepub async fn open_session(
self,
peer: String,
service: String,
) -> Result<(FrameReader<ControlRead>, ControlWrite), ClientError>
pub async fn open_session( self, peer: String, service: String, ) -> Result<(FrameReader<ControlRead>, ControlWrite), ClientError>
Send a request WITHOUT reading a response — for OpenSession, after which the
socket stops being JSON-RPC and becomes a raw MCP byte pipe (protocol.rs). Returns
the framed halves so the caller can pump the session — the SAME FrameReader that
read the Hello, so bytes the daemon pipelined behind it are never lost. A caller
that must re-box the read half calls FrameReader::into_inner, which returns the
BUFFERED reader (its read-ahead travels with it — see the pipelining test below).
Sourcepub async fn open_stream(
self,
method: &str,
) -> Result<(FrameReader<ControlRead>, ControlWrite), ClientError>
pub async fn open_stream( self, method: &str, ) -> Result<(FrameReader<ControlRead>, ControlWrite), ClientError>
Send a parameterless stream-upgrade request WITHOUT reading a response — like
open_session, but generic on the method: after this call the
socket stops being request/response and becomes a one-way push stream of frames the caller
READS (the subscribe telemetry surface). Returns the framed halves — the SAME
FrameReader that read the Hello, so any frame the daemon pipelined behind it is never
lost. The write half is handed back so the caller can hold the connection open (a watcher
only reads, but dropping the writer would half-close the socket).
Sourcepub async fn status(&mut self) -> Result<StatusResult, ClientError>
pub async fn status(&mut self) -> Result<StatusResult, ClientError>
The daemon’s status picture: services served, known peers, roster/presence state,
self identity, recent pairings, and advisory reachability.
Sourcepub async fn register_service(
&mut self,
name: &str,
backend: BackendSpec,
allow: Vec<String>,
) -> Result<(), ClientError>
pub async fn register_service( &mut self, name: &str, backend: BackendSpec, allow: Vec<String>, ) -> Result<(), ClientError>
Register/update a [services.*] entry idempotently (the daemon persists it and hot-reloads
serving). The daemon acks; the ack body is discarded.
Sourcepub async fn register_service_with(
&mut self,
name: &str,
backend: BackendSpec,
allow: Vec<String>,
ephemeral: bool,
) -> Result<(), ClientError>
pub async fn register_service_with( &mut self, name: &str, backend: BackendSpec, allow: Vec<String>, ephemeral: bool, ) -> Result<(), ClientError>
register_service with an explicit ephemeral flag (#36). When
ephemeral is true the registration lives only in daemon memory and is unregistered
automatically when THIS control connection closes — no config write, nothing to clean up.
Ideal for an embedder serving a socket backend from a fresh path each run.
Sourcepub async fn invite(
&mut self,
services: Vec<String>,
) -> Result<InviteResult, ClientError>
pub async fn invite( &mut self, services: Vec<String>, ) -> Result<InviteResult, ClientError>
Mint a single-use pairing invite granting services (see invite_multi for more than
one); return the copyable
mcpmesh-invite: line + its expiry.
Sourcepub async fn invite_with(
&mut self,
services: Vec<String>,
app_label: Option<String>,
) -> Result<InviteResult, ClientError>
pub async fn invite_with( &mut self, services: Vec<String>, app_label: Option<String>, ) -> Result<InviteResult, ClientError>
invite with an opaque app_label (#31) carried through to the redeemer’s
pair result. mcpmesh never interprets the label; the embedder does (e.g. its own URN).
Sourcepub async fn invite_multi(
&mut self,
services: Vec<String>,
app_label: Option<String>,
max_uses: Option<u32>,
) -> Result<InviteResult, ClientError>
pub async fn invite_multi( &mut self, services: Vec<String>, app_label: Option<String>, max_uses: Option<u32>, ) -> Result<InviteResult, ClientError>
invite_with, plus max_uses (#87): an invite redeemable up to that many times, each
redemption running its own SAS ceremony and writing its own peer rows.
None = 1, the single-use default. The value is clamped daemon-side to
MAX_INVITE_USES — read
InviteResult::uses_remaining for what you actually
got rather than assuming the request was honoured verbatim.
Sourcepub async fn invite_named(
&mut self,
services: Vec<String>,
app_label: Option<String>,
max_uses: Option<u32>,
peer_nickname: Option<String>,
) -> Result<InviteResult, ClientError>
pub async fn invite_named( &mut self, services: Vec<String>, app_label: Option<String>, max_uses: Option<u32>, peer_nickname: Option<String>, ) -> Result<InviteResult, ClientError>
Mint an invite, optionally under YOUR OWN local name for whoever redeems it (#87).
peer_nickname overrides the name they claim for themselves — the fix for two same-model
machines that does not require the other person to rename theirs. Never sent to them, and
rejected alongside max_uses > 1 (one name for every redeemer collides on the second).
Sourcepub async fn invite_full(
&mut self,
services: Vec<String>,
app_label: Option<String>,
max_uses: Option<u32>,
peer_nickname: Option<String>,
as_self: bool,
) -> Result<InviteResult, ClientError>
pub async fn invite_full( &mut self, services: Vec<String>, app_label: Option<String>, max_uses: Option<u32>, peer_nickname: Option<String>, as_self: bool, ) -> Result<InviteResult, ClientError>
Mint an invite, optionally as a SELF-ENROLLMENT (#86): the redeemer becomes another device of YOU rather than a peer, so both present one identity.
as_self requires an empty services and max_uses of 1 — it grants nothing, and a
multi-use identity invite is a standing offer to become you.
Sourcepub async fn endorse_peer(
&mut self,
subject: &str,
subject_user_id: Option<String>,
) -> Result<PeerEndorseResult, ClientError>
pub async fn endorse_peer( &mut self, subject: &str, subject_user_id: Option<String>, ) -> Result<PeerEndorseResult, ClientError>
Produce an endorsement of subject for someone else to redeem (#65).
Signs with THIS node’s user key. It is a statement for the recipient — it changes nothing about your own trust in the subject, and only resolves for someone paired with you.
Sourcepub async fn introduce_peer(
&mut self,
params: PeerIntroduceParams,
) -> Result<(), ClientError>
pub async fn introduce_peer( &mut self, params: PeerIntroduceParams, ) -> Result<(), ClientError>
Install a peer from an endorsement by someone you are already paired with (#65).
Installs IDENTITY, not authorization — the peer becomes resolvable and is granted nothing.
subject_user_id requires subject_binding, the subject’s OWN device→user binding: a
user_id is authorization-bearing and public, so an endorser alone must not attach one.
Sourcepub async fn pair(
&mut self,
invite_line: &str,
) -> Result<PairResult, ClientError>
pub async fn pair( &mut self, invite_line: &str, ) -> Result<PairResult, ClientError>
Redeem a pairing invite; return the inviter’s suggested nickname, the display-only SAS code, and the granted services.
Sourcepub async fn pair_as(
&mut self,
invite_line: &str,
as_nickname: Option<String>,
) -> Result<PairResult, ClientError>
pub async fn pair_as( &mut self, invite_line: &str, as_nickname: Option<String>, ) -> Result<PairResult, ClientError>
Redeem an invite, optionally under YOUR OWN local name for the inviter (#87).
as_nickname overrides the name the invite suggests. Use it when that name is already
taken locally — otherwise the pairing is refused and the only other fixes are asking the
inviter to re-mint or renaming your existing peer. It does not bypass the collision check:
an alias that itself collides is refused the same way.
Sourcepub async fn pair_opts(
&mut self,
invite_line: &str,
as_nickname: Option<String>,
allow_self_enroll: bool,
) -> Result<PairResult, ClientError>
pub async fn pair_opts( &mut self, invite_line: &str, as_nickname: Option<String>, allow_self_enroll: bool, ) -> Result<PairResult, ClientError>
Redeem an invite, stating whether a SELF-ENROLLMENT is a ceremony you offered (#178).
pair and pair_as pass false, so a mcpmesh-enroll: line
pasted into an ordinary “join” field is refused with
ERR_SELF_ENROLL_NOT_OFFERED before anything is
dialled — the invite survives, so the same line still works once the person is offered the
real choice. Pass true only from a path that actually means “add another of my own
devices”: the ceremony writes a device→user binding that is irrevocable short of rotating
the user key.
mcpmesh_node::pairing::is_enrollment_line answers which kind of line you are holding without
dialling, for a UI that wants to PROMPT rather than recover from a refusal.
Sourcepub async fn peer_remove(&mut self, nickname: &str) -> Result<(), ClientError>
pub async fn peer_remove(&mut self, nickname: &str) -> Result<(), ClientError>
Unpair a peer by nickname: drops its identity row AND its every-allow membership
(idempotent; live sessions are not severed). The daemon acks; the ack body is discarded.
Sourcepub async fn peer_rename(
&mut self,
user_id: Option<String>,
nickname: Option<String>,
to: &str,
) -> Result<(), ClientError>
pub async fn peer_rename( &mut self, user_id: Option<String>, nickname: Option<String>, to: &str, ) -> Result<(), ClientError>
Rename a contact’s nickname to to — every device sharing user_id when given, else the
single provisional nickname entry — carrying its grants along. The daemon refuses (a
ClientError::Api) when to is empty or already names a different identity. The daemon
acks; the ack body is discarded.
Sourcepub async fn roster_install(
&mut self,
path: &str,
org_root_pk: Option<String>,
) -> Result<RosterInstallResult, ClientError>
pub async fn roster_install( &mut self, path: &str, org_root_pk: Option<String>, ) -> Result<RosterInstallResult, ClientError>
Install a signed roster from the LOCAL file at path (org_root_pk pins the org root on
FIRST install); return the installed org id + serial + severed-session count.
Sourcepub async fn roster_members(
&mut self,
) -> Result<RosterMembersResult, ClientError>
pub async fn roster_members( &mut self, ) -> Result<RosterMembersResult, ClientError>
Read the installed roster’s MEMBERSHIP (#93): the declared groups, and every person with their display name, groups, and devices.
Distinct from status’s presence, which enumerates reachable DEVICES and
omits a person entirely when none of theirs is up. This is the member list — everyone the
roster carries, with online per device, so one read serves both questions.
Advisory: display and authoring input, never an authorization answer. Empty in a
pure-pairing daemon and before the first roster is installed. api_minor >= 46.
Sourcepub async fn org_create(
&mut self,
name: &str,
expires_secs: Option<i64>,
roster_url: Option<String>,
) -> Result<OrgCreateResult, ClientError>
pub async fn org_create( &mut self, name: &str, expires_secs: Option<i64>, roster_url: Option<String>, ) -> Result<OrgCreateResult, ClientError>
AUTHOR an org (#66): mint this node’s org root key, sign an empty roster, install it (which pins the root), and return the copyable invite plus the root’s fingerprint.
One-time per node — a second call is refused rather than replacing the key, which would orphan every roster already signed with it.
Show org_root_fingerprint to the operator: it is what every joiner reads back
out-of-band, and it is the only thing anchoring their trust in the org. api_minor >= 46.
Sourcepub async fn org_approve(
&mut self,
join_code: &str,
groups: Vec<String>,
user_id: Option<String>,
) -> Result<OrgApproveResult, ClientError>
pub async fn org_approve( &mut self, join_code: &str, groups: Vec<String>, user_id: Option<String>, ) -> Result<OrgApproveResult, ClientError>
APPROVE a join code into the roster (#66): verify its device→user-key binding, add the
member with groups, re-sign, install.
The result’s join_code_fingerprint is not decoration. Nothing in a join code binds it
to a human, so a substituted code is caught by the two people comparing that fingerprint
out-of-band, or it is not caught at all. Show it and have the operator confirm it.
Each group must already be declared in the roster; an undeclared one is refused. user_id
overrides the id the joiner requested — worth using, since that id is chosen by the person
being approved and is what every allow entry will name. api_minor >= 46.
Sourcepub async fn org_join_code(
&mut self,
join_code: &str,
) -> Result<OrgJoinCodeResult, ClientError>
pub async fn org_join_code( &mut self, join_code: &str, ) -> Result<OrgJoinCodeResult, ClientError>
INSPECT a join code without approving it (#66): what it claims, and the fingerprint that decides whether to believe it. Read-only — nothing is signed or installed.
Call this before org_approve, show
join_code_fingerprint, and have the operator confirm it out-of-band. Nothing in a join
code binds it to a person; a substituted one carries a different key and diverges here. The
fingerprint on the approval RESULT is the same words, but by then the member is in the
signed roster — too late to decline.
The claims (display_name, requested_user_id, device_label) are chosen by the sender.
Render them; do not trust them. A forged binding is refused rather than described.
api_minor >= 46.
Sourcepub async fn org_revoke(
&mut self,
target: &str,
user_key: bool,
) -> Result<OrgRevokeResult, ClientError>
pub async fn org_revoke( &mut self, target: &str, user_key: bool, ) -> Result<OrgRevokeResult, ClientError>
REVOKE from the roster (#66) — and sever the cut devices’ live sessions, immediately.
Three readings, and picking the wrong one is destructive, so the result reports which
mode was applied: "<user_id>/<label>" cuts ONE device; a bare user_id removes the
person and revokes ALL their devices; user_key = true is a key ROTATION — the person is
removed but their devices stay un-revoked so the same hardware re-enrolls under a fresh
user key. api_minor >= 46.
Sourcepub async fn org_join(
&mut self,
org_id: &str,
org_root_pk: &str,
user_id: &str,
user_key: &str,
) -> Result<OrgJoinResult, ClientError>
pub async fn org_join( &mut self, org_id: &str, org_root_pk: &str, user_id: &str, user_key: &str, ) -> Result<OrgJoinResult, ClientError>
Pin the org root on a JOINER (no roster yet). user_key is a LOCAL path — the key never
crosses the API. Returns the pinned org id.
Sourcepub async fn set_roster_url(&mut self, url: &str) -> Result<(), ClientError>
pub async fn set_roster_url(&mut self, url: &str) -> Result<(), ClientError>
Pin the HTTPS roster URL ([roster].url) in the daemon’s config. The daemon acks; the
ack body is discarded.
Sourcepub async fn peer_services(
&mut self,
peer: &str,
) -> Result<Vec<String>, ClientError>
pub async fn peer_services( &mut self, peer: &str, ) -> Result<Vec<String>, ClientError>
Discover which services a paired peer (a nickname, eid:, or b64u:) CURRENTLY grants
the caller (#52) — dials the peer and returns the service names its allow admits for the
caller’s principal (only your own admitted services, never the peer’s full registry).
Sourcepub async fn unregister_service(
&mut self,
name: &str,
) -> Result<(), ClientError>
pub async fn unregister_service( &mut self, name: &str, ) -> Result<(), ClientError>
Remove a service registration (#50) — the deregistration mirror of register_service.
Removes the whole entry (allow included) + any ephemeral registration of the name, then
hot-reloads. Idempotent: an unknown name is a clean no-op.
Sourcepub async fn service_allow_grant(
&mut self,
service: &str,
principal: &str,
) -> Result<(), ClientError>
pub async fn service_allow_grant( &mut self, service: &str, principal: &str, ) -> Result<(), ClientError>
Grant a stable principal (b64u:/eid:) access to service WITHOUT (re)pairing (#44)
— the per-peer “sharing on” toggle. Idempotent; an unknown service is a clean no-op.
Sourcepub async fn service_allow_revoke(
&mut self,
service: &str,
principal: &str,
) -> Result<(), ClientError>
pub async fn service_allow_revoke( &mut self, service: &str, principal: &str, ) -> Result<(), ClientError>
Revoke a stable principal from service’s allow WITHOUT unpairing (#44) — the
“sharing off” toggle. The peer’s identity row is untouched; it just cannot open NEW
sessions (in-flight ones run to completion). Idempotent.
Sourcepub async fn set_app_metadata(
&mut self,
metadata: &str,
) -> Result<(), ClientError>
pub async fn set_app_metadata( &mut self, metadata: &str, ) -> Result<(), ClientError>
Set this node’s opaque app-metadata blob (#39, roster mode): ≤256 bytes, folded
signed into each presence heartbeat so paired peers read it in status presence —
no per-peer session. "" clears it; in-memory (re-set on startup).
Sourcepub async fn set_relays(
&mut self,
relay_urls: &[String],
) -> Result<SetRelaysResult, ClientError>
pub async fn set_relays( &mut self, relay_urls: &[String], ) -> Result<SetRelaysResult, ClientError>
Set this node’s CUSTOM relay set LIVE (#53). relay_urls is the desired set (each must
parse as an iroh RelayUrl; empty is rejected). When the node is already in
relay_mode = "custom", the daemon diffs against the running endpoint and applies the
delta live (iroh insert_relay/remove_relay) — no restart, no dropped sessions — then
persists [network]. When the node is currently default/disabled, the config is
persisted but the live mode transition isn’t possible: the returned
SetRelaysResult::restart_required is true. Idempotent (an unchanged set → changed: false, no writes).
Sourcepub async fn set_nickname(&mut self, nickname: &str) -> Result<(), ClientError>
pub async fn set_nickname(&mut self, nickname: &str) -> Result<(), ClientError>
Rename this node LIVE (#37): the daemon validates + persists [identity].nickname
under its own config lock and updates the name future invites present — no restart.
Peers keep their stored pairing-time nickname until a re-invite (display-only).
Sourcepub async fn audit_summary(&mut self) -> Result<AuditSummaryResult, ClientError>
pub async fn audit_summary(&mut self) -> Result<AuditSummaryResult, ClientError>
Summarize the daemon’s LOCAL audit log into per-peer / per-service session counts (local-only — nothing is transmitted).
Sourcepub async fn blob_publish(
&mut self,
scope: &str,
path: &str,
) -> Result<BlobPublishResult, ClientError>
pub async fn blob_publish( &mut self, scope: &str, path: &str, ) -> Result<BlobPublishResult, ClientError>
Publish a local file into scope; return the minted mcpmesh/blob/1 ticket + hash.
Sourcepub async fn blob_list(&mut self) -> Result<BlobScopeList, ClientError>
pub async fn blob_list(&mut self) -> Result<BlobScopeList, ClientError>
List the daemon’s blob scopes (name → hashes + grants + withdrawn).
A DEFAULT LIMIT applies (#84b) — check truncated and page with
blob_list_paged rather than assuming you saw everything.
Sourcepub async fn blob_list_paged(
&mut self,
params: BlobListParams,
) -> Result<BlobScopeList, ClientError>
pub async fn blob_list_paged( &mut self, params: BlobListParams, ) -> Result<BlobScopeList, ClientError>
List blob scopes with filters + paging (#84b, api_minor >= 20).
Sourcepub async fn blob_fetch(
&mut self,
ticket: &str,
dest_path: &str,
) -> Result<BlobFetchResult, ClientError>
pub async fn blob_fetch( &mut self, ticket: &str, dest_path: &str, ) -> Result<BlobFetchResult, ClientError>
Fetch a mcpmesh/blob/1 ticket THROUGH the daemon (BLAKE3-verified), export to
dest_path; return the verified hash + byte length.
Sourcepub async fn blob_fetch_from(
&mut self,
ticket: &str,
dest_path: &str,
from: Vec<String>,
) -> Result<BlobFetchResult, ClientError>
pub async fn blob_fetch_from( &mut self, ticket: &str, dest_path: &str, from: Vec<String>, ) -> Result<BlobFetchResult, ClientError>
blob_fetch with ADDITIONAL sources to try when the ticket’s publisher
does not answer (#83).
Content addressing makes every recipient a potential source; a single-address ticket made that unusable, so a file shared with a room became unfetchable the moment the sender closed their laptop — even though others in the room already held the identical verified bytes.
from takes stable principals (eid:, b64u:) or paired nicknames — the same vocabulary
open_session takes, and naming a PERSON offers every device of theirs. They are tried in
order, after the publisher, so a live publisher costs nothing and an offline one costs
one dial timeout.
The bytes are BLAKE3-verified against the ticket’s hash whoever serves them, so an
alternate cannot substitute content. It can refuse: an alternate serves only hashes it has
republished into a scope that grants you (see blob_republish), and an ungranted one
answers a permission error and the fetch moves on. Every failure mode falls through, not
only an unreachable dial — a refusal, a missing hash, a reset, and a stalled transfer all
move to the next source. api_minor >= 47.
Sourcepub async fn blob_fetch_cancel(
&mut self,
hash: &str,
) -> Result<BlobFetchCancelResult, ClientError>
pub async fn blob_fetch_cancel( &mut self, hash: &str, ) -> Result<BlobFetchCancelResult, ClientError>
Stop every in-flight blob_fetch of hash (#172).
Send this on a DIFFERENT connection than the fetch it cancels. This client is one
request at a time — &mut self is borrowed until the fetch answers — so a cancel issued on
the same client can only run after the thing it would cancel is already over. The cancelled
fetch answers ERR_CANCELLED on its own connection.
cancelled: false means nothing was fetching that blob here. That is the honest answer to a
cancel that raced a fetch to completion, not an error.
Needs api_minor >= 44; below it the method is unknown.
Sourcepub async fn blob_grant(
&mut self,
scope: &str,
principal: &str,
) -> Result<(), ClientError>
pub async fn blob_grant( &mut self, scope: &str, principal: &str, ) -> Result<(), ClientError>
Grant a scope to a principal — any flat-namespace entry: a group name, a user_id,
or a nickname (the shared principal_set expansion).
The daemon acks; the ack body is discarded (a JSON-RPC error surfaces as
ClientError::Api). Granting a scope to your own user_id reaches ALL of that
person’s devices.
Sourcepub async fn subscribe(self) -> Result<StreamSubscription, ClientError>
pub async fn subscribe(self) -> Result<StreamSubscription, ClientError>
The TYPED subscribe upgrade: send Request::Subscribe (after which the connection
stops being request/response — see open_stream) and return a
StreamSubscription yielding StreamFrames. For raw frames (e.g. to tolerate frame
types newer than this crate), use open_stream("subscribe") instead.