Send non-urgent work to the Anthropic Batch API (or Vertex AI) at 50% cost — directly from Claude Code.
Code reviews, documentation, architecture analysis, refactoring plans, security audits — anything that can wait ~1 hour gets half-price processing with Claude Opus. Works with the Anthropic API directly or through Google Cloud's Vertex AI.
git clone git@github.com:s2-streamstore/claude-batch-toolkit.git
cd claude-batch-toolkit
./install.sh --api-key sk-ant-your-key-hereThe installer shows a manifest of every change it will make and asks for confirmation before proceeding.
| Flag | Description |
|---|---|
--api-key KEY |
Your Anthropic API key (required unless already in env) |
--no-poller |
Skip status line configuration |
--unattended |
No interactive prompts |
./uninstall.shThis shows what will be removed, asks for confirmation, and preserves your results in ~/.claude/batches/results/. Use --purge-data to also remove results.
Manual Installation (no script)
If you prefer not to run the install script — or need to install in a restricted environment — follow these steps to set up each component by hand.
| Dependency | Purpose | Install |
|---|---|---|
| uv | Runs the Python MCP server (no virtualenv needed) | curl -LsSf https://astral.sh/uv/install.sh | sh |
| jq | JSON processing in statusline + installer | brew install jq or apt-get install jq |
| curl | Polls the Anthropic API from statusline | brew install curl or apt-get install curl |
You also need an Anthropic API key (sk-ant-...). Get one from console.anthropic.com.
Verify prerequisites:
command -v uv && echo "uv ok" || echo "uv MISSING"
command -v jq && echo "jq ok" || echo "jq MISSING"
command -v curl && echo "curl ok" || echo "curl MISSING"mkdir -p ~/.claude/mcp
mkdir -p ~/.claude/skills/batch
mkdir -p ~/.claude/batches/resultscp mcp/claude_batch_mcp.py ~/.claude/mcp/claude_batch_mcp.pycp skills/batch/SKILL.md ~/.claude/skills/batch/SKILL.mdSkip this step if you don't want batch job counts in your Claude Code status bar. Everything else works without it.
cp statusline.sh ~/.claude/statusline.sh
chmod +x ~/.claude/statusline.shThe toolkit reads ANTHROPIC_API_KEY from ~/.claude/env. This file must be mode 600.
If ~/.claude/env does not exist yet:
echo 'export ANTHROPIC_API_KEY="sk-ant-YOUR-KEY-HERE"' > ~/.claude/env
chmod 600 ~/.claude/envIf ~/.claude/env already exists, open it in your editor and add (or replace) the ANTHROPIC_API_KEY line, then ensure chmod 600 ~/.claude/env.
Claude Code discovers MCP servers through ~/.claude.json. You need to add a claude-batch entry under the mcpServers key.
If ~/.claude.json does not exist yet:
API_KEY=$(grep ANTHROPIC_API_KEY ~/.claude/env | cut -d'"' -f2)
jq -n --arg home "$HOME" --arg key "$API_KEY" '{
"mcpServers": {
"claude-batch": {
"command": "uv",
"args": ["run", ($home + "/.claude/mcp/claude_batch_mcp.py"), "--mcp"],
"env": { "ANTHROPIC_API_KEY": $key }
}
}
}' > ~/.claude.jsonIf ~/.claude.json already exists — merge (don't overwrite):
API_KEY=$(grep ANTHROPIC_API_KEY ~/.claude/env | cut -d'"' -f2)
jq --arg home "$HOME" --arg key "$API_KEY" '
.mcpServers["claude-batch"] = {
"command": "uv",
"args": ["run", ($home + "/.claude/mcp/claude_batch_mcp.py"), "--mcp"],
"env": { "ANTHROPIC_API_KEY": $key }
}
' ~/.claude.json > ~/.claude.json.tmp && mv ~/.claude.json.tmp ~/.claude.jsonOr edit by hand — the path in args must be an absolute path (use echo $HOME to get yours).
Skip this if you skipped Step 4. The statusLine value must be an object, not a bare string.
If ~/.claude/settings.json does not exist yet:
jq -n --arg cmd "bash $HOME/.claude/statusline.sh" '{
"statusLine": {"type": "command", "command": $cmd}
}' > ~/.claude/settings.jsonIf it already exists:
jq --arg cmd "bash $HOME/.claude/statusline.sh" '
.statusLine = {"type": "command", "command": $cmd}
' ~/.claude/settings.json > ~/.claude/settings.json.tmp \
&& mv ~/.claude/settings.json.tmp ~/.claude/settings.jsonWarning: This overwrites any existing
statusLine. If you have a custom statusline, incorporate the batch script manually.
if [ ! -f ~/.claude/batches/jobs.json ]; then
echo '{"version": 1, "jobs": {}}' | jq '.' > ~/.claude/batches/jobs.json
echo "Created jobs.json"
else
echo "jobs.json already exists"
fisource ~/.claude/env
uv run ~/.claude/mcp/claude_batch_mcp.py list --base-dir ~/.claude/batchesExpected: an empty list or JSON showing no jobs. First run may take a moment while uv resolves dependencies.
If you installed the statusline:
echo '{}' | bash ~/.claude/statusline.shecho "=== File check ==="
[ -f ~/.claude/mcp/claude_batch_mcp.py ] && echo "ok MCP server" || echo "MISSING MCP server"
[ -f ~/.claude/skills/batch/SKILL.md ] && echo "ok Skill file" || echo "MISSING Skill file"
[ -f ~/.claude/statusline.sh ] && echo "ok Statusline" || echo "-- Statusline (optional)"
[ -f ~/.claude/env ] && echo "ok Env file" || echo "MISSING Env file"
[ -f ~/.claude/batches/jobs.json ] && echo "ok Jobs registry" || echo "MISSING Jobs registry"
echo ""
echo "=== Config check ==="
jq -e '.mcpServers["claude-batch"]' ~/.claude.json &>/dev/null \
&& echo "ok MCP registered in ~/.claude.json" \
|| echo "MISSING MCP entry in ~/.claude.json"
echo ""
echo "=== Permissions check ==="
PERMS=$(stat -f '%A' ~/.claude/env 2>/dev/null || stat -c '%a' ~/.claude/env 2>/dev/null)
[ "$PERMS" = "600" ] && echo "ok ~/.claude/env is mode 600" || echo "WARN ~/.claude/env is mode $PERMS (should be 600)"Step 1: Remove toolkit files
rm -f ~/.claude/mcp/claude_batch_mcp.py
rm -f ~/.claude/skills/batch/SKILL.md
rm -f ~/.claude/statusline.sh
rmdir ~/.claude/skills/batch 2>/dev/null || true
rmdir ~/.claude/skills 2>/dev/null || trueStep 2: Remove MCP entry from ~/.claude.json
jq 'del(.mcpServers["claude-batch"])' ~/.claude.json > ~/.claude.json.tmp \
&& mv ~/.claude.json.tmp ~/.claude.jsonStep 3: Remove statusline from ~/.claude/settings.json (if installed)
jq 'del(.statusLine)' ~/.claude/settings.json > ~/.claude/settings.json.tmp \
&& mv ~/.claude/settings.json.tmp ~/.claude/settings.jsonStep 4: Remove API key (optional)
grep -v '^export ANTHROPIC_API_KEY=' ~/.claude/env > ~/.claude/env.tmp \
&& mv ~/.claude/env.tmp ~/.claude/env && chmod 600 ~/.claude/env
[ ! -s ~/.claude/env ] && rm -f ~/.claude/envStep 5: Remove jobs data (optional)
rm -f ~/.claude/batches/jobs.json
rm -f ~/.claude/batches/.poll_cache
rm -f ~/.claude/batches/.poll.lock
# To also remove all batch results:
# rm -f ~/.claude/batches/results/*.md ~/.claude/batches/results/*.jsonl ~/.claude/batches/results/*.json
# rmdir ~/.claude/batches/results 2>/dev/null
# rmdir ~/.claude/batches 2>/dev/nullIn Claude Code, just say:
/batch Review this codebase for security issues
/batch Generate comprehensive tests for src/auth/
/batch Write API documentation for all public endpoints
Claude will gather all relevant context, build a self-contained prompt, submit it to the Batch API, and tell you the job ID.
/batch check
/batch status
/batch list
Results appear in your status bar automatically. When a job completes, Claude reads the result from disk and presents it.
The MCP server also works as a standalone CLI:
# Submit a job
uv run ~/.claude/mcp/claude_batch_mcp.py submit --packet-path prompt.md --label "security-review"
# List all jobs
uv run ~/.claude/mcp/claude_batch_mcp.py list
# Poll for completed jobs
uv run ~/.claude/mcp/claude_batch_mcp.py poll
# Fetch a specific result
uv run ~/.claude/mcp/claude_batch_mcp.py fetch msgbatch_xxx --print┌─────────────────────────────────────────────────────────────────┐
│ Claude Code Session │
│ │
│ User: "/batch review src/ for security issues" │
│ │
│ Claude: │
│ 1. Reads all files in src/ │
│ 2. Assembles self-contained prompt (bash → temp file) │
│ 3. Calls send_to_batch MCP tool with packet_path │
│ 4. Reports: "Submitted job msgbatch_abc123" │
│ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ Status Bar │ │
│ │ [Opus] 42% | $1.23 | batch: 1 pending │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │
│ ... ~30 minutes later ... │
│ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ Status Bar │ │
│ │ [Opus] 42% | $1.23 | batch: 1 done │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │
│ User: "/batch check" │
│ Claude: reads ~/.claude/batches/results/msgbatch_abc123.md │
│ presents formatted results │
└─────────────────────────────────────────────────────────────────┘
│
┌─────┴─────┐
▼ ▼
┌──────────────────┐ ┌──────────────────────┐
│ Anthropic Batch │ │ Vertex AI Batch │
│ API (direct) │ │ (via GCP) │
│ 50% cost │ │ 50% cost │
│ ~1hr turnaround │ │ ~1hr turnaround │
└──────────────────┘ └──────────────────────┘
The status line is the only moving part — no daemons, no background services, no launchd/systemd.
Assistant message arrives
│
▼
statusline.sh runs
│
├─► Render (instant): Read jobs.json → print status bar
│
└─► Poll (async fork): If pending jobs + cache stale (>60s)
└─► curl Anthropic API → update jobs.json
(never blocks the status line)
| Property | Value |
|---|---|
| Blocks status line? | Never — poll is forked |
| Polls when idle? | No — only during active Claude sessions |
| Poll frequency | At most once per 60s |
| Extra processes | None — no daemon |
| Wasted API calls | Zero when no pending jobs |
| Variable | Default | Description |
|---|---|---|
ANTHROPIC_API_KEY |
— | Your Anthropic API key (required) |
CLAUDE_BATCH_DIR |
~/.claude/batches |
Where jobs.json and results live |
CLAUDE_MODEL |
claude-opus-4-6 |
Model for batch jobs |
CLAUDE_MAX_TOKENS |
32768 |
Max output tokens |
CLAUDE_THINKING |
— | Set to enabled for extended thinking |
CLAUDE_THINKING_BUDGET |
— | Token budget for thinking |
Use Vertex AI as an alternative backend when you want batch processing through Google Cloud instead of the Anthropic API directly. Both backends offer the same 50% batch discount.
- A GCP project with the Vertex AI API enabled
- The
claude-opus-4-6(or other Claude model) available in your region - A GCS bucket in a supported region for batch input/output
- Application Default Credentials configured:
gcloud auth application-default login| Variable | Description |
|---|---|
VERTEX_PROJECT |
GCP project ID |
VERTEX_LOCATION |
e.g., us-central1 (must support Claude models) |
VERTEX_GCS_BUCKET |
GCS bucket for input/output JSONL files |
VERTEX_GCS_PREFIX |
Folder prefix in the bucket (default: claude-batch) |
When using Vertex, you don't need an ANTHROPIC_API_KEY. Configure ~/.claude.json with your Vertex env vars instead:
{
"mcpServers": {
"claude-batch": {
"command": "uv",
"args": ["run", "/Users/YOU/.claude/mcp/claude_batch_mcp.py", "--mcp"],
"env": {
"VERTEX_PROJECT": "my-gcp-project",
"VERTEX_LOCATION": "us-central1",
"VERTEX_GCS_BUCKET": "my-batch-bucket"
}
}
}
}You can also set both Anthropic and Vertex credentials to have both backends available.
When send_to_batch is called with backend: "auto" (the default):
- If
ANTHROPIC_API_KEYis set, Anthropic is used (preferred — supports background status line polling) - Otherwise, if all three Vertex env vars are set (
VERTEX_PROJECT,VERTEX_LOCATION,VERTEX_GCS_BUCKET), Vertex is used - You can force a specific backend by passing
backend: "anthropic"orbackend: "vertex"
The Vertex Batch API supports both GCS and BigQuery for input/output. This toolkit uses GCS because it's simpler for single-prompt-at-a-time usage — no table schema, no extra infrastructure, just JSONL files in a bucket. BigQuery support is not implemented but contributions are welcome.
The background status line poller (statusline.sh) only polls the Anthropic API. Vertex AI jobs are not polled in the background — their status updates when you run /batch check (which calls batch_poll_once). This is because Vertex polling requires Google auth credentials that the lightweight curl+jq statusline script can't handle.
For more details, see the Vertex AI Claude batch documentation.
~/.claude/
├── env # ANTHROPIC_API_KEY (mode 600)
├── settings.json # statusLine config
├── mcp/
│ └── claude_batch_mcp.py # MCP server
├── skills/
│ └── batch/
│ └── SKILL.md # Skill definition
├── statusline.sh # Status bar + cached poller
└── batches/
├── jobs.json # Job registry
├── .poll_cache # Last poll timestamp
├── .poll.lock # Prevents concurrent polls
└── results/
├── msgbatch_xxx.md # Completed results
└── msgbatch_xxx.meta.json
| Model | Standard | Batch (50% off) |
|---|---|---|
| Claude Opus 4 | $15 / $75 per 1M tokens | $7.50 / $37.50 |
| Claude Sonnet 4 | $3 / $15 per 1M tokens | $1.50 / $7.50 |
(Input / Output per million tokens)
Typical turnaround: under 1 hour. Maximum: 24 hours.
# Test the MCP server directly
uv run ~/.claude/mcp/claude_batch_mcp.py list
# Check if uv is installed
which uv
# Verify API key
grep ANTHROPIC_API_KEY ~/.claude/env# Check statusline config
jq '.statusLine' ~/.claude/settings.json
# Test statusline manually
echo '{}' | bash ~/.claude/statusline.sh
# Check jobs.json exists
cat ~/.claude/batches/jobs.json# Manual poll
uv run ~/.claude/mcp/claude_batch_mcp.py poll
# Check API status directly
source ~/.claude/env
curl -s -H "x-api-key: $ANTHROPIC_API_KEY" \
-H "anthropic-version: 2023-06-01" \
https://api.anthropic.com/v1/messages/batches/BATCH_ID# Refresh application default credentials
gcloud auth application-default login
# Verify your project and location
echo $VERTEX_PROJECT $VERTEX_LOCATION
# Check that the GCS bucket exists and is accessible
gcloud storage ls gs://$VERTEX_GCS_BUCKET/
# Test Vertex API access
gcloud ai batch-prediction-jobs list --project=$VERTEX_PROJECT --region=$VERTEX_LOCATIONchmod 600 ~/.claude/env- MCP Server (
claude_batch_mcp.py): Python script run byuv. Exposessend_to_batch,batch_status,batch_fetch,batch_list,batch_poll_oncetools. Also works as a CLI. - Skill (
SKILL.md): Teaches Claude Code how and when to use the batch tools. Loaded automatically. - Status Line (
statusline.sh): Bash script that renders batch job counts in the Claude Code status bar and triggers background polling viacurl+jq. - Jobs Registry (
jobs.json): JSON file tracking all submitted batch jobs, their states, and result paths.
MIT