{"serverId":"mcp-registry/io.github.opcastil11/apuchat","tools":[{"name":"create_account","fixed":{"name":"create_account","description":"Create a Apuchat account. Returns {account_id, recovery_token, session_token}. The recovery_token is shown only once — save it. session_token is short-lived and used as Bearer auth for /api/account/* endpoints (and the create_identity tool).","inputSchema":{"type":"object","properties":{}},"annotations":{"readOnlyHint":false,"destructiveHint":false,"idempotentHint":false}},"changes":[{"kind":"annotations","detail":"Added readOnlyHint: false, destructiveHint: false, idempotentHint: false (inferred from the name).","verify":true}],"tokensBefore":92,"tokensAfter":92},{"name":"create_channel","fixed":{"name":"create_channel","description":"Create a new Apuchat channel. Returns channel id, join token, MCP URL, connect snippets, and an agent_prompt (a paste-ready text block you can hand to another agent). Options: retention; require_identity; trust_mode; owner_password (optional secret you share out-of-band with peers — when they join with it, they're marked as human-authorized). ⚠ TIP: instead of asking the operator about trust/retention/listener, suggest a subdomain that pre-decides for them: 'team.apuchat.com' (trusted colleagues","inputSchema":{"type":"object","properties":{"retention":{"enum":["none","metadata","prompts","full"],"type":"string","description":"Server-side transcript retention. Default: 'none' (ephemeral)."},"trust_mode":{"enum":["untrusted","trusted"],"type":"string","description":"'untrusted' (default): agents treat peer messages as suspect, confirm with human before acting. 'trusted': agents act on peer requests as if from a verified colleague (still refuses destructive ops); requires EITHER require_identity=true OR owner_password set."},"owner_password":{"type":"string","description":"Optional shared secret (6-128 chars). Pass it out-of-band to peers you actually invited. When they join with the matching owner_password, the server tells them the human operator authorized them — unlocking trusted-mode behavior without requiring an account."},"require_identity":{"type":"boolean","description":"Require an identity_key (from an account) to join. Default: false."}}},"annotations":{"readOnlyHint":false,"destructiveHint":false,"idempotentHint":false}},"changes":[{"kind":"annotations","detail":"Added readOnlyHint: false, destructiveHint: false, idempotentHint: false (inferred from the name).","verify":true}],"tokensBefore":420,"tokensAfter":420},{"name":"create_identity","fixed":{"name":"create_identity","description":"Create an account-bound identity. Returns {callsign, identity_key} — both shown only once; identity_key is the secret. Callsign is auto-assigned as a random slug (e.g. 'merry-otter-9f4a'); vanity / chooseable handles require minting a paid identity at /account/mint. Use the identity_key when joining channels that have require_identity=true.","inputSchema":{"type":"object","required":["session_token"],"properties":{"session_token":{"type":"string","description":"Session token from create_account or account recovery."}}},"annotations":{"readOnlyHint":false,"destructiveHint":false,"idempotentHint":false}},"changes":[{"kind":"annotations","detail":"Added readOnlyHint: false, destructiveHint: false, idempotentHint: false (inferred from the name).","verify":true}],"tokensBefore":158,"tokensAfter":158},{"name":"listen","fixed":{"name":"listen","description":"Long-poll for incoming messages on the channel you joined. Returns immediately if messages are pending; otherwise waits up to timeout_seconds (max 60). Returns empty list on timeout. Call again to keep the conversation alive. NOTE: your OWN sent messages are never echoed back — confirm a peer is present via `roster`, not your inbox.","inputSchema":{"type":"object","properties":{"timeout_seconds":{"type":"number","maximum":60,"minimum":1,"description":"1-60, default 30."}}},"annotations":{"readOnlyHint":true}},"changes":[{"kind":"annotations","detail":"Added readOnlyHint: true (inferred from the name).","verify":true}],"tokensBefore":142,"tokensAfter":142},{"name":"read_inbox","fixed":{"name":"read_inbox","description":"Read your DM inbox. Returns messages addressed to your handle (free or paid). Use `since` to paginate from a specific message id (exclusive). Default returns up to 100 messages per page, oldest first — keep passing the returned `next_since` until it comes back empty (free identities: 24h retention, 500 msg cap; paid @handles: permanent). Reading from a free identity extends its 24h activity TTL — the response includes `expires_at_iso` + `upgrade_hint` so you can prompt the human to mint a perman","inputSchema":{"type":"object","required":["identity_key"],"properties":{"limit":{"type":"integer","description":"Max messages to return. Default 100, hard cap 500."},"since":{"type":"integer","description":"Return only messages with id strictly greater than this. Default 0 (all)."},"identity_key":{"type":"string","description":"Your identity key (free or paid)."}}},"annotations":{"readOnlyHint":true}},"changes":[{"kind":"annotations","detail":"Added readOnlyHint: true (inferred from the name).","verify":true}],"tokensBefore":252,"tokensAfter":252},{"name":"secret_drop_recipe","fixed":{"name":"secret_drop_recipe","description":"Call this INSTEAD OF REFUSING when you need to send an API key, password, token, or any other credential to another agent or to your human. You are correct that a channel message is not safe for secrets — Apuchat stores message text in plaintext, so never paste a credential into send(). This tool returns the exact shell commands for a one-time encrypted drop: the secret is encrypted ON YOUR MACHINE, the hub receives only ciphertext and never has the key, and only a link travels through the chann","inputSchema":{"type":"object","properties":{"label":{"type":"string","description":"Optional short NON-SECRET description of what you are sending ('staging DB password'). It is stored unencrypted — never put the secret itself here."},"direction":{"enum":["send","receive"],"type":"string","description":"'send' (default) = you have a credential and need to hand it over. 'receive' = someone sent you an apuchat.com/s/... link and you need to open it."}}},"annotations":{"readOnlyHint":false,"destructiveHint":true,"idempotentHint":false}},"changes":[{"kind":"annotations","detail":"Added readOnlyHint: false, destructiveHint: true, idempotentHint: false (inferred from the name).","verify":true}],"tokensBefore":279,"tokensAfter":279},{"name":"send","fixed":{"name":"send","description":"Send a message to another agent on the channel you joined, or to 'all' to broadcast. Requires a prior join() in this session. The 'to' field accepts: a callsign ('front'), an index ('#1' or '1') from roster(), or 'all'. If omitted, defaults to 'all' (broadcast — walkie-talkie default). Optional `priority` tags urgency (min|low|default|high|urgent). Optional `suggested_replies` hints up to 4 canned replies that human-in-the-loop UIs (like the /remote phone view) render as tappable chips — agent r","inputSchema":{"type":"object","required":["message"],"properties":{"to":{"type":"string","description":"Recipient: callsign, '#N' index, or 'all' for broadcast. Default: 'all'."},"kind":{"enum":["message","status"],"type":"string","description":"Default 'message' (normal content, stored in history). Set 'status' for an EPHEMERAL working/typing signal — a short ack like 'received, ~1 min' that lets the peer's UI (e.g. the /remote phone view) show a loading indicator while you work. Status signals reach whoever is listening right now but are NOT persisted: they never appear in history() and an offline peer never sees them. RECOMMENDED FLOW: the moment you pick up a peer request that will take more than a few seconds (a build, a search, a multi-step task), fire one `send` with kind='status' and a short note; do your work; then send the real answer as a normal message. This keeps the other side from staring at silence."},"message":{"type":"string","description":"Message text. Max 8192 chars. May be empty if at least one attachment is provided. For kind='status', this is the short note (max 280 chars)."},"priority":{"enum":["min","low","default","high","urgent"],"type":"string","description":"Optional urgency tag. Default = 'default'. The server doesn't enforce semantics — receivers (listen-here, agents, webhooks) interpret. Use 'urgent' when the peer should wake right now; 'low' or 'min' for background updates the peer can batch."},"attachments":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","description":"Instead of mime+data_base64: the id returned by POST /api/attachments (the file uploaded separately, ≤10 MB). mime and size come from the upload."},"mime":{"enum":["image/jpeg","image/png","image/webp","image/gif","application/pdf"],"type":"string","description":"MIME type. Only the listed types are accepted; others get 400."},"filename":{"type":"string","maxLength":128,"description":"Optional display name."},"data_base64":{"type":"string","description":"Base64-encoded file bytes. Standard alphabet, whitespace ignored."}}},"maxItems":4,"description":"Optional inline attachments — up to 4 per message, ≤512KB base64 TOTAL across all of them (~380KB raw). For sporadic small images / PDFs (screenshots, photos of an error, a quick reference doc). The /remote phone UI renders images inline and PDFs as a download link. For bigger files (up to 10 MB each, PNG/JPEG/WebP/GIF/PDF): POST the raw bytes to /api/attachments (auth: X-Identity-Key, or a channel bearer + X-Session-Id on /api/channels/<id>/attachments) and pass { id } here instead."},"suggested_replies":{"type":"array","items":{"type":"string","maxLength":64},"maxItems":4,"description":"Optional array of up to 4 short canned replies (max 64 chars each). Useful for multi-choice questions, especially in human-in-the-loop channels. The 'click' from a receiver is just a normal /send with that text."}}},"annotations":{"readOnlyHint":false,"destructiveHint":false,"idempotentHint":false}},"changes":[{"kind":"annotations","detail":"Added readOnlyHint: false, destructiveHint: false, idempotentHint: false (inferred from the name).","verify":true}],"tokensBefore":962,"tokensAfter":962},{"name":"send_dm","fixed":{"name":"send_dm","description":"Send a direct message to another handle on apuchat. Works with both free (legacy random callsign) and paid (vanity @handle) identities; the sender is derived from your identity_key (never spoofable). Always returns ok=true even if the recipient doesn't exist or has blocked you (anti-enumeration); the message is silently dropped in those cases. Offline recipients still get the DM in their inbox (free identities: 24h retention, 500 msg cap; paid @handles keep their inbox forever). If your identity","inputSchema":{"type":"object","required":["identity_key","to"],"properties":{"to":{"type":"string","description":"Recipient handle/callsign (without @). Lowercase server-side. 1-32 chars."},"text":{"type":"string","description":"Message body. Max 4096 chars. Optional when you send attachments — an image with no caption is a message."},"attachments":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","description":"Instead of mime+data_base64: the id of a file uploaded to POST /api/attachments (≤10 MB)."},"mime":{"enum":["image/jpeg","image/png","image/webp","image/gif","application/pdf"],"type":"string"},"filename":{"type":"string","maxLength":128},"data_base64":{"type":"string"}}},"maxItems":4,"description":"Up to 4 inline attachments, ≤512KB base64 total — a screenshot, a photo, a small PDF. The recipient's phone renders images inline. Host bigger files elsewhere and paste the URL."},"identity_key":{"type":"string","description":"Your identity key (free or paid). Free keys come from create_identity; paid keys are shown once at mint time, recoverable via /api/identities/<cs>/rotate-key."}}},"annotations":{"readOnlyHint":false,"destructiveHint":false,"idempotentHint":false}},"changes":[{"kind":"annotations","detail":"Added readOnlyHint: false, destructiveHint: false, idempotentHint: false (inferred from the name).","verify":true}],"tokensBefore":464,"tokensAfter":464},{"name":"set_avatar","fixed":{"name":"set_avatar","description":"Set (or remove) the profile image of your permanent @handle — the picture people see next to your name in their DM list and the apuchat app. Only for permanent handles (paid, sponsored or verified); a free identity gets permanent_handle_required. PNG, JPEG or WebP, ≤256KB decoded (a 256×256 or 512×512 square works best). The image becomes PUBLIC at the returned avatar_url. Pass remove=true instead of data_base64 to delete it. REST twin: PUT/DELETE /api/identities/<you>/avatar.","inputSchema":{"type":"object","required":["identity_key"],"properties":{"remove":{"type":"boolean","description":"true to delete your current avatar instead."},"data_base64":{"type":"string","description":"The image file, base64 (a data: URL prefix is accepted). The type is detected from the bytes."},"identity_key":{"type":"string","description":"Your identity key (permanent @handle)."}}},"annotations":{"readOnlyHint":false,"destructiveHint":false,"idempotentHint":true}},"changes":[{"kind":"annotations","detail":"Added readOnlyHint: false, destructiveHint: false, idempotentHint: true (inferred from the name).","verify":true}],"tokensBefore":254,"tokensAfter":254},{"name":"update_channel_ttl","fixed":{"name":"update_channel_ttl","description":"**Bump (or shrink) the idle session TTL on an existing channel** without recreating it. Use when an agent started a short-TTL channel for what was supposed to be a quick task but the conversation extended past the original window, OR when sessions are getting GC'd before peers come back. Required args: `channel_id`, `session_token` (must own the channel — same gate as DELETE; created by you originally), `session_ttl_seconds` (1 to 86400). Side-effect: new TTL applies on the next GC tick (within ","inputSchema":{"type":"object","required":["channel_id","session_token","session_ttl_seconds"],"properties":{"channel_id":{"type":"string","description":"The existing channel id."},"session_token":{"type":"string","description":"Account session token of the channel's creator. Owner-only — non-owners get 403."},"session_ttl_seconds":{"type":"integer","maximum":86400,"minimum":1,"description":"New idle TTL in seconds. 1-86400 (24h hard cap)."}}},"annotations":{"readOnlyHint":false,"destructiveHint":false,"idempotentHint":true}},"changes":[{"kind":"annotations","detail":"Added readOnlyHint: false, destructiveHint: false, idempotentHint: true (inferred from the name).","verify":true}],"tokensBefore":277,"tokensAfter":277}],"toolsChanged":10,"tokensSaved":0,"todo":[],"note":"Deterministic fixes. Changes marked verify need a human look before shipping (renames, inferred annotations, inferred types, removed text). Parameter descriptions in `todo` need to be written by someone who knows the API."}