# Claude Agent SDK

> Give a Claude Agent SDK agent your messaging channels, and let it answer incoming messages, in about ten minutes.

Source: https://docs.vlayers.ai/developers/frameworks/claude

Kind: Tutorial

## 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](https://docs.vlayers.ai/developers/mcp) 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-level `await`, and use `await using`, which closes a connection when the file ends (TypeScript 5.2 or later with `esnext.disposable` in `lib`; or close it yourself with `await tools[Symbol.asyncDispose]()`).
- A test key in `VOICELAYER_API_KEY` (`vl_test_…`): see [Authentication and API keys](https://docs.vlayers.ai/get-started/authentication). 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](https://docs.vlayers.ai/developers/test-mode#your-free-test-sender) stands in for one.

## Install

```bash
npm install @voicelayer/agents @anthropic-ai/claude-agent-sdk@0.3
```

Set the keys once. VoiceLayer's helpers read `VOICELAYER_API_KEY` themselves, so no example passes a key:

```bash
export VOICELAYER_API_KEY=vl_test_...
export ANTHROPIC_API_KEY=...
npm pkg set type=module
```

## Give your agent VoiceLayer's tools

Save this as `agent.ts`.

```ts title="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 for `query()`: 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 calls `mcp__voicelayer__message_send`.

Run it:

```bash
npx --yes tsx agent.ts
```

The 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`.

```ts title="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.

```bash
npx --yes tsx serve.ts
```

```text
voicelayer: 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`.

```ts title="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}`);
```

```bash
npx --yes tsx simulate.ts
```

The 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](https://docs.vlayers.ai/developers/handoff) 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`](https://docs.vlayers.ai/errors/unauthorized) | 401 | Send `Authorization: Bearer <key>` with a secret key from the dashboard (Developers → API keys). Test keys start with `vl_test_`. |
| [`insufficient_scope`](https://docs.vlayers.ai/errors/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`](https://docs.vlayers.ai/errors/test_address_required) | 422 | Use a reserved test address, or a live key to reach a real person. |
| [`test_mode_only`](https://docs.vlayers.ai/errors/test_mode_only) | 403 | Use a test key (`vl_test_…`) from the dashboard. |
| [`test_limit_reached`](https://docs.vlayers.ai/errors/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](https://docs.vlayers.ai/developers/handoff).

## Next steps

- [The MCP tools](https://docs.vlayers.ai/developers/mcp): what each tool does and which scope it needs.
- [Test mode](https://docs.vlayers.ai/developers/test-mode): more ways to simulate people.
- [Python frameworks](https://docs.vlayers.ai/developers/frameworks/python): the same setup in Python.
