---
title: "Claude Agent SDK — run it on company accounts · Selan"
description: "Run the Claude Agent SDK in TypeScript or Python on your company's accounts: install it, set two environment variables, write an agent, add tools over MCP."
url: https://selan.ai/docs/claude-agent-sdk
updated: 2026-10-13T17:34:17+00:00
---

Build

Docs · Claude Agent SDK

# Claude Agent SDK on your company's accounts

The Agent SDK is Claude Code as a library — the same loop and the same built-in tools, driven from your code instead of a terminal. Pointing it at Selan is two environment variables. For running the CLI itself, start at [the docs](https://selan.ai/docs).

## How do I install the Claude Agent SDK?

The SDK ships for TypeScript and Python. It needs Node 18 or newer even in the Python case, because it runs Claude Code underneath:

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

```bash
pip install claude-agent-sdk
```

**This is not `@anthropic-ai/sdk`.** That one sends a request and returns an answer, and it is covered on [the API page](https://selan.ai/docs/api). The Agent SDK runs a whole session: it reads files, runs commands and keeps going until the work is done. Both reach the gateway the same way, so the setup below applies to either.

## Which two variables point it at Selan?

A base URL carrying the `/anthropic` segment, which names the format the SDK speaks, and a token:

```bash
export ANTHROPIC_BASE_URL=https://gw.selan.ai/anthropic
export ANTHROPIC_AUTH_TOKEN=selanct_...
```

Mint the token in Selan under [**Settings → API tokens**](https://control.selan.ai/settings/access). It spends as itself, under an address like `nightly-triage@tokens.selan.ai`, so what the agent costs shows up as the agent in Usage rather than on whoever set it up.

`ANTHROPIC_API_KEY` works here too. The SDK turns either one into the `Authorization: Bearer` header the gateway reads, which is why both are accepted — unlike a raw HTTP call, where only the bearer form is.

**The SDK starts a subprocess, so it needs the variables handed to it.** Exporting them in your shell covers the simple case. Inside a service, a test runner or CI, pass them through the `env` option and spread the current environment rather than replacing it — drop `PATH` and the subprocess cannot start at all.

```typescript
const env = {
  ...process.env,
  ANTHROPIC_BASE_URL: "https://gw.selan.ai/anthropic",
  ANTHROPIC_AUTH_TOKEN: process.env.SELAN_TOKEN,
}
```

## How do I write a first agent?

A whole session in one call. `query` returns an async iterator of the messages the session produces, and the `result` message is the end of it:

```typescript
import { query } from "@anthropic-ai/claude-agent-sdk"

const env = {
  ...process.env,
  ANTHROPIC_BASE_URL: "https://gw.selan.ai/anthropic",
  ANTHROPIC_AUTH_TOKEN: process.env.SELAN_TOKEN,
}

for await (const message of query({
  prompt: "Find every TODO under src/ and write them to TODOS.md",
  options: {
    env,
    cwd: "/tmp/scratch",
    model: "claude-opus-5",
    permissionMode: "bypassPermissions",
  },
})) {
  if (message.type === "assistant") {
    for (const block of message.message.content) {
      if (block.type === "tool_use") console.log("tool:", block.name)
    }
  }
  if (message.type === "result") console.log(message.result)
}
```

Python, the same session:

```python
import os, anyio
from claude_agent_sdk import query, ClaudeAgentOptions

env = {
    **os.environ,
    "ANTHROPIC_BASE_URL": "https://gw.selan.ai/anthropic",
    "ANTHROPIC_AUTH_TOKEN": os.environ["SELAN_TOKEN"],
}

async def main():
    async for message in query(
        prompt="Find every TODO under src/ and write them to TODOS.md",
        options=ClaudeAgentOptions(
            env=env,
            cwd="/tmp/scratch",
            model="claude-opus-5",
            permission_mode="bypassPermissions",
        ),
    ):
        if message.type == "result":
            print(message.result)

anyio.run(main)
```

-   **`cwd`** is the directory the session may work in. Give it a scratch directory rather than the process's own.
-   **`permissionMode`** decides what happens when a tool asks. `bypassPermissions` never asks, which is the only workable setting when nobody is watching, and the reason `cwd` matters.
-   **`model`** takes an Anthropic id or one of your company's, below.

## How do I limit what the agent may do?

By default the session has Claude Code's whole tool set: reading and writing files, running shell commands, searching the web. `allowedTools` narrows that to a list you name:

```typescript
options: {
  env,
  cwd: "/tmp/scratch",
  model: "claude-opus-5",
  permissionMode: "bypassPermissions",
  allowedTools: ["Read", "Grep", "Glob"],
}
```

That session can read the tree and nothing else. An empty array leaves it with no tools at all, which turns the SDK into a plain one-turn call.

Narrowing is worth doing when you can name the tools the task needs. An agent handed `Bash` can reach anything the process can, so a restriction expressed anywhere above it is one the agent can walk around.

**Selan does not change what a session may do locally.** The gateway decides which credential is spent and on what. Which files a session touches is the SDK's business and yours — run unattended work in a container or a checkout you can throw away.

## How do I connect my own tools over MCP?

Tools of your own go in over MCP. The SDK can start an in-process server, so a tool is a function and there is no second process to run:

```typescript
import { createSdkMcpServer, tool, query } from "@anthropic-ai/claude-agent-sdk"
import { z } from "zod"

const server = createSdkMcpServer({
  name: "billing",
  tools: [
    tool(
      "invoice_total",
      "Returns the total on an invoice, in cents.",
      { invoiceId: z.string() },
      async ({ invoiceId }) => ({
        content: [{ type: "text", text: String(await lookupTotal(invoiceId)) }],
      })
    ),
  ],
})

for await (const message of query({
  prompt: "What is the total on invoice INV-4021?",
  options: { env, mcpServers: { billing: server }, permissionMode: "bypassPermissions" },
})) {
  if (message.type === "result") console.log(message.result)
}
```

The model sees that tool as `mcp__billing__invoice_total`: the server name and the tool name, joined. Name it in that full form if you are also narrowing the built-in set with `allowedTools`.

An external MCP server is the same option with a different value — a command to spawn over stdio, or a URL. The [SDK's MCP documentation](https://docs.claude.com/en/api/agent-sdk/mcp) covers both.

**Tool definitions are prompt, and prompt is metered.** Every tool you mount is sent on every turn of the session, so a large catalog costs on each one. The gateway records what your tool definitions weigh alongside the rest of the request.

## Which models can the Agent SDK use?

Anthropic's own ids work unchanged — `claude-opus-5`, `claude-sonnet-5`. To run a session on a model your company connected through another provider, use the id Selan lists for it:

```bash
curl https://gw.selan.ai/anthropic/v1/models \
  -H "Authorization: Bearer $SELAN_TOKEN"
```

Every row is a model this token may name, already in the form the SDK expects. Which credential serves it is decided per request, so the id is all your code needs to know.

## What does my company see?

The same as every other Selan client. Each turn is metered against the token that made it, the company-wide per-user spend limit applies, and the session appears in Logs under its own address.

One session is many turns rather than one request, so an agent that fails to terminate keeps spending until something stops it. Set a budget on the token before you leave one running unattended.

## Related

-   [How Claude Code and Codex run without API keys on laptops](https://selan.ai/guides/claude-code-without-api-keys): why the token is a Selan one, not an Anthropic key.
-   [Codex SDK](https://selan.ai/docs/codex-sdk): the same idea for Codex.
-   [Calling Selan from your own code](https://selan.ai/docs/api): one request, without an agent around it.
