Claude Code + Majordomo Gateway
This guide shows how to route Claude Code through Majordomo Gateway to track usage and costs across your development team. There are two approaches depending on the level of tracking you need.
Two Approaches
| Simple Setup | Companion Setup | |
|---|---|---|
| How | Environment variables + settings.json |
majordomo-companion local proxy |
| Tracks | Per-request tokens, costs, model, metadata | Everything in Simple, plus session grouping |
| Session tracking | No | Yes — groups requests into sessions with aggregates |
| Request detail parsing | Yes (requires X-Majordomo-Client: claude-code header) |
Yes (companion adds it automatically) |
| Setup effort | Minimal — edit one file | Run a command before each session |
| Best for | Individual developers, simple cost tracking | Teams wanting session-level analytics |
Both approaches log every request to llm_requests. When the gateway sees X-Majordomo-Client: claude-code, it also parses Claude Code-specific metadata (tool usage, thinking mode, plan mode) into claude_request_details. The companion adds session grouping on top.
Simple: Claude Code → Majordomo Gateway → Anthropic API
↓
PostgreSQL
Companion: Claude Code → companion (local proxy) → Majordomo Gateway → Anthropic API
↓
PostgreSQL
Prerequisites
- A running Majordomo Gateway instance (Getting Started or Docker Compose)
- A Majordomo API key (
mdm_sk_...) - Claude Code installed (
npm install -g @anthropic-ai/claude-code) - An Anthropic API key
Simple Setup (No Companion)
Point Claude Code at the gateway using environment variables. No extra tooling required.
Option A: Persistent via settings.json
Edit ~/.claude/settings.json to set the gateway URL and tracking headers for every session:
{
"env": {
"ANTHROPIC_BASE_URL": "http://localhost:7680",
"ANTHROPIC_CUSTOM_HEADERS": "X-Majordomo-Key: mdm_sk_your_key_here\nX-Majordomo-Client: claude-code"
}
}
The X-Majordomo-Client: claude-code header tells the gateway to parse Claude Code-specific metadata (tool usage, thinking mode, plan mode) into claude_request_details. Without it, requests are only logged to llm_requests.
To add metadata (developer name, project, team), add more headers separated by newlines:
{
"env": {
"ANTHROPIC_BASE_URL": "http://localhost:7680",
"ANTHROPIC_CUSTOM_HEADERS": "X-Majordomo-Key: mdm_sk_your_key_here\nX-Majordomo-Client: claude-code\nX-Majordomo-Developer: alice\nX-Majordomo-Project: backend-api\nX-Majordomo-Team: platform"
}
}
Then just run Claude Code as usual:
Option B: Inline Environment Variables
For one-off sessions or testing, set variables inline:
ANTHROPIC_BASE_URL=http://localhost:7680 \
ANTHROPIC_CUSTOM_HEADERS="X-Majordomo-Key: mdm_sk_your_key_here
X-Majordomo-Client: claude-code" \
claude
Don't mix both
ANTHROPIC_CUSTOM_HEADERS is a single string value, not an array. If set in both settings.json and the shell, one will override the other entirely — the headers won't merge. Pick one location and stick with it.
Verify It Works
After running Claude Code, check that requests are being logged:
SELECT model, input_tokens, output_tokens, total_cost, requested_at
FROM llm_requests
ORDER BY requested_at DESC
LIMIT 5;
Companion Setup (With Session Tracking)
The companion proxy (majordomo-companion) wraps each Claude Code session with lifecycle tracking. It registers a session on startup, injects the session ID into every request, and ends the session on shutdown.
Build the Companion
This creates ./bin/majordomo-companion.
Quick Start with exec
The simplest way — start the proxy, run Claude Code, clean up automatically:
majordomo-companion exec \
--gateway http://localhost:7680 \
--api-key mdm_sk_your_key_here \
-- claude
The companion:
- Registers a new session with the gateway
- Starts a local proxy on a random port
- Launches
claudewithANTHROPIC_BASE_URLpointing at the local proxy - Injects
X-Majordomo-Key,X-Majordomo-Client: claude-code, andX-Majordomo-ClaudeCode-Session-Idheaders into every request - Waits for Claude Code to exit
- Ends the session and shuts down
You can pass any arguments to Claude Code after the --:
majordomo-companion exec \
--gateway http://localhost:7680 \
--api-key mdm_sk_your_key_here \
-- claude "Explain this codebase"
Adding Metadata
Via CLI Flags
Use repeatable --metadata flags:
majordomo-companion exec \
--gateway http://localhost:7680 \
--api-key mdm_sk_your_key_here \
--metadata Developer=alice \
--metadata Project=backend-api \
--metadata Team=platform \
-- claude
Via Metadata File
Create a .majordomo/metadata.json file in your project directory:
The companion reads this file automatically from the current working directory. CLI --metadata flags override file values when both specify the same key.
You can specify a different directory with --workdir:
majordomo-companion exec \
--gateway http://localhost:7680 \
--api-key mdm_sk_your_key_here \
--workdir /path/to/project \
--metadata Developer=alice \
-- claude
Long-Running Setup with start / stop
For workflows where you want the proxy running across multiple Claude Code invocations:
Start the companion:
majordomo-companion start \
--gateway http://localhost:7680 \
--api-key mdm_sk_your_key_here \
--pid-file /tmp/companion.pid \
--env-file /tmp/companion.env \
--metadata Developer=alice
This prints the local proxy URL (e.g., http://127.0.0.1:54321) and writes:
- A PID file for stopping later
- An env file containing
export ANTHROPIC_BASE_URL=http://127.0.0.1:54321
Source the environment and run Claude Code:
Stop the companion:
What Gets Tracked
Every Request (llm_requests)
Both approaches log every API call with:
- Model name, provider
- Input and output token counts
- Calculated cost
- Request timing
- Custom metadata from
X-Majordomo-*headers
Claude Code Details (claude_request_details)
Both approaches automatically parse Claude Code request/response pairs to extract:
| Field | Description |
|---|---|
message_count |
Total messages in the conversation |
user_message_count |
Number of user messages |
assistant_message_count |
Number of assistant messages |
tool_names |
Tools used in this request (e.g., {Read,Edit,Bash}) |
tool_use_count |
Number of tool calls in the response |
has_thinking |
Whether extended thinking was active |
is_plan_mode |
Whether Claude Code was in plan mode (read-only tools only) |
stop_reason |
Why the response ended (end_turn, tool_use, max_tokens) |
system_prompt_hash |
SHA256 hash of the system prompt |
Sessions (claude_sessions) — Companion Only
The companion groups requests into sessions with running aggregates:
| Field | Description |
|---|---|
id |
Session ID (UUID) |
started_at / ended_at |
Session duration |
total_requests |
Number of API calls in this session |
total_input_tokens |
Sum of input tokens |
total_output_tokens |
Sum of output tokens |
total_cost |
Cumulative cost |
Useful Queries
Cost Per Developer (Using Metadata)
SELECT
raw_metadata->>'Developer' as developer,
COUNT(*) as requests,
SUM(total_cost) as total_cost
FROM llm_requests
WHERE raw_metadata->>'Developer' IS NOT NULL
GROUP BY 1
ORDER BY total_cost DESC;
Cost Per Developer (Using API Keys)
If using per-developer Majordomo API keys:
SELECT
ak.name as developer,
COUNT(*) as requests,
SUM(lr.total_cost) as total_cost
FROM llm_requests lr
JOIN api_keys ak ON lr.majordomo_api_key_id = ak.id
GROUP BY ak.id, ak.name
ORDER BY total_cost DESC;
Model Usage Breakdown
SELECT
model,
COUNT(*) as requests,
SUM(input_tokens) as input_tokens,
SUM(output_tokens) as output_tokens,
SUM(total_cost) as total_cost
FROM llm_requests lr
JOIN claude_request_details crd ON crd.llm_request_id = lr.id
GROUP BY model
ORDER BY total_cost DESC;
Tool Usage
SELECT
unnest(tool_names) as tool,
COUNT(*) as times_used
FROM claude_request_details
GROUP BY 1
ORDER BY times_used DESC;
Thinking vs Non-Thinking Cost
SELECT
has_thinking,
COUNT(*) as requests,
SUM(lr.total_cost) as total_cost
FROM claude_request_details crd
JOIN llm_requests lr ON crd.llm_request_id = lr.id
GROUP BY has_thinking;
Plan Mode vs Full Mode
SELECT
CASE WHEN is_plan_mode THEN 'Plan Mode' ELSE 'Full Mode' END as mode,
COUNT(*) as requests,
SUM(lr.total_cost) as total_cost
FROM claude_request_details crd
JOIN llm_requests lr ON crd.llm_request_id = lr.id
GROUP BY is_plan_mode;
Daily Spend
SELECT
DATE(requested_at) as date,
COUNT(*) as requests,
SUM(total_cost) as total_cost
FROM llm_requests lr
JOIN claude_request_details crd ON crd.llm_request_id = lr.id
GROUP BY 1
ORDER BY date DESC
LIMIT 30;
Cost Per Project
SELECT
raw_metadata->>'Project' as project,
COUNT(*) as requests,
SUM(total_cost) as total_cost
FROM llm_requests
WHERE raw_metadata->>'Project' IS NOT NULL
GROUP BY 1
ORDER BY total_cost DESC;
Session Summary (Companion Only)
SELECT
cs.id,
ak.name as api_key_name,
cs.started_at,
cs.ended_at,
cs.total_requests,
cs.total_input_tokens,
cs.total_output_tokens,
cs.total_cost
FROM claude_sessions cs
JOIN api_keys ak ON cs.majordomo_api_key_id = ak.id
ORDER BY cs.started_at DESC
LIMIT 20;
Requests in a Session (Companion Only)
SELECT
lr.model,
lr.input_tokens,
lr.output_tokens,
lr.total_cost,
crd.tool_names,
crd.tool_use_count,
crd.has_thinking,
crd.is_plan_mode,
crd.stop_reason,
lr.requested_at
FROM claude_request_details crd
JOIN llm_requests lr ON crd.llm_request_id = lr.id
WHERE crd.session_id = 'your-session-uuid-here'
ORDER BY lr.requested_at;
Sessions API
The gateway exposes REST endpoints for managing Claude Code sessions. These are used by the companion automatically, but you can also query them directly.
| Method | Endpoint | Description |
|---|---|---|
POST |
/api/v1/claude-sessions |
Start a new session |
GET |
/api/v1/claude-sessions |
List sessions (paginated) |
GET |
/api/v1/claude-sessions/{id} |
Get session details |
POST |
/api/v1/claude-sessions/{id}/end |
End a session |
GET |
/api/v1/claude-sessions/{id}/requests |
List requests in a session |
All endpoints require the X-Majordomo-Key header.
# List recent sessions
curl -H "X-Majordomo-Key: mdm_sk_your_key_here" \
http://localhost:7680/api/v1/claude-sessions
# Get session details
curl -H "X-Majordomo-Key: mdm_sk_your_key_here" \
http://localhost:7680/api/v1/claude-sessions/your-session-uuid
Team Deployment
Per-Developer API Keys
Create individual Majordomo API keys for each developer:
./bin/majordomo keys create --name "Alice"
./bin/majordomo keys create --name "Bob"
./bin/majordomo keys create --name "Charlie"
Each developer configures their own key — either in settings.json (simple setup) or passed to the companion.
Shared Key with Developer Metadata
Use a single API key and identify developers via metadata headers:
Simple setup:
{
"env": {
"ANTHROPIC_BASE_URL": "http://localhost:7680",
"ANTHROPIC_CUSTOM_HEADERS": "X-Majordomo-Key: mdm_sk_team_key\nX-Majordomo-Client: claude-code\nX-Majordomo-Developer: alice"
}
}
Companion setup:
majordomo-companion exec \
--gateway http://localhost:7680 \
--api-key mdm_sk_team_key \
--metadata Developer=alice \
-- claude
Shell Alias (Companion)
Add a convenience alias to your shell profile:
alias mcc='majordomo-companion exec --gateway https://majordomo.yourcompany.com --api-key $MAJORDOMO_API_KEY --'
Then just run:
Companion CLI Reference
Usage: majordomo-companion <command> [options]
Commands:
start Start the companion proxy (runs in foreground)
stop Stop a running companion proxy
exec Start the proxy, run a command, then stop on exit
Common Options (start, exec):
--gateway Gateway URL (default: http://localhost:7680)
--api-key Majordomo API key (required)
--port Local port to listen on (default: 0 = random)
--workdir Directory to read .majordomo/metadata.json from
--metadata Metadata key=value pair (repeatable)
Start Options:
--pid-file Path to write PID file (required)
--env-file Path to write ANTHROPIC_BASE_URL export
Stop Options:
--pid-file Path to PID file (required)
Troubleshooting
Requests not appearing in logs
Simple setup:
- Verify the gateway is running:
curl http://localhost:7680/health - Check
ANTHROPIC_BASE_URLis set:echo $ANTHROPIC_BASE_URL - Confirm the Majordomo key is in your custom headers
Companion setup:
- Verify the gateway is running:
curl http://localhost:7680/health - Check the API key is valid:
./bin/majordomo keys list - Try registering a session manually:
claude_request_details rows missing
The gateway only creates claude_request_details rows when:
- The request includes
X-Majordomo-Client: claude-codeheader (orX-Majordomo-ClaudeCode-Session-Id) - The request is to the Anthropic Messages API (
/v1/messages) - The upstream response has HTTP status < 400
If using the simple setup, make sure X-Majordomo-Client: claude-code is in your ANTHROPIC_CUSTOM_HEADERS. If requests are failing upstream, details won't be recorded. Check that requests appear in llm_requests first.
Metadata not showing up
settings.json: Verify headers are newline-separated (\n) in theANTHROPIC_CUSTOM_HEADERSvalue- Companion: Check
.majordomo/metadata.jsonis valid JSON with string values, and--metadataflags usekey=valueformat - Query
raw_metadataonllm_requeststo see what was actually captured
SSL/TLS errors with remote gateway
Make sure the URL includes https://:
Next Steps
- Custom metadata indexing: Configure active metadata keys for faster queries on high-cardinality fields
- Proxy keys: Use Proxy Keys to manage Anthropic API keys centrally so developers don't need their own
- S3 body storage: Enable S3 storage to capture full request/response bodies for debugging
- Production deployment: See the Deployment guide for Docker Compose, Kubernetes, and standalone Docker options