A vendor bot does not get a phone number of its own. It borrows the phone and mailbox of the bot that directs it, and it acts through tools, the same way your bot does. It never drives Allternit’s screens. Because the tools go through the same paths as your bot, the same rules hold:
  • Texts and calls only reach people who contacted the number first, or whom you added as contacts. Anyone who replied STOP stays blocked. See Calls and texts in threads.
  • Email is always a draft. send_email waits for your OK before anything is sent.
  • The daily text limit and carrier registration apply. If a tool is refused, the vendor bot gets one plain sentence saying why.
  • Every message says where it came from: “Sent by Claude via Gizzi”. Calls open with that same line in their brief.

Connect

  1. In Allternit, open the vendor bot and look under Use the phone. See Vendor bots on your bot’s phone.
  2. The vendor bot uses the phone and mailbox of the bot that directs it. If exactly one of your bots has a phone number, that is the one. With more than one, a developer sets it with the connector API below until the app offers a picker.
  3. Copy the connector address, which looks like https://mcp.allternit.com/mcp/bots/<vendor bot id>.
  4. Add it where the vendor lets you:
    • Claude: Settings → Connectors → Add custom connector. Paste the address and sign in to Allternit when asked.
    • ChatGPT: turn on developer mode, then add the address as a connector and sign in to Allternit when asked.
    • Grok: Grok has no custom connectors, so Gizzi relays instead: ask Gizzi to pass the message to your bot.
Under Use the phone you can see each app that has used the connector and when. Revoke cuts one off at once: its next call is refused and any event subscriptions it made end. The address works from anywhere. Claude and ChatGPT reach mcp.allternit.com, which is hosted by Allternit, not by your computer. Your computer doesn’t need to be reachable from the internet. When a call comes in, Allternit checks the sign-in, finds the computer your bot lives on (Desktop or cloud computer), wakes it if it is asleep, and passes the call through. If that takes longer than about 25 seconds, or the computer is offline, the app gets a plain message, “Your Allternit computer is offline; it will be woken — try again in a minute”, and you can ask again.

What the vendor bot can do

Cards. In Claude and ChatGPT the results show as cards, not raw text: a thread card for list_threads and read_thread, a result card for send_text and start_call (it says the person contacted the bot first, or is a contact), an email card that reads “Waiting for the owner’s OK”, and a channel post card for post_message. A refused call still gets a card that says why nothing went out. The card takes its accent from the app it appears in (Claude, ChatGPT, Gemini or Hermes) and falls back to Allternit’s neutral look in any other app. No browser to sign in with? An agent that runs in its own sandbox with a shell can use the allternit-bot command instead. See Give a shell agent your bot’s phone with allternit-bot. Their view. A vendor bot sees only threads it is attached to, plus threads you share with it. Sharing a thread adds it to that view (a developer shares it with the connector API below). Your bot’s other conversations stay private. Everything works the same on Desktop, the ai.allternit.com phone layout and the m.allternit.com app.

For developers

Endpoint. POST /mcp/bots/:vendorBotId (MCP Streamable HTTP, JSON-RPC; stateless protocol 2026-07-28 with server/discover, plus initialize for 2025-11-25 and earlier, see protocol versions) is served in two places:
  • Public edge, cloud-api (cmd/allternit-cloud-api/src/routes/mcp_edge.rs), at api.allternit.com and mcp.allternit.com. Both hosts use the same paths, with no prefix change. This is what Claude and ChatGPT call. It also serves the agents server (POST /mcp/server, POST /mcp) and the .well-known documents below. The edge answers server/discover itself after checking the token, so a client learns versions, capabilities and instructions without waking the computer; every other method is forwarded. GET on a connector path answers 405 with Allow: POST after the token is checked.
  • Runtime, allternit-api (cmd/allternit-api/src/mcp_vendor_bots.rs, next to the agents server in mcp_agents.rs), for a signed-in session on the computer itself. The edge never sends the OAuth token here.
How the edge forwards.
  1. It verifies the Clerk token (signature, issuer, expiry) and then the sign-in model described under OAuth below: either a token for this connector with aud and its own scope, or a Clerk token with scope profile whose OAuth client you approved for this connector. Nothing is forwarded until all of that passes.
  2. It lists the token owner’s paired runtimes (connected ones first, then most recently seen) and calls POST /webhooks/mcp-edge/bots/:vendorBotId on the first. The call goes over the signed runtime relay (x-allternit-runtime-sig, x-allternit-runtime-ts, x-allternit-owner = the verified user, x-allternit-mcp-client = the OAuth client), and the relay wakes a sleeping computer. The runtime accepts only that signature, and only as the owner it is paired as; its own owner checks, revoked-client check and audit run unchanged.
  3. A runtime that doesn’t hold the bot answers 404 {"error":"not_found"} and the next runtime is tried. The runtime that answered is remembered for 10 minutes. If none holds the bot the caller gets the same 404 as for someone else’s bot id, so there is no way to probe for bot ids. The agents server uses only the best runtime.
  4. The whole attempt, including waking a computer, has a 25 second budget. When it is exceeded, or every candidate is offline or still waking, the answer is HTTP 200 with a JSON-RPC error carrying the caller’s request id: {"jsonrpc":"2.0","id":<id>,"error":{"code":-32000,"message":"Your Allternit computer is offline; it will be woken — try again in a minute."}}.
  5. A revoked connection comes back 401 with the WWW-Authenticate challenge (the edge adds it, because the relay drops runtime headers).
Until MCP_PUBLIC_URL is set on cloud-api, every edge route answers 503 {"error":"mcp_edge_not_configured"}. DNS for mcp.allternit.com and the reverse proxy in front of cloud-api are set up outside this repo. OAuth. Clerk is the authorization server, as for the agents server. Protected-resource metadata is per path (RFC 9728): GET /.well-known/oauth-protected-resource/mcp/bots/<id> returns
Why profile. Clerk can only issue its own scopes (openid, profile, email, public_metadata, …), not bots:act or agents:read, so the edge advertises profile and the sign-in is two checks: Clerk proves who you are, and your approval says which app may act on this bot. Clerk’s dynamic client registration is switched on for the instance (dynamic_oauth_client_registration, set through PATCH /v1/instance/oauth_application_settings), so registration_endpoint is https://allternit.com/__clerk/oauth/register and clients such as Claude, ChatGPT and Claude Code register themselves. Clerk also supports client ID metadata documents. Approval. A Clerk token with scope profile and a client id (azp / client_id) is accepted only when its owner approved that client for that target (bot:<vendorBotId>, or agents for /mcp). Without an approval the edge answers 403 and nothing is forwarded:
MCP_APPROVE_URL overrides the base of approve_url. Approvals are one row per owner, client and target (cloud migration 043_mcp_oauth_approvals.sql), so approving Claude for one bot approves nothing else. Routes (Clerk session or compute API key): Approving in the app. The approve_url opens /mcp/approve on ai.allternit.com (Desktop, the phone layout and the m.allternit.com app all show the same page; signed out, it asks you to sign in and brings you back). It names the app and what it gets (act as the bot through its phone, for example “Act as Raven through Gizzi’s phone”: the vendor bot’s name and the bot whose phone it uses, from GET /api/v1/vendor-bots/:id/connector botName and directingBotName; “your bot through its phone” until they load, or read your agents and runs) with Approve and Deny. After Approve it says to go back to Claude or ChatGPT and retry. Approved apps are listed, with Revoke, under Use the phone on each vendor bot and in Settings › Connected apps, each with the events it gets and Stop for one (Connected apps). A token minted for this connector (aud equal to the resource and scope bots:act, or agents:read for /mcp) still works without an approval. A token for another connector with our own scope is refused with 401; a token with neither our scope nor profile gets 403; both carry the WWW-Authenticate challenge. The caller must own the vendor bot’s execution binding (bot_execution_bindings.type = 'vendor'), otherwise the answer is 404. Gates. The tools call the paths the bot itself uses:
  • send_text is a message in the phone:<botE164>:<to> thread, sent by the sms transport to the cloud’s POST /api/v1/channels/sms/send. The cloud enforces opt-out, consent, registration state and the daily cap (ALLTERNIT_SMS_DAILY_CAP).
  • start_call calls POST /api/v1/phone/calls/outbound; 403 no_consent becomes a tool error.
  • send_email is send_email_for_user, which always queues a mailflare approval.
  • post_message calls the runtime’s channel-start path (channel_start::VendorStarter, registered at boot), so each platform’s rules, the bot’s channel policy and approvals apply. A refusal comes back as one plain sentence. See Starting conversations.
  • ask_bot is agent_send to the directing bot, so its approval flow applies.
Cards. The tools that have a card carry _meta.ui.resourceUri (SEP-1865 MCP Apps) and the server answers resources/list and resources/read for four resources, all text/html;profile=mcp-app with no network access: ui://allternit/vendor-thread.v1.html (list_threads, read_thread), vendor-result.v1.html (send_text, start_call), vendor-email.v1.html (send_email) and vendor-post.v1.html (post_message). They share one template (assets/vendor-bot-card.v1.html) with the kind filled in when the resource is read. The card renders the call’s structuredContent: the tool’s own answer plus request (the arguments echoed back, only to, text, purpose, subject, body, provider, target) and tool; a refusal is {refused:true, message, request, tool} while the result stays isError with the plain sentence for the model. The card styles itself from the host’s name in ui/initialize (hostInfo.name) using assets/vendor-bot-card-packs.json, a small table of the accents of the allternit-ai look packs (claude, chatgpt, gemini, hermes). Code: mcp_vendor_cards.rs. Audit. Every tools/call writes a vendor_bot_audit row: owner, vendor bot, directing bot, OAuth client, tool, a SHA-256 hash of the arguments (never the arguments), ok and a short result. Migration V220. Keys page API (signed-in owner, under /api): The connector needs no environment of its own on the runtime. On cloud-api, MCP_PUBLIC_URL (for example https://mcp.allternit.com/mcp) turns the public edge on and sets the aud it expects. MCP_OAUTH_ISSUER (default https://allternit.com/__clerk) is the authorization server it advertises. The runtime’s own MCP_PUBLIC_URL / MCP_OAUTH_ISSUER behave as before.