1Lab Overview
Janus is an MCP gateway and capability broker — one small, stable tool surface that sits in front of every downstream MCP server an AI agent might need.
We built Janus while designing agent stacks for our own products and for customers, and it solves a problem that appears the moment an agent becomes genuinely useful: tool sprawl. Every capable agent loads its tools by connecting to MCP servers — an issue tracker, a notes/memory store, a project board, billing, deploy tooling. Connect a dozen of them and every session pays the full context cost of every tool schema, the model tool-selection accuracy degrades, and credentials for all of those services end up scattered across per-agent configs.
Instead of loading N servers into every session, an agent loads one server — Janus — which exposes about seven broker tools and keeps the dozens of downstream tools as an implementation detail behind a registry, a policy engine, a credential broker, a sanitizer, and an audit log.
Small fixed tool surface — the model sees ~7 broker tools instead of 100+ downstream tools.
Just-in-time discovery — capabilities are found search, then describe, then call; never dumped up front.
Deny-by-default policy with risk tiers and environment gates, scoped per agent profile.
Gateway-owned credentials — secrets are injected downstream at call time and never reach the model or the logs.
Every invocation audited, and any change to a tool description auto-quarantines it until a human re-approves.
Janus is open source (Cloud-Ops-Dev/janus), Phases 1 and 2 are implemented and deployed, and the reference deployment currently brokers Beads and Paperclip in read-only mode.
2Architecture
Clients — Claude Code, Codex, or any SSH/REST consumer — reach Janus over MCP or a REST/CLI fallback. Before a call ever touches a downstream server, the gateway resolves it through five cooperating subsystems.
The five subsystems
Registry — the source-of-truth catalog of servers and capabilities (transport, trust level, risk ceiling, environment scope). A public-repo-safe YAML seed defines structure; runtime state lives in a local SQLite store.
Policy engine — a deny-by-default decision per call from the agent profile, risk tier, and environment, resolving to allow, confirm, or deny, always with a human-readable reason.
Credential broker — resolves secret references at call time, injects them into the downstream environment, caches them in memory with a TTL, and never logs or returns them.
Sanitizer — caps output size, redacts secrets, and labels untrusted downstream content before it reaches the model.
Audit + drift — an append-only invocation log, plus descriptor/schema drift detection that auto-quarantines a changed capability until it is re-approved.
The broker surface
The agent only ever sees a handful of stable tools: a capability search that returns a short ranked list (no full schemas), a describe call that returns one capability schema, risk tier, and policy, and a call that runs a policy-checked, credential-injected, audited invocation — plus server inventory/health, a policy explainer, and a recent-audit view.
The design is deliberately dependency-inverted: the broker enforces policy but never decides it, and the local store — not the YAML file — is the runtime authority for which capabilities are approved or quarantined, so a quarantined tool cannot silently re-enable itself after a restart.
3Setup and Deployment
Janus runs on Python 3.11+ and installs with uv. Configuration is entirely public-repo-safe — it contains no secrets, tokens, op:// references, or internal endpoints; connection details and credentials are supplied at runtime through named environment variables.
Install and validate
# 1. Environment
uv venv && uv pip install -e .
# 2. Configure — copy the env template, fill in per-host tokens + downstream endpoints
cp config/janus.env.template janus.env # gitignored
# 3. Validate configuration (the systemd ExecStartPre gate — exits non-zero on problems)
python -m janus --checkServe
python -m janus --serve # REST API (always-on networked surface)
python -m janus --mcp-http # MCP over streamable-HTTP
python -m janus --stdio # MCP over stdio (per-session spawn)Run it as a managed service
Janus ships a systemd --user unit and is built to pass a strict “logout test” — it comes back after a reboot with no shell session present:
cp systemd/janus.service ~/.config/systemd/user/
systemctl --user enable --now janus.service
loginctl enable-linger # survive logout / rebootConfiguration files
servers.yaml— downstream registry: transport, trust level, risk ceiling, environment scope.capabilities.yaml— per-capability summaries, tags, and risk tiers (the searchable surface).profiles.yaml— agent profiles (e.g.default_assistant,infra_operator) mapping risk tiers to allow/confirm/deny per environment.
Day-to-day CLI
bin/janus is a thin REST client for SSH/scripted use; bin/janus-admin is host-local administration that talks to the registry SQLite directly:
bin/janus search "open issues assigned to me"
bin/janus call <capability-id> '{"arg": "value"}'
bin/janus-admin discover # crawl downstreams, refresh observations
bin/janus-admin pending # capabilities awaiting first approval
bin/janus-admin approve <id> # approve + lock the reviewed baseline4Troubleshooting Highlights
A few things we learned running Janus that are worth knowing before you deploy it:
Deny-by-default means new capabilities start uncallable. A freshly discovered tool is pending until a human approves it. This is intentional: it is the human-review gate that makes the model-visible surface trustworthy.
Validation fails loud, by design. Missing tokens or downstream endpoints cause a non-zero exit rather than a half-broken service. It is wired as the systemd start-up gate, so the unit refuses to start misconfigured.
Transport mismatch is the most common config error. Each downstream declares its transport (stdio vs streamable-HTTP). If a server is declared one way but actually speaks the other, the connection fails — fix the declaration, not the code.
Descriptor drift auto-quarantine. If a downstream changes a tool description or schema, Janus quarantines that capability and alerts, rather than silently trusting the new text (a tool-poisoning defense). Review the change and re-approve to re-lock the baseline.
Credentials are fail-loud too. If a required secret reference cannot be resolved at call time, the call fails clearly instead of proceeding without auth — the credential broker never falls back to an unauthenticated request.
5Practical Business Use
Janus is a concrete example of how Novique builds and operates production AI infrastructure: deployment over features, secure by default. The same pattern applies directly to a customer agent stack.
Where it pays off
Lower running cost and better answers. Trimming a session from 100+ tool schemas to ~7 broker tools cuts token cost on every call and measurably improves the model tool selection — fewer wrong-tool mistakes, less prompt bloat.
Credential governance in one place. Instead of copies of API keys spread across every agent config, secrets live with the gateway, are injected at call time, and never appear in a model context or a log. Rotating a key becomes a one-place change.
An audit trail you can hand to compliance. Every capability invocation is logged — which profile called which capability, when, and whether policy allowed it. That is the difference between guessing the agent only did safe things and having the record.
Safe rollout of risky tools. Write, production, and destructive actions sit behind explicit policy and confirmation, scoped per agent profile — so you can grant an autonomous agent read access everywhere while still gating the few actions that can do damage.
If you are standing up AI agents and the tooling is starting to sprawl — too many integrations, scattered keys, no record of what the agent actually did — this is the kind of infrastructure we build and run for clients. Book a consultation and we will help you put a clean broker surface in front of your agent stack.

