"""The tracked MCP configs must never carry a live credential.
``mcp_servers.d/*.json`` is committed, so a token written there is in git
history for good — and ``GET /admin/mcp/config`` hands the same value to the
admin client. A config therefore *names* the variable (``Bearer ${NAVI_MCP_X_TOKEN}``)
and the value lives in the service ``.env``; see ``docs/mcp.md``.
This is the guard on that invariant: it is what fails when someone pastes a
token back into a config file, by hand or through ``PUT /admin/mcp/config``.
"""
import json
import re
from pathlib import Path
import navi.mcp.config as mcp_config
from navi.tools._internal.redact import is_sensitive_key
REPO_ROOT = Path(mcp_config.__file__).resolve().parents[2]
CONFIG_DIR = REPO_ROOT / "mcp_servers.d"
_PLACEHOLDER = re.compile(r"\$\{([A-Za-z_][A-Za-z0-9_]*)(?::-([^}]*))?\}")
# Non-credential settings a config may map out of `.env`. Not secrets: the
# placeholder is there so the value stays out of a tracked file (one `.env`
# serves every machine), and the fallback form keeps a host without one working
# — see `docs/mcp.md#secrets`. A setting belongs in `env`, never in a header.
SETTINGS_VARIABLES = {"SEARXNG_URL"}
# The credential-bearing servers, and the variable each one names. Keep in step
# with `deploy/env.template`.
EXPECTED_VARIABLES = {
"gnexus-creds": "NAVI_MCP_GNEXUS_CREDS_TOKEN",
"gntodo": "NAVI_MCP_GNTODO_TOKEN",
"hard-panel": "NAVI_MCP_HARD_PANEL_TOKEN",
"synapse": "NAVI_MCP_SYNAPSE_TOKEN",
"tgclient": "NAVI_MCP_TGCLIENT_TOKEN",
}
def _names(value: object) -> list[str]:
return [match.group(1) for match in _PLACEHOLDER.finditer(str(value))]
def _configs() -> dict[str, dict]:
return {
path.stem: json.loads(path.read_text(encoding="utf-8"))
for path in sorted(CONFIG_DIR.glob("*.json"))
}
def test_every_sensitive_header_or_env_value_is_a_placeholder():
offenders = [
f"{server}: {field}.{key}"
for server, cfg in _configs().items()
for field in ("headers", "env")
for key, value in (cfg.get(field) or {}).items()
if is_sensitive_key(key) and "${" not in str(value)
]
assert not offenders, (
"literal credential in a tracked config — replace it with "
f"`${{NAVI_MCP_<SERVER>_TOKEN}}` and put the value in .env: {offenders}"
)
def test_placeholder_names_are_credentials_or_known_settings():
names = {
name
for cfg in _configs().values()
for field in ("headers", "env")
for value in (cfg.get(field) or {}).values()
for name in _names(value)
}
assert names, "no placeholders in any config — did a token get pasted back in?"
unexpected = [
name
for name in names
if not name.startswith("NAVI_MCP_") and name not in SETTINGS_VARIABLES
]
assert not unexpected, (
"unexpected placeholder names — a credential is NAVI_MCP_*, a plain "
f"setting must be listed in SETTINGS_VARIABLES: {sorted(unexpected)}"
)
def test_settings_placeholders_live_in_env_blocks():
"""`headers` carry credentials; a SearXNG URL there would mean someone
mapped a setting through the wrong field."""
offenders = [
f"{server}: headers.{key}"
for server, cfg in _configs().items()
for key, value in (cfg.get("headers") or {}).items()
if any(name in SETTINGS_VARIABLES for name in _names(value))
]
assert not offenders, f"a plain setting belongs in `env`: {offenders}"
def test_the_credential_servers_name_their_variable():
configs = _configs()
for server, variable in EXPECTED_VARIABLES.items():
assert server in configs, f"{server}.json is gone — update this test with it"
assert configs[server]["headers"]["Authorization"] == f"Bearer ${{{variable}}}"