{"_id":"@callmcp/driver-byok","name":"@callmcp/driver-byok","dist-tags":{"latest":"0.1.0"},"versions":{"0.1.0":{"name":"@callmcp/driver-byok","version":"0.1.0","description":"CallMCP BYOK driver: Twilio transport (calls/numbers/SMS) bridged to a bring-your-own-key realtime brain (OpenAI Realtime API today, Grok Voice Agent API wire-compatible adapter stubbed). driver_id: twilio_openai. Normative reference: SPEC.md at the repo ","license":"MIT","type":"module","main":"./dist/index.js","types":"./dist/index.d.ts","exports":{".":{"types":"./dist/index.d.ts","default":"./dist/index.js"}},"dependencies":{"twilio":"^5.3.4","ws":"^8.18.0","@callmcp/driver-interface":"0.1.0"},"devDependencies":{"@types/node":"^22.9.0","@types/ws":"^8.5.13","typescript":"^5.6.3","vitest":"^2.1.4"},"publishConfig":{"access":"public"},"scripts":{"build":"tsc -p tsconfig.json","dev":"tsc -p tsconfig.json --watch","test":"vitest run","test:watch":"vitest","typecheck":"tsc -p tsconfig.json --noEmit"},"_id":"@callmcp/driver-byok@0.1.0","_integrity":"sha512-DnOFF6kWIr2/hJE+GlZd7nIOkUzuQOqibsz3510l2VD1aESuMGpNt6fBz+T8OoPMPuXhKDnr1WgOyMqoQStmLw==","_resolved":"C:\\Users\\cgall\\AppData\\Local\\Temp\\60e4911a2a56fc4bbe3a1a25b8960c91\\callmcp-driver-byok-0.1.0.tgz","_from":"file:callmcp-driver-byok-0.1.0.tgz","_nodeVersion":"20.18.1","_npmVersion":"10.8.2","dist":{"integrity":"sha512-DnOFF6kWIr2/hJE+GlZd7nIOkUzuQOqibsz3510l2VD1aESuMGpNt6fBz+T8OoPMPuXhKDnr1WgOyMqoQStmLw==","shasum":"41c57b446c1f49cd7404dc400448fcb760de49dc","tarball":"https://registry.npmjs.org/@callmcp/driver-byok/-/driver-byok-0.1.0.tgz","fileCount":28,"unpackedSize":150597,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEYCIQCi0Kdy48nOnHnBEdE56YTHaHQRY/qH4ymYHQ4OXrZ15QIhAIdkkB1Uuez5eWH5LPBYZUeKScULrh68LzeybHX5U5Z+"}]},"_npmUser":{"name":"kaicmo","email":"connor@kaicalls.com"},"directories":{},"maintainers":[{"name":"kaicmo","email":"connor@kaicalls.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/driver-byok_0.1.0_1783643308497_0.5544917514175136"},"_hasShrinkwrap":false}},"time":{"created":"2026-07-10T00:28:28.364Z","0.1.0":"2026-07-10T00:28:28.669Z","modified":"2026-07-10T00:28:29.073Z"},"maintainers":[{"name":"kaicmo","email":"connor@kaicalls.com"}],"description":"CallMCP BYOK driver: Twilio transport (calls/numbers/SMS) bridged to a bring-your-own-key realtime brain (OpenAI Realtime API today, Grok Voice Agent API wire-compatible adapter stubbed). driver_id: twilio_openai. Normative reference: SPEC.md at the repo ","license":"MIT","readme":"# @callmcp/driver-byok\n\n`driver_id: twilio_openai`. A CallMCP driver composed from two independent\npieces you bring your own keys for:\n\n- **Transport** — Twilio (Voice, Programmable Messaging, Phone Numbers).\n- **Brain** — OpenAI's Realtime API (speech-to-speech, GA today). A second\n  brain (xAI's Grok Voice Agent API, documented as OpenAI-Realtime-wire-\n  compatible) is stubbed as a deliberate TODO — see `src/brain/adapter.ts`.\n\nNormative reference: [`SPEC.md`](../../SPEC.md) at the repo root. If this\nREADME and `SPEC.md` disagree, `SPEC.md` wins.\n\n## Architecture\n\n```\n caller ──PSTN──▶ Twilio number\n                     │\n                     ▼\n           Twilio Voice webhook (POST)\n           handleVoiceWebhook(callSid) → TwiML <Connect><Stream url=\"wss://…\">\n                     │\n                     ▼\n     Twilio Media Streams WebSocket (8kHz mu-law, bidirectional)\n                     │\n                     ▼\n        attachMediaStream(ws) → RealtimeBridge\n                     │  (audio in both directions, g711_ulaw, no resampling)\n                     ▼\n         OpenAI Realtime API WebSocket session\n         (wss://api.openai.com/v1/realtime) — tool calls, transcript\n         events, and barge-in all flow back through the same bridge.\n```\n\nThis package does **not** run its own HTTP/WebSocket server. The host\nprocess (`@callmcp/server`, or anything else embedding this driver) owns the\nserver; this driver exposes two integration points:\n\n- `BYOKDriver.handleVoiceWebhook({ callSid })` → returns a TwiML XML string.\n  Mount this at the URL you pass as `voiceWebhookUrl`, parse Twilio's\n  form-encoded POST body for `CallSid` yourself, and return the string with\n  `Content-Type: text/xml`.\n- `BYOKDriver.attachMediaStream(ws)` → wires a `RealtimeBridge` onto a raw\n  `ws.WebSocket`. Mount this at the URL you pass as `mediaStreamUrl` and hand\n  it the WebSocket from your server's upgrade handler.\n\nEverything else is the normal 11-method `Driver` interface from\n`@callmcp/driver-interface`.\n\n## Required configuration\n\n| Field (constructor) | Typical env var | Required | Notes |\n|---|---|---|---|\n| `twilioAccountSid` | `TWILIO_ACCOUNT_SID` | yes | |\n| `twilioAuthToken` | `TWILIO_AUTH_TOKEN` | yes | |\n| `twilioFromNumber` | `TWILIO_FROM_NUMBER` | recommended | default caller ID; `make_call`/`send_sms` can override per-call via `from` |\n| `openaiApiKey` | `OPENAI_API_KEY` | yes | |\n| `openaiModel` | `OPENAI_REALTIME_MODEL` | no | defaults to `gpt-realtime`; set to `gpt-realtime-2.1-mini` for the cheaper tier |\n| `voiceWebhookUrl` | `CALLMCP_BYOK_VOICE_WEBHOOK_URL` | yes | public `https://` URL routed to `handleVoiceWebhook` |\n| `mediaStreamUrl` | `CALLMCP_BYOK_MEDIA_STREAM_URL` | yes | public `wss://` URL routed to `attachMediaStream` |\n| `statusCallbackUrl` | `CALLMCP_BYOK_STATUS_CALLBACK_URL` | no | Twilio call-status webhook, informational only |\n| `defaultInstructions` | — | no | fallback agent instructions when no `agentConfigResolver` is wired |\n| `agentConfigResolver` | — | no | resolves `make_call`'s `agent_config_ref` into brain session config — see note below |\n| `toolCallHook` | — | no | routes mid-call tool invocations through your own approval-aware logic |\n\nThese match the naming already used in `examples/claude-desktop-byok.json`\nat the repo root (`TWILIO_ACCOUNT_SID`, `TWILIO_AUTH_TOKEN`,\n`TWILIO_FROM_NUMBER`, `OPENAI_API_KEY`). The `CALLMCP_BYOK_*` webhook/stream\nURLs aren't in that example yet because they depend on wherever\n`@callmcp/server` is actually deployed — set them to your public\ndomain/tunnel URL.\n\n### Why `agentConfigResolver` exists\n\nSPEC Appendix A puts agent-configuration tools (`create_agent`, prompt/voice\ntuning) explicitly out of v0 scope — backend config models diverge too much\nto unify honestly. `make_call`'s `agent_config_ref` is defined as \"opaque\"\n(SPEC §1.4). This driver doesn't invent its own agent-config storage on top\nof that; it just gives you a resolver hook so your host application's own\nconfig store can turn `agent_config_ref` into concrete instructions/tools\nfor the OpenAI Realtime session. If you don't need per-call configuration,\nskip it and set `defaultInstructions` once.\n\n### Why `toolCallHook` exists\n\nIf the agent's toolset includes something that would itself contact a third\nparty (place another call, send a message), that tool's implementation is\nresponsible for checking CallMCP's approval/allowlist state (SPEC §3) before\nacting — and that state is server-core, not something this driver package\nhas visibility into (see `@callmcp/driver-interface`'s README). `toolCallHook`\nis the seam: wire it to whatever your host process uses to check/request\napproval, and `RealtimeBridge` guarantees every brain-originated tool call is\nround-tripped through it before being settled back to the brain.\n\n## Usage sketch\n\n```ts\nimport { BYOKDriver } from \"@callmcp/driver-byok\";\nimport { createServer } from \"node:http\";\nimport { WebSocketServer } from \"ws\";\n\nconst driver = new BYOKDriver({\n  twilioAccountSid: process.env.TWILIO_ACCOUNT_SID!,\n  twilioAuthToken: process.env.TWILIO_AUTH_TOKEN!,\n  twilioFromNumber: process.env.TWILIO_FROM_NUMBER,\n  openaiApiKey: process.env.OPENAI_API_KEY!,\n  voiceWebhookUrl: \"https://your-domain.example.com/callmcp/byok/voice\",\n  mediaStreamUrl: \"wss://your-domain.example.com/callmcp/byok/media-stream\",\n});\n\nconst httpServer = createServer(async (req, res) => {\n  if (req.url === \"/callmcp/byok/voice\" && req.method === \"POST\") {\n    const body = await readFormBody(req); // your own form-decoder\n    res.writeHead(200, { \"Content-Type\": \"text/xml\" });\n    res.end(driver.handleVoiceWebhook({ callSid: body.CallSid }));\n    return;\n  }\n  res.writeHead(404).end();\n});\n\nconst wss = new WebSocketServer({ server: httpServer, path: \"/callmcp/byok/media-stream\" });\nwss.on(\"connection\", (ws) => driver.attachMediaStream(ws));\n\nhttpServer.listen(3000);\n\n// Elsewhere, wired into the CallMCP server core's `make_call` tool handler\n// (after approval gating has already happened — this driver never checks\n// approval itself):\nawait driver.makeCall({ to: \"+14155551234\", approval_id: \"a_9f2c...\" });\n```\n\n## Per-minute cost math\n\nSourced from `workspace/research/r2-transports.md` (Twilio) and\n`workspace/research/r3-realtime-brains.md` (OpenAI Realtime), both dated\n2026-07-09 against each provider's own pricing pages.\n\n**Transport (Twilio):**\n\n| Component | Rate |\n|---|---|\n| Voice, outbound (`make_call`) | $0.014/min |\n| Voice, inbound (receive) | $0.0085/min |\n| Media Streams add-on (bidirectional) | $0.004/min |\n| SMS (Programmable Messaging) | $0.0083/message |\n| Number rental | ~$1.00/mo local, ~$2.00/mo toll-free |\n\nA typical outbound call: **$0.014 + $0.004 = $0.018/min transport.**\n\n**Brain (OpenAI Realtime, `gpt-realtime-2.1`):**\n\nToken pricing: audio input $32/1M tokens, audio output $64/1M tokens.\nUser audio ≈ 600 tokens/min, assistant audio ≈ 1,200 tokens/min. For a\nmixed-duplex minute (~50% each speaking):\n\n```\ninput:  600  × $32/1M  ≈ $0.0192\noutput: 1200 × $64/1M  ≈ $0.0768\n                          -------\nmixed-duplex minute      ≈ $0.048  (uncached)\n```\n\nReal-world measured sessions (HackerNoon's 4,000-session dataset, cited in\nthe research doc) land at **$0.18–$0.46/min uncached, $0.05–$0.10/min with\nprompt caching** — caching matters a lot here because tool schemas and\nsystem instructions are re-sent on every turn otherwise. The cheaper\n`gpt-realtime-2.1-mini` tier runs ~40% of flagship audio-token cost\n(≈$0.015–0.03/min).\n\n**All-in per minute (this driver's default configuration, `gpt-realtime-2.1`, cached):**\n\n```\n  Twilio transport   $0.018/min\n+ OpenAI brain        $0.05–0.08/min  (cached, mixed-duplex)\n-------------------------------------\n= ~$0.07–0.10/min outbound voice\n```\n\nSwap to `gpt-realtime-2.1-mini` for roughly **$0.03–0.05/min all-in**\ninstead. Recording ($0.0025–0.004/min per various Twilio community pricing\nreferences — not independently re-verified against Twilio's own recording\npricing page while writing this driver, treat as approximate) and SMS\n($0.0083/message) are additive, not included in the per-minute voice figure\nabove.\n\n## Compliance: KYC and A2P 10DLC, surfaced honestly\n\nThis driver does not hide Twilio's own compliance gates behind a generic\nerror:\n\n- **`buy_number`** — international numbers and several US number types\n  require a Twilio **Regulatory Bundle** (identity/address verification)\n  before purchase succeeds. `mapTwilioError` (`src/transport/twilio.ts`)\n  recognizes Twilio's regulatory/bundle-shaped errors and surfaces them as\n  SPEC §5.5 `KYC_REQUIRED`, with `typical_turnaround` and a link to Twilio's\n  own regulatory-compliance console — not a bare 400.\n- **`send_sms`** at volume — **A2P 10DLC** brand/campaign registration is\n  required for sustained US long-code SMS traffic. Per Twilio's own vetting\n  FAQ this currently runs **10–15 business days** (nominally \"up to 5\n  business days\" but backlog has pushed it out), plus a $15 campaign\n  verification fee. Low-volume/unregistered sends may work initially but are\n  subject to carrier filtering over time. **This gate does not affect\n  voice-only use of this driver** — `make_call` has no A2P dependency.\n\nBoth gaps are also declared in `known_degradations` in\n`src/manifest.ts` / `callmcp.manifest.json` per SPEC §6.1 — the manifest is\nthe durable record, this README is the human-readable summary of it.\n\n## What's not implemented (v1)\n\n- `send_sms` with `channel: whatsapp` or `channel: rcs` — Twilio supports\n  both, neither is wired into this driver yet (`supports_whatsapp: false`,\n  `supports_rcs: false` in the manifest, not silently dropped).\n- `grokAdapter` (`src/brain/adapter.ts`) — xAI's Grok Voice Agent API is\n  documented as OpenAI-Realtime-wire-compatible (same event vocabulary,\n  swap the base URL and key), but this repo hasn't verified that against a\n  live session, so it's a real thrown-error stub with the exact\n  implementation sketch left in a comment, not a silent no-op.\n- Explicit caller-side barge-in detection in `RealtimeBridge` — OpenAI's\n  server-side `turn_detection` already interrupts generation brain-side on\n  `input_audio_buffer.speech_started`, but this bridge doesn't yet listen\n  for that event to also clear Twilio's outbound buffer client-side (see\n  `RealtimeBridge.interruptForBargeIn`, currently exposed but not\n  auto-wired).\n\n## Development\n\n```bash\npnpm install\npnpm --filter @callmcp/driver-byok run build\npnpm --filter @callmcp/driver-byok run test\n```\n\n`test/driver.test.ts` mocks the Twilio SDK client and the brain adapter\nfactory — no real Twilio or OpenAI calls, no spend, safe to run in CI.\n","readmeFilename":"README.md","_rev":"1-6d745c58f4013793b5f5e014369d161d"}