Public
Remixed
Public remote MCP: your agent phones you. BYO Vapi keys, no env.
Val Town is a collaborative website to build and scale JavaScript apps.
Deploy APIs, crons, & store data – all from the browser, and deployed in milliseconds.

dispatch-my-human-escape β€” autonomous voice agent with a phone-home escape hatch πŸ“ž

A public remote MCP server: connect it to any AI agent (Claude Code, Codex, claude.ai) and it dispatches an autonomous voice agent to phone your human, run the whole call itself, and report back β€” with an escape hatch. When the human asks something the agent wasn't briefed on, the agent phones you (the operator) live, mid-call, keeps chatting while it waits, and relays your answer when it lands. Same BYOK design as its siblings: no env vars, no server-side keys, no signup.

Three siblings, three control models:

  • call-my-human-no-middle-agent β€” puppet: you drive every word (full control, high latency).
  • call-my-human β€” autonomous: brief it once, it runs the call, you collect a structured report.
  • dispatch-my-human-escape (this one) β€” autonomous + async escape hatch: like dispatch, plus the agent can phone home to you mid-call.
your agent (operator, any MCP client)
   β”‚  dispatch_call({ objective, context, questions })
   β–Ό
this val ── POST api.vapi.ai/call (your key) ─▢ Vapi ── gpt-4o brain + Elliot voice ─▢ πŸ“± your human
   β–²  β”‚                                          β”‚
   β”‚  β”‚   ask_operator(question)  ◀───────────────   (off-brief? agent phones home, keeps talking)
   β”‚  └─▢ /tool (records it, returns instantly)  β”‚
   β”‚                                             β”‚
   └── await_report ─▢ needs_answer ─▢ provide_answer(text)
                                          └─▢ add-message into the live call ─▢ agent relays it

The loop (operator side)

  1. dispatch_call { objective, context?, questions?, number?, first_message?, caller_identity?, max_minutes? } β€” hand off the call.
  2. await_report { wait_seconds } β€” long-poll. Returns either:
    • needs_answer: true with { escalation: { question } } β€” the agent hit an off-brief question. Reply with provide_answer, then call await_report again.
    • done: true with { summary, objective_met, answers[], followups, transcript } β€” the call is over.
  3. provide_answer { text } β€” your answer is pushed into the live call and the agent announces + relays it in its own words.

Optional: listen (eavesdrop), say (barge in), get_status, end_call.

How the escape hatch works (async)

The agent has an ask_operator(question) tool. When the human asks something off-brief, it calls it. Our /tool endpoint records the question and returns instantly β€” so the agent doesn't go on hold; it keeps the conversation going. Meanwhile await_report surfaces the question to you. When you provide_answer, we push it into the live call via Vapi's add-message (with triggerResponseEnabled) β€” that lands in the agent's context (not raw TTS), so it says "oh β€” just heard back from my operator…" and weaves it in, staying in the loop for follow-ups.

In effect the escape hatch hands the phone agent your entire toolset: ask it "list my files" and you (the operator) run the tool and relay the result.

Connect

Two values from dashboard.vapi.ai: your private key and a phone number ID.

claude mcp add --transport http dispatch-escape \ "https://dcm31--019ec8e38fe975a29f23e932794ecdea.web.val.run/mcp?vapi_key=YOUR_KEY&phone_number_id=YOUR_PHONE_ID&human_number=%2B15551234567"

Then tell your agent "if you get stuck on the call, ask me." The operator just needs to keep calling await_report to catch escalations.

Raw MCP (no special tooling)

It's a standard MCP server β€” drive it from anything that speaks JSON-RPC over HTTP, exactly like Claude Code/Codex do:

BASE="https://dcm31--019ec8e38fe975a29f23e932794ecdea.web.val.run/mcp" CREDS="vapi_key=YOUR_KEY&phone_number_id=YOUR_PHONE_ID&human_number=%2B15551234567" curl -s "$BASE?$CREDS" -H 'content-type: application/json' \ -d '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"dispatch_call","arguments":{"objective":"Quick check-in"}}}'

Authorization: Bearer <vapi_key> is accepted in place of the vapi_key query param (and keeps the key out of the URL).

Security & privacy

  • Your Vapi key is the crown jewel. Read per request, never stored, never logged (only a hash is logged). Everything gates on it β€” without it, nobody can place calls or read your data. Rotate it if it ever leaks.
  • BYOK rides in the URL by default. URLs leak (server/proxy logs, history, referrers), so prefer the Authorization: Bearer header. And on a hosted instance you don't own, the val owner can see your key and transcripts in their logs/DB β€” so for anything real, remix this val and host your own (zero config). This is the single biggest privacy lever.
  • Stored data. Transcripts and structured reports live in the val's SQLite β€” unencrypted, no expiry β€” scoped by tenant = first 12 hex of SHA-256(your key), unguessable without the key. Remix for full custody.
  • Watch links are bearer capabilities. /?t=<tenant> shows the live transcript to anyone with the link (a 48-bit unguessable id). Don't share it for a sensitive call.
  • Webhook/tool auth is light. /webhook and /tool check a secret equal to your tenant id; /tool even allows a missing secret. An attacker would need your tenant id (only semi-exposed via watch links) to forge events β€” and could then only inject fake transcript lines or spurious escalations into your own feed, never place calls or read your key. Remixing + not leaking watch links closes this.
  • Prompt-injection surface. Whatever the human says flows into the agent's brain (and into escape-hatch questions). You're calling your own human, so risk is low β€” but don't wire the operator to take destructive, irreversible actions purely on phoned-in instructions.
  • Solid bits. TLS end to end; the val never touches raw audio (Vapi does the telephony, we only receive transcripts); all DB queries are tenant-scoped.
  • Consent. Only call numbers you have consent to call β€” automated calls without consent violate TCPA/FCC rules.

Files

  • main.ts β€” routes, per-tenant Vapi webhook, the async /tool escape-hatch endpoint, landing + live feed
  • tools.ts β€” the 7 tools (incl. provide_answer); builds the agent's system prompt from your brief
  • vapi.ts β€” Vapi client: native-model call creation with the ask_operator tool, endCall, analysis plan, and add-message
  • mcp.ts β€” minimal stateless streamable-HTTP MCP plumbing (no SDK)
  • db.ts β€” SQLite: calls, append-only events, and the escalations table

Notes

  • Brain: your Vapi account's bundled OpenAI model (gpt-4o default; &model= to override). Voice: Vapi-native Elliot (tuned for telephony). No extra keys for either.
  • The agent ends calls itself via endCall; max_minutes (default 10) is a hard cap.
  • Phone audio is narrowband (8kHz) β€” it sounds worse than the Vapi dashboard's web "test call." Free Vapi numbers add latency and have a daily outbound limit; import a Twilio number for reliability.