Declaring capabilities
Naming them
Capabilities are read by a human under time pressure, at the exact moment they are deciding whether to trust a device. Two rules follow:- Group by consequence, not by function.
notes.deleteis one capability even if three methods can delete.notes.rpcCallis not a capability, it is a bypass. - Split when the answer could differ. If someone might reasonably say yes to reading and no to writing, those are two capabilities.
Policy: what the host will allow at all
capabilities says what exists. permissions says what may ever be granted,
and it is evaluated before your approval hook runs — a bug in that hook
cannot hand out more than the policy permits.
low, and every high-risk capability forces a human decision regardless of
what pairing.approve returns.
Approving a pairing
true grants everything requested; an array or { caps } grants a
subset; false refuses. Compare the SAS digits on both screens — that
comparison is the entire defense against a machine-in-the-middle during
pairing, and skipping it silently gives that defense up.
pairing.autoApprove: true exists for development. It is capped by
maxAutoGrantRisk, so it cannot silently grant write access, but it should
never reach production.
Exposing methods
The handler context
ctx.deviceId— which paired device is calling. Use it for per-device state.ctx.requestId— correlates with the client’s request.ctx.signal— aborts when the client cancels or the session drops. Long handlers should check it.ctx.emitProgress(json)— progress events for the same request.ctx.log— a logger pre-bound with device, method and request id.
idempotent: true is a promise you are making to the client: it may replay this
call after a reconnect without asking you. Do not set it on anything that
appends, charges, or sends.
Events
Per-use consent
A capability markedconfirmEachUse: true is refused outright unless the host
supplies a prompt:
"session" lasts until that device disconnects; "always" until the TTL
expires or the grant is revoked. An unanswered prompt is a refusal, not a hang —
which is why the timeout defaults to 60 seconds rather than infinity.
Calling from the client
capability_denied error rather than
returning an empty result — see Error Codes. Handle it as a
UI state (“this device is not allowed to do that”), not as a network failure.
Next
Permissions concept
The model behind the policy
API Reference
Every method and option
Production Checklist
Before other people run this