@renkoya1/claude-app-server
v0.5.2
Published
JSON-RPC server that exposes Claude Agent SDK to any client (codex app-server compatible wire protocol).
Maintainers
Readme
claude-app-server
JSON-RPC server that exposes the official Claude Agent SDK to any client
over a wire protocol modeled on codex app-server.
┌─────────────────┐ JSON-RPC 2.0 ┌──────────────────────┐ @anthropic-ai/ ┌──────────────────┐
│ your client │ (stdio JSONL) │ claude-app-server │ claude-agent-sdk │ Anthropic API │
│ (VS Code, web, │ ◀───────────────▶ │ (Rust frontend + │ ──────────────────▶ │ api.anthropic │
│ desktop, CLI) │ │ TypeScript sidecar) │ │ .com │
└─────────────────┘ └──────────────────────┘ └──────────────────┘Why two languages? Rust owns the protocol surface, transport, thread state
and multi-client fan-out. The TypeScript sidecar owns the agent loop
(tool use, MCP, hooks, skills, subagents, sessions) by hosting
@anthropic-ai/claude-agent-sdk, which is the official Claude SDK
implementation. The split keeps the protocol layer fast and statically
typed while letting the SDK update itself with Claude Code releases.
This is not the official
claudeCLI or Claude Code product. It is a standalone server that lets you build your own client on top of the Claude agent. Authentication piggybacks onclaude login.
Install (users)
npx @renkoya1/claude-app-server # one-off run
npm i -g @renkoya1/claude-app-server # install globallyThat's the whole install. npm install runs a postinstall step that
downloads the Rust binary for your platform from this package's GitHub
Release. Supported platforms: darwin-arm64, darwin-x64, linux-x64,
linux-arm64.
The package ships:
bin/launcher.mjs— Node wrapper that exec's the Rust binary.dist/sidecar/— compiled TypeScript sidecar bundling@anthropic-ai/claude-agent-sdk.dist/bin/<platform>/claude-app-server— Rust binary for your platform.
Authentication
This server uses @anthropic-ai/claude-agent-sdk, which is the same
engine that powers the claude CLI / Claude Code app. If you have
already run claude login, you do not need any environment variables.
Credential resolution order (same as the official claude CLI):
ANTHROPIC_API_KEYenv var, if set, takes precedence.- OAuth tokens written by
claude login:- macOS: Keychain entry
Claude Code-credentials. - Linux / Windows:
~/.claude/.credentials.json.
- macOS: Keychain entry
- AWS Bedrock / Google Vertex credentials, if configured for the SDK.
So the two usual setups are:
# You already use Claude Code — nothing to do.
claude login # one-time
npx @renkoya1/claude-app-server
# Or you want raw API key access (no Claude subscription):
ANTHROPIC_API_KEY=sk-ant-... npx @renkoya1/claude-app-serverBuilding a custom agent
You have two parallel ways to configure an agent, and you can mix them freely. Both produce a fully working Claude Agent SDK session behind the JSON-RPC surface.
Way 1 — Filesystem conventions (no code, file-based)
The Claude Agent SDK auto-loads everything from the standard Claude Code directory layout. Drop files in the right place and they just work — no JSON-RPC config required.
| Feature | Location | Notes |
|-------------------|-----------------------------------------------------------------------------------|------------------------------------------------|
| Rules | ~/.claude/CLAUDE.md (user) and <cwd>/CLAUDE.md (project) | Injected into the system prompt. |
| Skills | ~/.claude/skills/<name>/SKILL.md, <cwd>/.claude/skills/<name>/SKILL.md | The model invokes them via the Skill tool. |
| Subagents (file) | ~/.claude/agents/<name>.md, <cwd>/.claude/agents/<name>.md | Reachable from the Task tool. |
| Slash commands | ~/.claude/commands/<name>.md, <cwd>/.claude/commands/<name>.md | User invokes as /<name>. |
| Hooks | ~/.claude/settings.json hooks field | PreToolUse / PostToolUse / etc. |
| Memories | ~/.claude/memories/*.md | Loaded into context as agent recall. |
| MCP servers | ~/.claude/mcp.json | Auto-connected. |
| Settings | ~/.claude/settings.json, <cwd>/.claude/settings.json, .claude/settings.local.json | All SDK settings keys. |
You can ship an agent as a self-contained bundle:
my-agent-bundle/
├── CLAUDE.md # rules
└── .claude/
├── settings.json # SDK settings
├── skills/
│ └── sql-expert/SKILL.md
├── agents/
│ └── sql-reviewer.md
├── commands/
│ └── analyze-query.md # /analyze-query
└── mcp.jsonThen tell the server to use it as the working directory:
{ "method": "thread/start", "params": { "cwd": "/path/to/my-agent-bundle" } }The SDK reads CLAUDE.md, the skills, the file subagents, the slash
commands, and the MCP servers automatically. No protocol options
needed.
Way 2 — Programmatic config via JSON-RPC
thread/start and turn/start are open-ended: any field the
official @anthropic-ai/claude-agent-sdk Options type accepts is
forwarded verbatim into query({ options }). New SDK options work
without a protocol bump.
Common knobs:
| params key | Effect |
|---------------------------|-------------------------------------------------------------------------|
| model | Pick the model (claude-opus-4-7, claude-sonnet-4-6, claude-haiku-4-5-20251001, ...). |
| systemPrompt | Replace the default Claude Code system prompt with your own. |
| cwd | Working directory the agent operates from. |
| additionalDirectories | Extra directories the agent may read/write beyond cwd. |
| permissionMode | default / acceptEdits / bypassPermissions / plan / dontAsk / auto. |
| allowedTools | Restrict the agent to a tool whitelist (e.g. ["Read", "Grep"]). |
| disallowedTools | Tool blacklist (e.g. ["Bash"] to forbid shell exec). |
| maxTurns | Cap the autonomous tool-use loop length. |
| env | Env vars passed into the agent's child processes. |
| skills | "all" or an array of skill names to enable. |
| mcpServers | Per-thread MCP server registration (DB connectors, internal tools, ...).|
| agents | Programmatic subagents reachable via the Task tool. |
| settingSources | Subset of user / project / local to read settings from. |
| bridgeCanUseTool | true → tool calls emit item/*/requestApproval; client replies with permission/respond (see Bridged callbacks). |
| bridgeHooks | "all" or array of hook events → server emits hook/started/hook/completed. |
Example — a SQL-only agent with a DB-backed MCP server:
{
"method": "thread/start",
"id": 1,
"params": {
"model": "claude-sonnet-4-6",
"systemPrompt": "You are a SQL expert. Use the `db` MCP to query.",
"permissionMode": "plan",
"allowedTools": ["Read", "Grep"],
"skills": ["sql-expert"],
"maxTurns": 8,
"mcpServers": {
"db": {
"type": "stdio",
"command": "node",
"args": ["mcp-postgres.js"],
"env": { "DATABASE_URL": "postgres://localhost/app" }
}
},
"agents": {
"performance-reviewer": {
"description": "Reviews SQL plans for performance issues.",
"prompt": "You audit query plans. Use EXPLAIN ANALYZE.",
"tools": ["Read"]
}
}
}
}Way 3 — Hybrid (file defaults, JSON-RPC overrides)
Filesystem conventions provide the baseline; JSON-RPC params override
or extend per session. This is the most common production setup —
ship a .claude/ skill/agent bundle with your UI, and let the user
flip permissionMode, model, systemPrompt from the UI.
{
"method": "thread/start",
"params": {
"cwd": "/path/to/my-agent-bundle", // file conventions load here
"systemPrompt": "User-selected persona", // override CLAUDE.md preset
"permissionMode": "plan", // UI choice
"skills": ["sql-expert", "git-helper"] // narrow file skills
}
}Bridged callbacks (canUseTool, hooks)
JavaScript callbacks cannot cross a JSON boundary, but the sidecar bridges them as request/response events:
// Opt-in once at thread/start:
{ "method": "thread/start", "params": { ..., "bridgeCanUseTool": true, "bridgeHooks": "all" } }
// Then server emits e.g.:
// {"method":"item/commandExecution/requestApproval","params":{"requestId":"...","tool":"Bash","input":{...}}}
// Client must reply:
{ "method": "permission/respond", "id": 99, "params": { "requestId": "...", "result": { "behavior": "allow" } } }Same pattern for hooks: server emits hook/started / hook/completed
with the hook payload; client optionally replies via hook/respond.
Runtime control (mid-session steering)
Once a session is running, mutate it without recreating:
| Method | Effect |
|-----------------------------------|---------------------------------------------------------|
| thread/model/set | Switch model mid-session. |
| thread/maxThinkingTokens/set | Adjust extended-thinking budget. |
| agent/define | Add a new programmatic subagent (recreates session). |
| agent/list / agent/remove | Inspect / remove current subagents. |
| mcpServer/set | Reconfigure MCP servers (recreates session). |
| turn/steer | Append user input to the active turn without new turn. |
| turn/interrupt | Abort the active turn. |
Feature parity with Claude Code
Everything the official Claude Code CLI supports works here, with one exception:
| SDK feature | Status |
|------------------------------------------------|-----------------------------------------------------------|
| File-based: rules, skills, subagents, commands, hooks, memories, MCP, settings | ✅ Auto-loaded by the SDK |
| Programmatic: agents, mcpServers (stdio/sse/http), systemPrompt, etc. | ✅ Pass via thread/start params |
| canUseTool callback | ✅ Via bridgeCanUseTool: true |
| hooks callbacks | ✅ Via bridgeHooks: "all" |
| Runtime control (model, permission mode, MCP) | ✅ Via thread/*/set endpoints |
| createSdkMcpServer() + tool() (in-process JS tool) | ⚠️ Fork the sidecar (see below) |
For in-process JS tools, ship a custom sidecar:
# your-custom-sidecar.mjs imports claude-agent-sdk + adds tools via tool()
CLAUDE_APP_SERVER_SIDECAR=/path/to/your-custom-sidecar.mjs \
npx @renkoya1/claude-app-serverThe CLAUDE_APP_SERVER_SIDECAR env var lets you swap the default
sidecar with your own — every other piece of the server (transport,
thread store, JSON-RPC dispatch) stays intact.
Notes
- The frontend does not type-check unknown keys; the SDK rejects
malformed shapes and the server emits a
turn/completedwithisError: trueand the SDK error inerrors[]. - The full SDK
Optionsreference: https://github.com/anthropics/claude-agent-sdk-typescript
Build from source (contributors)
You only need this section if you cloned the repo to develop the server
itself. End users should use npx above.
git clone https://github.com/RenKoya1/claude-app-server.git
cd claude-app-server
npm install # installs launcher + sidecar runtime deps
npm run build # cargo build --release + tsc + stage dist/
node bin/launcher.mjs # smoke testRun
claude-app-server speaks newline-delimited JSON-RPC 2.0 on stdin/stdout.
Drive it from your client:
{
printf '%s\n' '{"id":1,"method":"initialize","params":{"clientInfo":{"name":"demo","version":"0.1.0"}}}'
printf '%s\n' '{"method":"initialized","params":{}}'
printf '%s\n' '{"id":2,"method":"thread/start","params":{"model":"claude-haiku-4-5-20251001"}}'
# remember thr_xxx from id:2 result
printf '%s\n' '{"id":3,"method":"turn/start","params":{"threadId":"thr_xxx","input":[{"type":"text","text":"hello"}]}}'
} | npx claude-app-serverYou will see item/agentMessage/delta notifications stream the response,
followed by a final turn/completed with token usage.
Environment variables (all optional)
| Var | Meaning |
|--------------------------------|-------------------------------------------------------------------------|
| ANTHROPIC_MODEL | default model when thread/start omits one. |
| ANTHROPIC_API_KEY | raw API key. Only needed if you don't use claude login for auth. |
| CLAUDE_HOME | reported as codexHome in initialize (default ~/.claude). |
| CLAUDE_APP_SERVER_SIDECAR | override the sidecar script path. Auto-set by the launcher. |
| CLAUDE_APP_SERVER_SKIP_DOWNLOAD=1 | skip the postinstall binary download. |
| CLAUDE_APP_SERVER_RELEASE_URL | override the binary download template used by postinstall. |
| RUST_LOG | tracing filter, e.g. info,claude_app_server=debug. |
| LOG_FORMAT=json | emit JSON tracing lines on stderr. |
Protocol overview
The wire protocol mirrors codex app-server so a client already written
against Codex can talk to this server with minimal changes:
- Newline-delimited JSON.
jsonrpcfield omitted. - Required
initializehandshake per connection. Subsequent requests beforeinitializedare rejected with code-32002. - Repeated
initializecalls return-32003 Already initialized. - When the ingress queue saturates, requests are rejected with code
-32001 Server overloaded; retry later.— retry with backoff.
Supported methods
Lifecycle
| Method | Notes |
|---------------|----------------------------------------------------------------|
| initialize | Handshake; must precede every other request on a connection. |
| initialized | Notification acking the handshake. |
Threads
| Method | Notes |
|------------------------|------------------------------------------------------------------------|
| thread/start | Create a thread. Accepts model, cwd, ephemeral, systemPrompt. |
| thread/resume | Reopen a thread by id; supports excludeTurns. |
| thread/fork | Branch a thread; ephemeral supported. |
| thread/list | In-memory list, sorted newest first. |
| thread/loaded/list | Ids of currently loaded threads. |
| thread/read | Returns the stored thread; includeTurns hydrates history. |
| thread/archive | Move thread out of the active set; emits thread/archived. |
| thread/unarchive | Restore an archived thread; emits thread/unarchived. |
| thread/unsubscribe | Stop receiving events for a thread; emits thread/closed. |
| thread/name/set | Rename a thread; emits thread/name/updated. |
| thread/inject_items | Push raw SDK user messages into a session's input stream. |
| thread/compact/start | Request manual context compaction. |
| thread/goal/set | Create/update the persisted goal; emits thread/goal/updated. |
| thread/goal/get | Read the current goal (null if none). |
| thread/goal/clear | Remove the goal; emits thread/goal/cleared if state changed. |
Turns
| Method | Notes |
|------------------|-------------------------------------------------------------------------|
| turn/start | Stream a turn through the Claude Agent SDK. Tools/MCP/skills/hooks work.|
| turn/interrupt | Abort the in-flight turn via the sidecar's AbortController. |
| turn/steer | Append additional user input to the active turn. |
Models / config
| Method | Notes |
|---------------|----------------------------------------------------------------------|
| model/list | Reports Opus 4.7, Sonnet 4.6, Haiku 4.5. |
| config/read | Effective model, claude home, platform, current cwd. |
Skills / hooks / MCP
| Method | Notes |
|----------------------------|-------------------------------------------------------------|
| skills/list | Discover SKILL.md files under ~/.claude/skills/. |
| hooks/list | Placeholder until the SDK exposes a hook discovery API. |
| mcpServerStatus/list | Placeholder until the SDK exposes MCP status API. |
| mcpServer/tool/call | Currently returns -32601; route through turn/start. |
Filesystem
| Method | Notes |
|-----------------------|----------------------------------------------------------------------|
| fs/readFile | Return base64-encoded bytes for an absolute file path. |
| fs/writeFile | Write base64-decoded bytes to an absolute path; parents auto-created.|
| fs/createDirectory | recursive defaults to true. |
| fs/getMetadata | isDirectory / isFile / isSymlink / createdAtMs / modifiedAtMs / sizeBytes. |
| fs/readDirectory | Sorted child entries with isDirectory / isFile. |
| fs/remove | recursive / force default to true. |
| fs/copy | Files copy directly; directories require recursive: true. |
| fs/watch | Cross-platform notify-backed watcher with watchId-keyed routing. |
| fs/unwatch | Cancel a registered watcher. |
Command exec
| Method | Notes |
|---------------------------|-------------------------------------------------------------------------|
| command/exec | Spawn argv, capture stdout/stderr/exit. Optional streaming via processId + streamStdoutStderr. |
| command/exec/write | Write base64 stdin to a running streaming process (or close stdin). |
| command/exec/terminate | Kill a running streaming process. |
command/execis unsandboxed. Codex enforces a sandbox; we leave that to the host environment. Use only with trusted local UIs.
Notifications emitted
thread/started,thread/archived,thread/unarchived,thread/name/updated,thread/goal/updated,thread/goal/cleared,thread/closed.thread/status/changedon idle/active transitions.turn/started,turn/completed(withtokenUsage).item/started,item/completed,item/agentMessage/delta.fs/changed(perwatchId).command/exec/outputDelta(whenstreamStdoutStderr: true).
Item kinds streamed
userMessage— recorded onturn/start.agentMessage— the assistant's text reply.toolUse— the assistant invoked a tool (Bash,Read,Edit, MCP tool, …).toolResult— the result of a tool invocation;isErrorflags failures.reasoning— extended thinking output, when enabled.
Gaps vs Codex app-server
These exist in codex-rs/app-server but are out of scope for now. A client
touching them gets -32601 Method not supported.
| Area | Status |
|-----------------------------|-----------------------------------------------------------------|
| websocket / unix-socket | Only --listen stdio:// is wired up. |
| Persistence | Threads + rollouts are RAM-only and lost on restart. |
| process/spawn / PTY exec | Use command/exec. PTY mode + Windows sandbox not implemented. |
| mcpServer* rich queries | Stub responses; SDK does not currently expose MCP status APIs. |
| marketplace/*, plugin/* | No plugin/marketplace concept. |
| review/start | No automated reviewer pipeline. |
| thread/realtime/* | No realtime / WebRTC bridge. |
| feedback/upload, windowsSandbox/*, experimentalFeature/*, ... | Not implemented. |
| turn/steer | Not implemented; AbortController-based interrupt is. |
| thread/compact/start, thread/shellCommand, thread/inject_items | Not implemented. |
| Schema export | No generate-ts / generate-json-schema subcommands yet. |
Repository layout
claude/
├── package.json # npm package manifest
├── bin/launcher.mjs # `claude-app-server` entry point
├── scripts/
│ ├── build-local.sh # cargo + tsc + stage dist/
│ └── postinstall.mjs # download platform binary on `npm install`
├── dist/
│ ├── sidecar/ # compiled TS sidecar (shipped)
│ └── bin/<triple>/ # Rust binary (shipped)
├── crates/ # Rust source (not shipped; use cargo)
│ ├── claude-app-server/
│ ├── claude-app-server-protocol/
│ └── claude-app-server-transport/
├── sidecar/ # TS source (not shipped)
│ ├── package.json
│ ├── tsconfig.json
│ └── src/index.ts
└── README.mdBuilding a custom client
The simplest client is just JSON-RPC over a process pipe:
import { spawn } from "node:child_process";
const server = spawn("npx", ["claude-app-server"], {
stdio: ["pipe", "pipe", "inherit"],
});
let nextId = 1;
function send(method: string, params?: unknown) {
const id = nextId++;
server.stdin.write(JSON.stringify({ id, method, params }) + "\n");
return id;
}
server.stdout.on("data", (chunk) => {
for (const line of chunk.toString().split("\n")) {
if (!line.trim()) continue;
const msg = JSON.parse(line);
if (msg.method === "item/agentMessage/delta") {
process.stdout.write(msg.params.delta);
} else if (msg.method === "turn/completed") {
console.log("\n[done]", msg.params.tokenUsage);
}
}
});
send("initialize", { clientInfo: { name: "demo", version: "0.0.1" } });
server.stdin.write(JSON.stringify({ method: "initialized", params: {} }) + "\n");
const threadId = "<read from id:1 response>";
send("turn/start", { threadId, input: [{ type: "text", text: "hello" }] });(Real clients should track request ids, route responses, and respect the initialize handshake before sending other requests.)
Caveats
- The Rust → TypeScript IPC is internal and may change between versions. Speak to the JSON-RPC surface, not the sidecar.
claude loginitself is not implemented here. Run the officialclaudeCLI once to populate credentials.- The bundled binary is downloaded on
npm install. Sandboxed CI environments without outbound HTTP needCLAUDE_APP_SERVER_RELEASE_URLpointing at a mirror ornpm run buildfrom a source checkout.
Contributing
We welcome PRs. Please read CONTRIBUTING.md before
opening one.
Releases are restricted to maintainers. Only repository maintainers
with push access to RenKoya1/claude-app-server (and the NPM_TOKEN
secret on this GitHub repo) can publish to npm. Contributors should:
- Fork the repo on GitHub.
- Push a feature branch to your fork.
- Open a PR against
RenKoya1/claude-app-server:main.
A maintainer will review, merge, and cut the next release (see
RELEASING.md for the procedure they follow).
