Claude Agent SDK
Give a Claude Agent SDK agent your messaging channels, and let it answer incoming messages, in about ten minutes.
What you'll build
A Claude agent that texts a test number with VoiceLayer's message_send tool, then answers a simulated incoming text from your own process. You use @voicelayer/agents/claude, which gives the Claude Agent SDK the VoiceLayer channel tools over MCP. It adds no tools of its own, so they are the same in every framework.
Prerequisites
- Node.js 22 or later, and TypeScript 5.2 or later if you compile your own (the examples run with
tsx). The examples are ES modules with top-levelawait, and useawait using, which closes a connection when the file ends (TypeScript 5.2 or later withesnext.disposableinlib; or close it yourself withawait tools[Symbol.asyncDispose]()). - A test key in
VOICELAYER_API_KEY(vl_test_…): see Authentication and API keys. With a test key every send goes to a simulated carrier and costs nothing. - An Anthropic API key in
ANTHROPIC_API_KEY. - No phone number needed: with a test key, your workspace's free test sender stands in for one.
Install
npm install @voicelayer/agents @anthropic-ai/[email protected]Set the keys once. VoiceLayer's helpers read VOICELAYER_API_KEY themselves, so no example passes a key:
export VOICELAYER_API_KEY=vl_test_...
export ANTHROPIC_API_KEY=...
npm pkg set type=moduleGive your agent VoiceLayer's tools
Save this as agent.ts.
import { query } from '@anthropic-ai/claude-agent-sdk';
import { voicelayerMcp } from '@voicelayer/agents/claude';
// VoiceLayer's tools and none of Claude Code's own (files, shell, web): this agent only talks.
const options = { ...voicelayerMcp(), tools: [], systemPrompt: 'You answer customers in one short sentence.' };
for await (const message of query({ prompt: 'Text "Hello from VoiceLayer" to +15550101 over sms.', options })) {
if (message.type === 'result') console.log(message.subtype === 'success' ? message.result : `Stopped: ${message.subtype}`);
}
voicelayerMcp()returns options forquery(): the VoiceLayer MCP server and the list of its tools Claude may use without a permission prompt.tools: []removes Claude Code's built-in file, shell and web tools: this agent only talks.query()asks Claude to text+15550101, a reserved test number. Claude callsmcp__voicelayer__message_send.
Run it:
npx --yes tsx agent.tsThe agent texts the test number and prints its answer.
Answer incoming messages
Direct mode: your process answers the conversations VoiceLayer hands to code. Save this as serve.ts.
import { serveDirect } from '@voicelayer/agents/claude';
await serveDirect({ tools: [], systemPrompt: 'You answer customers in one short sentence.' });
serveDirect(options) answers incoming messages: each message in a conversation your code controls becomes one Claude turn, and the reply is sent back in that conversation. It keeps running until you stop the process. To let Claude use VoiceLayer's tools while it answers, spread ...voicelayerMcp() into the options.
npx --yes tsx serve.tsvoicelayer: answering incoming messages (Ctrl+C to stop)Test it without a person
With the server from the last step running, open a second terminal and simulate a person texting you. Save this as simulate.ts.
import { simulateInbound } from '@voicelayer/agents';
const { conversationId } = await simulateInbound({ text: 'What are your opening hours?' });
console.log(`Simulated a text from +15550101 in conversation ${conversationId}`);
npx --yes tsx simulate.tsThe first terminal prints voicelayer: replied in conversation …. Open Conversations in the dashboard and turn on test conversations to see the text and the reply. Both went to the simulated carrier.
Direct mode answers conversations your code controls (api). A new workspace's free test sender is yours by default; if the number you use has an agent that answers texts, hand the conversation to your code first.
To answer real people, swap the test key for a live key. Nothing else changes.
Handle errors
A missing key stops the program at start with a message that names VOICELAYER_API_KEY. A wrong key, or anything else the API refuses, comes back as an error with a stable code:
| Error | Status | How to fix |
|---|---|---|
unauthorized | 401 | Send Authorization: Bearer <key> with a secret key from the dashboard (Developers → API keys). Test keys start with vl_test_. |
insufficient_scope | 403 | Use a key with the scope named in details.required, or one whose restriction covers this channel or Binding. |
test_address_required | 422 | Use a reserved test address, or a live key to reach a real person. |
test_mode_only | 403 | Use a test key (vl_test_…) from the dashboard. |
test_limit_reached | 429 | Try again after midnight UTC, or ask for a higher test limit. |
If the first terminal never prints a reply, the conversation is answered by your workspace's agent, not by your code. See Hand off a conversation.
Next steps
- The MCP tools: what each tool does and which scope it needs.
- Test mode: more ways to simulate people.
- Python frameworks: the same setup in Python.