Switchmaxxer is an open-source, local-first LLM reverse-proxy where the CLI, MCP, and observability surfaces are first-class peers — designed from day one to be operated by agents, not just by humans.
# point any OpenAI SDK at Switchmaxxer
client = OpenAI(base_url="http://127.0.0.1:4080/v1")
response = client.chat.completions.create(
model="claude-sonnet-4-6", # named route, not a provider model
messages=[{"role": "user", "content": "Hello"}]
)
Clients talk to Switchmaxxer. Switchmaxxer resolves named routes from the catalog, applies runtime policy from configuration, translates Anthropic / OpenAI message dialects on the fly if needed, and tracks every observation in a local data store off the hot path.
Two dialects on one port: /v1/chat/completions (OpenAI) and
/anthropic/v1/messages (Anthropic). Any compatible SDK connects without modification.
Named routes live in catalog.json alongside service_providers and
models. Per-route api_mode controls outbound dialect; timeout_ms and
streaming limits are policy, not request flags.
Auth injection from api_key_env (or owner-only secrets.json). DNS-pinned
endpoints, screened against private/loopback addresses unless explicitly opted-in per provider.
One in-process observability store powers logs, observations, traces, benchmarks, optimize runs, and the Control Plane Audit Ledger. Off the hot path, queryable from CLI and MCP alike.
Every operator capability is an agent capability. Not a subset. Not a sandbox. The same surface, with capability tiers you grant explicitly.
Drive the gateway directly: gateway run, config validate, routes,
trace, bench, optimize, ledger. Stable exit codes and
--json on every surface that matters.
The same surface, agent-fitted. Capability tiers — read, mutation,
privileged — are granted explicitly in configuration. Your agent operates
Switchmaxxer within the trust envelope you set.
One store, one query layer, one mental model. Traces, benchmarks, optimize decisions, and the Control Plane Audit Ledger live in the same observability database — readable from CLI and MCP.
Point two routes at the same model — claude-sonnet-4-6 direct
and openrouter-claude-sonnet-4-6 through OpenRouter — and let
your agent ask which one is fastest right now?
Run measured benchmarks against any route, any path (gateway / direct / both), any prompt. No stale data. No mystery provenance. Every run lands in the local store.
switchmaxxer bench --routes <a>,<b> --path bothScore routes by cost against catalog rate cards and a reference token workload, or by
latency through the benchmark runtime. Recommendations persist with the run that backed them.
switchmaxxer optimize --model <m> --objective latencyoptimize apply writes a pre-apply catalog snapshot before mutating.
optimize restore rolls back if something goes sideways. Privileged MCP clients can do both.
switchmaxxer optimize apply <run-id> --reload --verifyEvery control-plane mutation writes to the Control Plane Audit Ledger. Inspect with
ledger list / ledger show, or expose to privileged MCP clients.
switchmaxxer ledger list --jsonPoint existing SDK clients at Switchmaxxer. No client code changes required when switching providers or models.
from openai import OpenAI
import os
# Point at Switchmaxxer instead of api.openai.com
client = OpenAI(
base_url="http://127.0.0.1:4080/v1",
api_key=os.environ["SWITCHMAXXER_INBOUND_API_KEY"],
)
# Use a named route — Switchmaxxer resolves the upstream provider
response = client.chat.completions.create(
model="gpt-4o-mini",
messages=[{"role": "user", "content": "Summarize this document."}],
stream=True,
)
for chunk in response:
print(chunk.choices[0].delta.content, end="")
import anthropic
import os
# Point at Switchmaxxer's Anthropic-compatible listener
client = anthropic.Anthropic(
base_url="http://127.0.0.1:4080/anthropic",
api_key=os.environ["SWITCHMAXXER_INBOUND_API_KEY"],
)
# Route names live in catalog.json; api_mode=anthropic-messages
message = client.messages.create(
model="claude-sonnet-4-6",
max_tokens=1024,
messages=[{"role": "user", "content": "Analyze this codebase."}],
)
print(message.content[0].text)
# Validate config + catalog, then start the gateway
./switchmaxxer config validate
./switchmaxxer gateway run
# Test a route through the live runtime path
./switchmaxxer test --route claude-sonnet-4-6
# Send a one-off prompt through a named route
./switchmaxxer invoke --route claude-sonnet-4-6 --prompt "hello"
# Bench two routes for the same model, both paths, JSON output
./switchmaxxer bench --routes claude-sonnet-4-6,openrouter-claude-sonnet-4-6 \
--path both --json
# Ask which route is cheapest, then apply with rollback safety
./switchmaxxer optimize --model claude-sonnet-4-6 --objective cost --json
./switchmaxxer optimize apply <run-id> --route claude-sonnet-4-6 --reload --verify
# Hot-reload config after safe edits
./switchmaxxer gateway reload
Any client that speaks the OpenAI or Anthropic wire protocol connects to Switchmaxxer without modification.
Agents inspect traces, run benchmarks, request optimize recommendations, and apply or restore route changes
— gated by read, mutation, privileged tiers.
Run as a systemd --user service. Logs land in journald and surface through
switchmaxxer gateway logs with normalized JSON.
Node 22 node:sqlite in-process. Observations, traces, benchmarks, optimize runs, and the audit
ledger all share one store.
Upstream provider and inbound protocol. Any OpenAI-compatible client connects to
/v1/chat/completions unchanged.
Upstream provider and inbound protocol. Anthropic-compatible clients connect to
/anthropic/v1/messages on the same port.
Stand a route up against the same model through OpenRouter, then let optimize pick the best
path on cost or latency.
Observations flow into traces, traces feed benchmark runs, benchmark runs back optimize recommendations — and every mutation is ledgered. All one observability store, all queryable from CLI and MCP.
Reconstruct the full lifecycle of any request. Verify trace completeness, repair gaps when something dropped mid-flight, and inspect by request id.
switchmaxxer trace show <request-id> --jsonEvery meaningful event in a request's lifecycle is captured and queryable. Walk observations directly when you need finer grain than a trace summary.
switchmaxxer trace observations --route claude-sonnet-4-6Clients hold the route name; catalog.json holds the binding. Swap the upstream model or
provider without touching a single client.
switchmaxxer routes update <route> --service-provider <p>
One command for the whole observability store; bench and optimize histories also have their own scoped
prune, delete, and clear.
switchmaxxer prune --older-than 30d --jsonSwitchmaxxer is designed around safe defaults and explicit configuration rather than permissive assumptions.
Binds to 127.0.0.1. Inbound auth required unless allow_unauthenticated_gateway
is explicitly opted into on a loopback bind — and even then, browser cross-site request signals are
rejected.
Resolved through api_key_env by default. Optional owner-only secrets.json for
machine-specific overrides. Inline api_key values are gated behind privileged surfaces and
warn during validation.
Config, catalog, and secrets fail closed on group/world permission bits. chmod 0600 or the
gateway refuses to read them. No quiet permissive fallbacks.
Provider endpoints are DNS-pinned and screened against private and loopback addresses unless
allow_private_endpoints is opted in per provider.
read, mutation, privileged are granted explicitly in
configuration. Default is read-only. Mutation and privileged tiers are opt-in for trusted local
automation only.
Every mutation — CLI or MCP — writes to the ledger. Inspect with ledger list /
ledger show. optimize apply snapshots catalog state so
optimize restore can roll it back.
Clone, build, copy the example config and catalog, point your SDK at 127.0.0.1:4080.
# Requires Node.js 22+
git clone https://github.com/<your-org>/switchmaxxer.git
cd switchmaxxer
npm install
npm run build
cp configs/config.example.json config.json
cp configs/catalog.example.json catalog.json
chmod 0600 config.json catalog.json
export SWITCHMAXXER_OPENAI_API_KEY=...
export SWITCHMAXXER_INBOUND_API_KEY=...
./switchmaxxer config validate
./switchmaxxer gateway run