feat(cli): relay-like CLI closure — target manifest, Codex TOML, Gemini launcher, guards

- canonical executable manifest (bin/cli/cli-manifest.mjs): run/configure/completion
  derive targets, aliases and --model wiring from one table; drift test cross-checks
  manifest x cliRuntime x UI catalog (tests/unit/cli/cli-manifest-drift.test.ts)
- dashboard Codex generator converged to ~/.codex/config.toml (modern Codex v0.137+,
  verified against codex-cli 0.147.0): conservative merge, env_key auth (key never
  written), refuses invalid TOML, reports legacy config.yaml as migration note
- omniroute run gemini: launcher over OmniRoute's /v1beta surface via
  GOOGLE_GEMINI_BASE_URL + isolated GEMINI_CLI_HOME forcing gemini-api-key auth
  (contract proven against @google/gemini-cli 0.50.0); ACP registration kept distinct
- opt-in real smoke harness for upstream CLIs (RUN_CLI_SMOKE=1, credential by env
  NAME, redacted output): tests/integration/upstream-cli-smoke.int.test.ts
- container-guard homologation for POST /api/cli-tools/apply (422 in container,
  dry-run preview allowed, host write passes) + docs; guard untouched
- typecheck: omniglyphAdapter union narrowing, usageTracking typed signatures
  (UsageLike, no any), models.ts isValidModel params — typecheck:core and
  typecheck:noimplicit:core now clean
- relay core (prior session of this effort): omniroute run for 6 CLIs, configure
  picker with per-context favorites/recents, contexts with optional keychain +
  0600 fallback, provider CRUD with recursive redaction, completion updates, docs
This commit is contained in:
Xiangzhe
2026-08-18 08:24:05 -03:00
parent 34bb018d21
commit 0a74bfbdea
50 changed files with 3499 additions and 284 deletions

View File

@@ -18,6 +18,23 @@ There are also two launchers — `omniroute launch` (Claude Code) and
`omniroute launch-codex` (Codex) — that spawn the CLI with the right env injected,
without writing any config at all.
Provider onboarding is available from the same local/remote context. The
API-first commands below keep management authentication separate from provider
credentials and never print a credential in structured output:
```bash
omniroute providers add glm --credential-env GLM_API_KEY --name work
omniroute providers import ./providers.json --dry-run --json
omniroute providers auth openai
omniroute providers edit <connection-id> --default-model glm/glm-5.2
omniroute providers remove <connection-id> --yes
```
For scripts, prefer `--credential-stdin` or `--credential-env`; `--credential`
is retained for controlled local use. `providers remove` requires `--yes` on a
non-interactive terminal, and all five commands honor the active context or the
global `--base-url`/`--api-key` options.
For the one-time, hand-written base setup of the two richest integrations, see the
per-tool deep dives:
@@ -37,23 +54,23 @@ Every command honours the **active context** (set with `omniroute connect`, see
with `--remote` (or an active remote context) it fetches the catalog from that
server and writes the config locally.
| Command | Tool | What it writes | Key flags | Local vs remote |
| -------------------------- | ---------------------------- | --------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------- | --------------- |
| `omniroute setup-codex` | OpenAI Codex CLI | `~/.codex/<name>.config.toml` — one profile per compatible text model (`codex --profile <name>`) | `--remote` `--api-key` `--only` `--dry-run` `--port` `--codex-home` | Both |
| `omniroute setup-claude` | Claude Code | `~/.claude/profiles/<name>/settings.json` — one profile per matched model (`CLAUDE_CONFIG_DIR`) | `--remote` `--api-key` `--only` `--dry-run` `--port` `--claude-home` | Both |
| `omniroute setup-opencode` | OpenCode (openai-compatible) | `~/.config/opencode/opencode.json``omniroute` provider with every catalog model (`opencode -m omniroute/<model>`) | `--remote` `--api-key` `--only` `--model` `--dry-run` `--port` | Both |
| `omniroute setup-cline` | Cline | `~/.cline/data/{globalState,secrets}.json` (CLI mode) + prints VS Code extension settings | `--remote` `--api-key` `--model` `--yes` `--dry-run` `--port` `--cline-dir` | Both |
| `omniroute setup-kilo` | Kilo Code | `~/.local/share/kilo/auth.json` (CLI) + merges `kilocode.*` into VS Code `settings.json` if present | `--remote` `--api-key` `--model` `--yes` `--dry-run` `--port` `--auth-path` `--vscode-settings` | Both |
| `omniroute setup-continue` | Continue / `cn` CLI | `~/.continue/config.yaml``provider: openai` models, key via `${{ secrets.OMNIROUTE_API_KEY }}` | `--remote` `--api-key` `--only` `--dry-run` `--port` `--config-path` | Both |
| `omniroute setup-cursor` | Cursor | Nothing — prints the in-app steps (Cursor config is opaque SQLite) | `--remote` `--api-key` `--only` `--port` | Both |
| `omniroute setup-roo` | Roo Code | `~/.omniroute/roo-settings.json` (import doc) + sets `roo-cline.autoImportSettingsPath` if a VS Code `settings.json` exists | `--remote` `--api-key` `--model` `--yes` `--dry-run` `--port` `--import-path` `--vscode-settings` | Both |
| `omniroute setup-crush` | Crush | `~/.config/crush/crush.json``openai-compat` provider, key via `$OMNIROUTE_API_KEY` | `--remote` `--api-key` `--only` `--dry-run` `--port` `--config-path` | Both |
| `omniroute setup-goose` | Goose | `~/.config/goose/config.yaml` (`GOOSE_PROVIDER`/`OPENAI_HOST`/`GOOSE_MODEL`) + prints env recipe | `--remote` `--api-key` `--model` `--yes` `--dry-run` `--port` `--config-path` | Both |
| `omniroute setup-aider` | Aider | `~/.aider.conf.yml` (`openai-api-base` + `model: openai/<id>`) + prints env recipe | `--remote` `--api-key` `--model` `--yes` `--dry-run` `--port` `--config-path` | Both |
| `omniroute setup-qwen` | Qwen Code | `~/.qwen/settings.json` — V4 `modelProviders.openai` array + `OMNIROUTE_API_KEY` in `~/.qwen/.env` | `--remote` `--api-key` `--model` `--yes` `--dry-run` `--port` `--config-path` `--env-path` | Both |
| `omniroute run <target>` | Runtime launch (generic) | Nothing — spawn `claude`/`codex` with the right env and args | `--remote` `--base-url` `--context` `--provider` `--model` `--api-key` `--dry-run` `--json` `--port` `--profile` `--token` | Both |
| `omniroute launch` | Claude Code | Nothing — spawns `claude` with `ANTHROPIC_BASE_URL`/`ANTHROPIC_AUTH_TOKEN` injected | `--remote` `--api-key` `--token` `--profile` `--port` | Both |
| `omniroute launch-codex` | OpenAI Codex CLI | Nothing — spawns `codex` with the `omniroute` provider injected via `-c` flags | `--remote` `--api-key` `--profile` (`-p`) `--port` | Both |
| Command | Tool | What it writes | Key flags | Local vs remote |
| -------------------------- | ---------------------------- | ----------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------ | --------------- |
| `omniroute setup-codex` | OpenAI Codex CLI | `~/.codex/<name>.config.toml` — one profile per compatible text model (`codex --profile <name>`) | `--remote` `--api-key` `--only` `--dry-run` `--port` `--codex-home` | Both |
| `omniroute setup-claude` | Claude Code | `~/.claude/profiles/<name>/settings.json` — one profile per matched model (`CLAUDE_CONFIG_DIR`) | `--remote` `--api-key` `--only` `--dry-run` `--port` `--claude-home` | Both |
| `omniroute setup-opencode` | OpenCode (openai-compatible) | `~/.config/opencode/opencode.json``omniroute` provider with every catalog model (`opencode -m omniroute/<model>`) | `--remote` `--api-key` `--only` `--model` `--dry-run` `--port` | Both |
| `omniroute setup-cline` | Cline | `~/.cline/data/{globalState,secrets}.json` (CLI mode) + prints VS Code extension settings | `--remote` `--api-key` `--model` `--yes` `--dry-run` `--port` `--cline-dir` | Both |
| `omniroute setup-kilo` | Kilo Code | `~/.local/share/kilo/auth.json` (CLI) + merges `kilocode.*` into VS Code `settings.json` if present | `--remote` `--api-key` `--model` `--yes` `--dry-run` `--port` `--auth-path` `--vscode-settings` | Both |
| `omniroute setup-continue` | Continue / `cn` CLI | `~/.continue/config.yaml``provider: openai` models, key via `${{ secrets.OMNIROUTE_API_KEY }}` | `--remote` `--api-key` `--only` `--dry-run` `--port` `--config-path` | Both |
| `omniroute setup-cursor` | Cursor | Nothing — prints the in-app steps (Cursor config is opaque SQLite) | `--remote` `--api-key` `--only` `--port` | Both |
| `omniroute setup-roo` | Roo Code | `~/.omniroute/roo-settings.json` (import doc) + sets `roo-cline.autoImportSettingsPath` if a VS Code `settings.json` exists | `--remote` `--api-key` `--model` `--yes` `--dry-run` `--port` `--import-path` `--vscode-settings` | Both |
| `omniroute setup-crush` | Crush | `~/.config/crush/crush.json``openai-compat` provider, key via `$OMNIROUTE_API_KEY` | `--remote` `--api-key` `--only` `--dry-run` `--port` `--config-path` | Both |
| `omniroute setup-goose` | Goose | `~/.config/goose/config.yaml` (`GOOSE_PROVIDER`/`OPENAI_HOST`/`GOOSE_MODEL`) + prints env recipe | `--remote` `--api-key` `--model` `--yes` `--dry-run` `--port` `--config-path` | Both |
| `omniroute setup-aider` | Aider | `~/.aider.conf.yml` (`openai-api-base` + `model: openai/<id>`) + prints env recipe | `--remote` `--api-key` `--model` `--yes` `--dry-run` `--port` `--config-path` | Both |
| `omniroute setup-qwen` | Qwen Code | `~/.qwen/settings.json` — V4 `modelProviders.openai` array + `OMNIROUTE_API_KEY` in `~/.qwen/.env` | `--remote` `--api-key` `--model` `--yes` `--dry-run` `--port` `--config-path` `--env-path` | Both |
| `omniroute run <target>` | Runtime launch (generic) | Nothing — spawn `claude`/`codex`/`aider`/`goose`/`opencode`/`qwen` with the right env and args; Qwen uses a temporary isolated home | `--remote` `--base-url` `--context` `--provider` `--model` `--api-key` `--api-key-env` `--dry-run` `--json` `--port` `--profile` `--token` | Both |
| `omniroute launch` | Claude Code | Nothing — spawns `claude` with `ANTHROPIC_BASE_URL`/`ANTHROPIC_AUTH_TOKEN` injected | `--remote` `--api-key` `--token` `--profile` `--port` | Both |
| `omniroute launch-codex` | OpenAI Codex CLI | Nothing — spawns `codex` with the `omniroute` provider injected via `-c` flags | `--remote` `--api-key` `--profile` (`-p`) `--port` | Both |
Notes on flags (verified in the command source):
@@ -76,6 +93,20 @@ Notes on flags (verified in the command source):
a profile written by `setup-claude` / `setup-codex`, plus pass-through args for
the underlying `claude` / `codex` binary.
The interactive picker is also shared by the setup recipes:
```bash
# Pick from the active local or remote model catalog and configure the target.
omniroute configure claude
omniroute configure opencode --provider glm
omniroute configure qwen --model qwen/qwen3.8-max-preview --yes
```
`configure` currently delegates to the tested recipes for `codex`, `claude`,
`opencode`, `qwen`, `aider`, `goose`, `cline`, `continue`, and `kilo`. IDE-only,
MITM, and guide-only catalog entries remain explicit `setup-*`/manual flows and
are not presented as launchable targets.
> `setup-opencode` is the **lightweight openai-compatible** OpenCode integration.
> There is also a richer plugin integration — `omniroute setup opencode` — which
> installs `@omniroute/opencode-plugin`. They are different commands; the table
@@ -118,6 +149,10 @@ omniroute launch-codex # Codex CLI → local OmniRoute
omniroute launch-codex --profile glm52
omniroute run claude --model openai/gpt-5.4
omniroute run codex --model openai/gpt-5.4 --dry-run --json
omniroute run aider --model glm/glm-5.2 -- --message "reply OK"
omniroute run goose --model glm/glm-5.2
omniroute run opencode --model glm/glm-5.2 -- run "reply OK"
omniroute run qwen --model glm/glm-5.2 -- -p "reply OK"
# Explicit command path: pass through whatever comes after --
omniroute run claude -- --print-system-prompt "review this diff"
@@ -202,6 +237,56 @@ outdated), `--apply` (install without prompting), `--changelog`, `--no-backup`,
---
## Google Gemini CLI via `omniroute run gemini`
Contract verified against `@google/gemini-cli` 0.50.0: the CLI honors
`GOOGLE_GEMINI_BASE_URL` and issues `POST /v1beta/models/<model>:generateContent`
(and `:streamGenerateContent?alt=sse`) against it — exactly OmniRoute's native
Gemini surface (`/v1beta`). `omniroute run gemini` wires that automatically:
- `GOOGLE_GEMINI_BASE_URL` → the active OmniRoute base URL (root, no `/v1`);
- `GEMINI_API_KEY` → the resolved OmniRoute credential (option/env/context);
- a **temporary isolated `GEMINI_CLI_HOME`** whose `.gemini/settings.json`
selects `gemini-api-key` auth, so a stored Google OAuth session (Code Assist)
never overrides the OmniRoute-directed launch — removed after exit;
- `--model <id>` injection from `--provider`/`--model`.
```bash
omniroute run gemini --model glm/glm-5.2 -- --skip-trust -p "hello"
```
Gemini's workspace-trust guard still applies in headless mode — pass
`--skip-trust` (or trust the directory interactively) yourself; the launcher
deliberately does not bypass it. This launcher is distinct from the **ACP
registration** (`src/lib/acp/registry.ts`, `gemini --acp`), which remains the
agent-protocol integration for `/dashboard/acp-agents`.
---
## Real smoke sweep (opt-in)
Deterministic launch-plan regression runs in CI (`tests/unit/cli/run-command.test.ts`,
`tests/unit/cli/run-execution.test.ts`). To validate the REAL binaries against a REAL
OmniRoute server, an opt-in harness exists at
`tests/integration/upstream-cli-smoke.int.test.ts`. It never runs automatically
(every sub-test skips unless `RUN_CLI_SMOKE=1`), passes the credential by env-var
NAME (never by value), redacts key-shaped strings from any recorded output, skips
targets whose binary is not installed, and classifies failures as
auth / upstream / config instead of a bare boolean:
```bash
RUN_CLI_SMOKE=1 \
OMNIROUTE_SMOKE_BASE_URL="http://localhost:20128" \
OMNIROUTE_SMOKE_MODEL="<provider/model>" \
OMNIROUTE_SMOKE_API_KEY_ENV="OMNIROUTE_API_KEY" \
node --import tsx/esm --test tests/integration/upstream-cli-smoke.int.test.ts
```
Optional: `OMNIROUTE_SMOKE_TARGETS="codex,opencode,qwen"` restricts the sweep;
`OMNIROUTE_SMOKE_TIMEOUT_MS` overrides the 120s per-target timeout.
---
## See also
- [Claude Code configuration](./CLAUDE-CODE-CONFIGURATION.md) — the deeper Claude Code guide

View File

@@ -10,6 +10,15 @@ Complete guide for using the Codex CLI pointed at OmniRoute as an OpenAI-compati
---
> **TOML is the only effective format.** Modern Codex reads `~/.codex/config.toml`
> exclusively (verified against codex-cli 0.147.0: `codex --help` documents
> `-c/--config` overrides "loaded from `~/.codex/config.toml`"). The old
> `~/.codex/config.yaml` belonged to the legacy npm CLI and is silently ignored.
> The dashboard generator (`/api/cli-tools/apply`, tool `codex`) writes TOML with a
> conservative merge — existing keys and other provider blocks are preserved, the
> API key stays in `OMNIROUTE_API_KEY` (never in the file), and a leftover legacy
> `config.yaml` is reported as a migration note without being touched.
## Ready-to-paste config.toml
Replace `<YOUR_HOST>` and `<YOUR_KEY>` with your values:

View File

@@ -271,8 +271,16 @@ omniroute configure codex
# non-interactive
omniroute configure codex --provider glm --model glm/glm-5.2 --name glm52
# keep a frequently used model at the top of the interactive picker
omniroute configure codex --provider glm --model glm/glm-5.2 --favorite --yes
```
The picker keeps only model IDs (never URLs or credentials) in the local
`model-preferences.json` file, scoped by context and CLI target. Favorites are
shown before recent selections; use `--unfavorite` to remove a selected model
from that context/target list.
The written profile references the inference key by env var
(`OMNIROUTE_API_KEY`) — the secret is never written to disk. For the one-time
base Codex setup (the `[model_providers.omniroute]` block), see
@@ -360,14 +368,20 @@ omniroute contexts remove stg --yes
> revoke the token on the server with `omniroute tokens revoke <id>` to actually
> kill access.
**Export / import** contexts (e.g. to move them between machines — secrets included,
so handle the file carefully):
**Export / import** contexts (e.g. to move them between machines). New contexts persist
only a keychain reference; credentials are not copied into the export when the OS
keychain is available:
```bash
omniroute contexts export --out contexts.json # default: stdout
omniroute contexts import contexts.json # overwrite; --merge to keep existing
omniroute contexts migrate --yes # move legacy plaintext tokens to keychain
```
On headless systems without a usable OS keychain, the CLI falls back to
`config.json` with mode `0600` and prints a one-time warning. Treat exports from
that fallback (and any legacy config before migration) as secret material.
---
## Quick end-to-end check
@@ -409,8 +423,12 @@ omniroute contexts remove 192-168-0-15 --yes # drop the local context (even if
- `omniroute connect` reuses the login brute-force lockout + audit logging.
- Prefer HTTPS or a Tailnet for the transport; a bare host defaults to `http://`
for LAN/Tailscale convenience — pass a full `https://…` URL for TLS.
- The local context file is `~/.omniroute/config.json` (`chmod 600`); tokens are
never printed in logs (masked to a prefix).
- The preferred local context file is `~/.omniroute/config.json` (`chmod 600`)
containing only a `credentialRef`; the token itself is stored in the OS
keychain (`keytar`) and is never printed in logs. Headless installs without a
working native keychain use the same `0600` file as an explicit fallback and
emit a warning once. Use `omniroute contexts migrate --yes` after installing a
keychain backend.
---

View File

@@ -83,6 +83,17 @@ actually meant; `OMNIROUTE_ALLOW_CONTAINER_CONFIG_WRITE=true` does the same for
the server. See
[Docker Guide → Configuring host CLI tools](../guides/DOCKER_GUIDE.md#configuring-host-cli-tools-when-omniroute-runs-in-docker).
The dashboard's **apply endpoint** (`POST /api/cli-tools/apply`) enforces the
same guard: in a container, a write whose target is not bind-mounted from the
host answers **`422`** with `containerEphemeralTarget: true`, the safe error
text and a `hostSetupCommand` (e.g. `omniroute setup-opencode`) to run on the
host instead — nothing is written. `dryRun: true` keeps working in container
mode and returns the generated content + target path without touching disk, so
you can preview from the dashboard and apply on the host. This behavior is
intentional and regression-guarded by
`tests/unit/api/cli-tools/apply-container-guard.test.ts` — never "fix" a 422
by removing the guard.
---
## Source of Truth
@@ -102,6 +113,26 @@ Each entry has these fields (defined in `src/shared/schemas/cliCatalog.ts`):
Entries with `baseUrlSupport: "none"` are **not shown** in the dashboard pages — they are registered in the MITM backlog for plan 11 (see `_tasks/features-v3.8.6/refactorpages/_orchestration/_plan11-mitm-backlog.md`).
### Capability tiers (cataloged × detectable × configurable × launchable)
Not every cataloged tool is detectable, configurable or launchable. Each tier has one
declaring source, and a drift test keeps them aligned:
| Tier | Meaning | Declared in |
| ---------------- | ------------------------------------------------------------------ | ----------------------------------------------------------------- |
| **Cataloged** | Appears in the dashboard catalog (name, vendor, docs, config type) | `src/shared/constants/cliTools.ts` (`CLI_TOOLS`) |
| **Detectable** | Binary/config detection, health checks, config paths | `src/shared/services/cliRuntime.ts` (`CLI_TOOLS` runtime catalog) |
| **Configurable** | Supported by `omniroute configure <cli>` (setup recipe exists) | `bin/cli/cli-manifest.mjs` (`configure: true`) |
| **Launchable** | Supported by `omniroute run <target>` (env/args injection defined) | `bin/cli/cli-manifest.mjs` (`run: true`) |
`bin/cli/cli-manifest.mjs` is the canonical executable manifest for the CLI command
surfaces: `run`, `configure` and the shell-completion generators all derive their
target lists, alias resolution (for example `kilocode`/`kilo-code`/`kilo_cli``kilo`)
and `--model` flag wiring from it. The drift guard
`tests/unit/cli/cli-manifest-drift.test.ts` asserts that the manifest, the runtime
catalog, the UI catalog and every consumer surface stay in sync — a target added to
one surface without the others fails the suite instead of drifting silently.
---
## 1. CLI Code's Catalog (25 tools)
@@ -384,14 +415,26 @@ Use the unified Anthropic gateway root for Claude Code. Do not append `/v1` here
#### OpenAI Codex
Modern Codex (v0.137+) reads `~/.codex/config.toml` only — the old
`config.yaml` belongs to the legacy npm CLI and is silently ignored. The API
key stays in the `OMNIROUTE_API_KEY` environment variable (`env_key`), never
inside the file:
```bash
mkdir -p ~/.codex && cat > ~/.codex/config.yaml << EOF
model: auto
apiKey: sk-your-omniroute-key
apiBaseUrl: http://localhost:20128/v1
mkdir -p ~/.codex && cat > ~/.codex/config.toml << EOF
model_provider = "omniroute"
[model_providers.omniroute]
name = "OmniRoute"
base_url = "http://localhost:20128/v1"
env_key = "OMNIROUTE_API_KEY"
requires_openai_auth = false
EOF
export OMNIROUTE_API_KEY="sk-your-omniroute-key"
```
Full reference (profiles, `wire_api`, context windows): [CODEX-CLI-CONFIGURATION.md](../guides/CODEX-CLI-CONFIGURATION.md).
**Test:** `codex "what is 2+2?"`
---
@@ -613,10 +656,19 @@ omniroute providers list --json
omniroute providers test <id|name> # Test one configured connection
omniroute providers test-all # Test every active connection
omniroute providers validate # Local-only structural validation
omniroute providers add <provider> --credential-env PROVIDER_KEY
omniroute providers import ./providers.json --dry-run --json
omniroute providers auth <provider> # Existing OAuth flow
omniroute providers edit <id|name> --default-model <model>
omniroute providers remove <id|name> --yes
```
> `providers available` reads the OmniRoute catalog; `providers list/test/test-all/validate`
> read the local SQLite database directly and do not require the server to be running.
`providers add/import/auth/edit/remove` are API-first and therefore work against
the active local or remote context. Credential input should use
`--credential-stdin` or `--credential-env`; `--dry-run --json` reports only
redacted presence/shape. `providers available` reads the OmniRoute catalog;
`providers list/test/test-all/validate` retain their local SQLite behavior and
do not require the server to be running.
### Recovery & Reset

View File

@@ -474,6 +474,7 @@ detection above).
| `OMNIROUTE_ISSUE_AGENT_ENABLED` | `false` | `src/app/api/issue-agent/runs/route.ts` | Enables the offline/local Issue Agent recorded-triage endpoint. Leave disabled unless explicitly running local recorded-triage workflows. |
| `OMNIROUTE_ISSUE_AGENT_TIMEOUT_MS` | _(unset)_ | `src/lib/issueAgent/execution.ts` | Timeout (ms) for a single Issue Agent recorded-triage run. Clamped to an internal maximum; falls back to the built-in default when unset or invalid. |
| `OMNIROUTE_CONTEXT` | _(active context)_ | `bin/cli/program.mjs`, `bin/cli/api.mjs` | CLI remote-mode context/profile for `omniroute` commands; overrides the active context in the local contexts store. Equivalent to `--context <name>`. |
| `OMNIROUTE_CONTEXT_KEYCHAIN_DISABLED` | `0` | `bin/cli/contexts.mjs` | Disable the optional `keytar` OS-keychain backend for CLI context credentials. When enabled, credentials remain in `config.json` mode `0600` and the CLI emits a one-time fallback warning; intended for deliberate headless/container operation. |
| `OMNIROUTE_MCP_ENFORCE_SCOPES` | `true` | `open-sse/mcp-server/server.ts` | Enforce scope-based access control on MCP tool calls. |
| `OMNIROUTE_MCP_SCOPES` | _(all)_ | `open-sse/mcp-server/server.ts` | Comma-separated scopes: `admin`, `combos`, `health`, `models`, `routing`, `budget`, `metrics`, `pricing`, `memory`, `skills`. |
| `OMNIROUTE_MCP_COMPRESS_DESCRIPTIONS` | `false` | `open-sse/mcp-server/descriptionCompressor.ts` | Compress MCP tool descriptions before serializing the manifest. Enable values: `1`, `true`, `on`. |