Quickstart • Features • How It Works • Configuration
An MCP server that gives your AI agent a phone number. Answer calls, dial out, edit code, call APIs, book meetings — all over voice.
Built on the Patter Voice AI SDK. Claude Code connects over Streamable HTTP or stdio and gets access to voice calling tools. During calls, the AI agent can read files, run commands, and search code in real time.
git clone https://github.com/PatterAI/patter-mcp
cd patter-mcp
npm install
cp .env.example .env # fill in your API keysnpm run dev # development
# or
npm run build && npm start # productionTwo transports are supported. HTTP (default) runs a long-lived server you connect to by URL; stdio lets an MCP client spawn the server as a subprocess (no port, no URL).
HTTP (default):
npm run dev # or: npm run build && npm start — serves Streamable HTTP at :3000/mcp
claude mcp add --transport http patter-mcp http://localhost:3000/mcpstdio: pass --stdio and the client launches the server itself:
claude mcp add patter-mcp -- npx -y getpatter-mcp --stdioSee stdio transport for OpenClaw and Hermes config.
Ask Claude:
"Call +15551234567 and ask them about their order status"
"Call this restaurant and ask if there's a table for 2 tonight"
"Show me the transcript from the last call"
| Tool | Description |
|---|---|
make_call |
Place an outbound call with an AI voice agent; waits for the call to end and returns the outcome + transcript |
call_third_party |
Call a third party with an autonomous task (e.g. restaurant reservation); waits and returns the transcript |
get_calls |
List all calls with status, duration, and cost |
get_transcript |
Get the full conversation transcript of a call |
end_call |
Hang up an in-progress call |
get_metrics |
Read latency / token / cost metrics |
configure_inbound |
Configure how inbound calls are answered |
Both make_call and call_third_party are completion-aware: they block until
the call reaches a terminal state and return a structured outcome
(answered / voicemail / no_answer / busy / failed), duration, transcript,
and cost — see How It Works.
During a phone call, the voice agent has access to a full Claude Code session via the Agent SDK. This means the agent can:
- Read, write, and edit files
- Run shell commands and tests
- Search codebases with Glob and Grep
- Create git commits
- Install dependencies
- Anything Claude Code can do interactively
| Claude Code | Patter MCP | Phone Calls | ||
|---|---|---|---|---|
|
You Claude Code / Desktop make_callcall_third_partyget_transcript
|
→ |
MCP Server Streamable HTTP :3000 Patter SDK Twilio + STT/TTS :8000 Claude Code Agent SDK (in-call) |
→ |
Outbound Call users & third parties Inbound Answer on your number |
patter-mcp leans on one SDK primitive for every outbound call:
const result = await phone.call({ to, agent, machineDetection, voicemailMessage, wait: true });
// result: { callId, outcome, status, durationSeconds, transcript, cost, metrics }-
Outbound (
make_call,call_third_party).call({ wait: true })blocks until the call hangs up (timeout-bounded by the SDK) and resolves with aCallResult. Every field comes from a real carrier signal:outcomeis the carrier-agnostic projection (answered/voicemailfrom answering-machine detection + media-stream end;no_answer/busy/failedstraight from the carrier status callback). patter-mcp maps that result directly into aCallRecord— there is no provisional id and no polling.Before 0.4.0 the outbound path stitched the carrier call together by hand with a
pending_<ts>provisional id that never matched the real carrier SID, so the lifecycle callbacks never correlated andcall_third_partypolled a record that stayedringinguntil it timed out. It is now functional end-to-end. -
Inbound. Inbound calls have no initiator to await, so the server-wide
onCallStart/onCallEndcallbacks wired onphone.serve(...)create and finalise their records. Those callbacks fire for every call but filter ondirectionand ignore outbound events (whichmakeCallowns). -
Teardown. On
SIGTERM/SIGINTthe server callsphone.disconnect()— closing the cloudflared tunnel, the WebSocket server, and any pendingcall({ wait: true })awaiters — so nothing is left running on exit.
1. Claude Code needs approval for a plan
2. → make_call({ to: "+39...", systemPrompt: "Describe the plan..." })
3. Phone rings, user answers
4. AI agent: "Hi, I have a plan for the auth refactor..."
5. User: "Show me the current auth.ts file"
6. → claude_code({ task: "read src/auth.ts" }) [Claude Code Agent SDK]
7. AI agent: "The file has 45 lines, it uses JWT tokens..."
8. User: "Fix the token expiration bug"
9. → claude_code({ task: "fix the token expiration bug in auth.ts" })
10. AI agent: "Done. I updated line 23 to use a 24h expiration..."
11. User: "Run the tests"
12. → claude_code({ task: "run the tests" })
13. AI agent: "All 42 tests passing."
14. Call ends → transcript returned to Claude Code
1. User: "Call the restaurant and ask if there's a table for 2 at 8pm"
2. → call_third_party({ to: "+39...", task: "ask for a table for 2 at 8pm" })
3. AI agent calls restaurant autonomously
4. Agent: "Buonasera, c'è un tavolo per due stasera alle 20?"
5. Restaurant: "Sì, abbiamo disponibilità"
6. → transcript returned to Claude Code
7. Claude: "The restaurant confirmed a table for 2 at 8pm tonight."
| Variable | Required | Description |
|---|---|---|
TWILIO_ACCOUNT_SID |
Yes | Twilio account SID |
TWILIO_AUTH_TOKEN |
Yes | Twilio auth token |
TWILIO_PHONE_NUMBER |
Yes | Your Twilio phone number (E.164) |
OPENAI_API_KEY |
Yes | OpenAI API key |
DEEPGRAM_API_KEY |
Yes | Deepgram STT key (for voice tools) |
ELEVENLABS_API_KEY |
Yes | ElevenLabs TTS key (for voice tools) |
MCP_PORT |
No | MCP server port (default: 3000) |
PATTER_PORT |
No | Patter server port (default: 8000) |
AGENT_SYSTEM_PROMPT |
No | Default system prompt for inbound calls |
AGENT_VOICE |
No | Default TTS voice (default: nova) |
cp .env.example .env
# Edit .env with your API keysThe embedded Patter server (HTTP + cloudflared tunnel) is expensive to boot. By default it starts lazily on the first tool call, so MCP sessions that never place a call pay zero startup cost. Four modes are supported:
| Mode | Trigger | When to use |
|---|---|---|
| Lazy (default) | No env var set | Local dev, Claude Code sessions where calls are occasional |
| Eager | PATTER_EAGER=1 |
CI smoke tests, demos where the first call must be instant |
| Stable tunnel | PATTER_TUNNEL_HOSTNAME=patter.example.com |
Long-running deployments — named cloudflared tunnel, stable webhook URL across restarts |
| Production webhook | WEBHOOK_URL=https://your.api/webhook |
Hosted deployments behind your own ingress, no tunnel |
npm start
# MCP server up immediately; Patter HTTP/tunnel boots on first make_call.The first make_call (or any other tool) triggers a one-time boot. Concurrent tool calls during boot coalesce on a single in-flight promise — no double-boot. If boot fails (e.g. tunnel handshake error), the next tool call retries cleanly.
PATTER_EAGER=1 npm startBoots the Patter server during MCP startup. Same behaviour as patter-mcp ≤ 0.2.x. Use when you need the first call to be instant or you want startup errors to surface immediately (rather than at first tool call).
PATTER_TUNNEL_HOSTNAME=patter.your-domain.com npm startPatter uses a named cloudflared tunnel with a stable hostname instead of the default quick tunnel (which generates a fresh *.trycloudflare.com URL on every restart). Required when you've configured a Twilio/Telnyx webhook to point at a fixed URL.
Prerequisites: a cloudflared tunnel created with cloudflared tunnel create patter and the corresponding DNS CNAME pointing to <tunnel-id>.cfargotunnel.com.
WEBHOOK_URL=https://your-domain.com/webhook npm startSkip cloudflared entirely. Use when the MCP server is deployed behind your own ingress (nginx, Caddy, k8s ingress, Fly.io, Railway, etc.) and the carrier webhook can reach it directly. The Patter SDK will not attempt to start a tunnel — it just listens on PATTER_PORT and trusts the URL you provided.
/health exposes the current lifecycle state:
{
"status": "ok",
"mode": "mcp",
"phone": "+15551234567",
"serverRunning": false, // ← true after first tool call (lazy mode)
"activeSessions": 1
}Edit ~/Library/Application Support/Claude/claude_desktop_config.json:
{
"mcpServers": {
"patter-mcp": {
"type": "http",
"url": "http://localhost:3000/mcp"
}
}
}Local MCP clients that prefer to spawn the server as a subprocess (rather
than connect to a URL) use the stdio transport. Launch it with the --stdio
flag (or MCP_TRANSPORT=stdio):
npx -y getpatter-mcp --stdio
# stdout carries the raw JSON-RPC stream; all logs go to stderr.In stdio mode no HTTP port is opened, so /health, the /inspector UI, and
Auth0 OAuth are unavailable — the subprocess boundary is the trust boundary, so
the client owns the process. Tools, resources, and lazy lifecycle behave
exactly as in HTTP mode.
Long-call timeout (important).
make_call/call_third_partyblock until the call ends, which can exceed a client's default MCP timeout. Raise the per-server timeout (see the snippets below) or those calls get killed mid-call — same as for the HTTP transport.
OpenClaw — ~/.openclaw/openclaw.json (JSON5). Set the server under
mcp.servers, raise the timeout above 20 s, and allowlist the MCP tools in the
sandbox (without alsoAllow the tools exist but the agent can't call them):
{
mcp: {
servers: {
"patter-mcp": {
transport: "stdio",
command: "npx",
args: ["-y", "getpatter-mcp", "--stdio"],
env: {
TWILIO_ACCOUNT_SID: "AC...",
TWILIO_AUTH_TOKEN: "...",
TWILIO_PHONE_NUMBER: "+15551234567",
OPENAI_API_KEY: "sk-...",
},
timeout: 120, // seconds — a phone call exceeds the ~20s default
},
},
},
tools: {
sandbox: {
tools: {
alsoAllow: ["bundle-mcp"], // or "patter__*"
},
},
},
}Hermes — ~/.hermes/config.yaml, under mcp_servers. Raise timeout
(default 120 s) for long wait:true calls:
mcp_servers:
patter-mcp:
command: npx
args: ["-y", "getpatter-mcp", "--stdio"]
env:
TWILIO_ACCOUNT_SID: "AC..."
TWILIO_AUTH_TOKEN: "..."
TWILIO_PHONE_NUMBER: "+15551234567"
OPENAI_API_KEY: "sk-..."
timeout: 600 # seconds — long calls can run minutesClaude Desktop — stdio variant of the config above:
{
"mcpServers": {
"patter-mcp": {
"command": "npx",
"args": ["-y", "getpatter-mcp", "--stdio"]
}
}
}npm run dev # Run with tsx (auto-restart)
npm run build # Build for production
npm start # Run built versionpatter-mcp ships in two transports:
-
HTTP (Streamable HTTP) — a long-lived server you connect to by URL (
http://localhost:3000/mcp). Best forgit clone && npm install && npm startplus the HTTP-transport config block shown above. Clients never spawn the process; they connect to the port. -
stdio — the server runs as a subprocess the client spawns via
npx -y getpatter-mcp --stdio, communicating over stdin/stdout. This is what local desktop MCP clients (OpenClaw, Hermes, Claude Desktop) expect. See stdio transport.
A couple of npx -y caveats worth knowing:
-
Cold start.
cloudflared(postinstall binary download) can push the firstnpx -yinto the 30s+ range. The embedded tunnel only boots on the firstmake_call(lazy lifecycle), so listing tools and dry runs stay fast, but the very first real call after a fresh install pays the download once. -
Supply-chain hygiene. Recent npm supply-chain attacks (Shai-Hulud, Sep 2025; mini-Shai-Hulud, May 2026) targeted MCP-adjacent packages. If you prefer not to resolve a fresh tarball on every launch,
git clone && npm installwith the HTTP transport keeps the trust boundary on the repo + your carrier credentials.
Pull requests are welcome. Please open an issue before submitting large changes.
MIT — see LICENSE.