parse_mcp_config
Python · package toolnexus · SPEC §2 · python/src/toolnexus/mcp_source.py
def parse_mcp_config(input: str | dict[str, Any]) -> McpConfigReads MCP server configuration and normalises it into a flat dict of server name → config. It
connects to nothing — no child processes, no HTTP. That is the whole point: it is the cheap,
synchronous check you can run before paying for load_mcp.
When to use it
Section titled “When to use it”- Validate at startup or in a test, so a typo in
mcp.jsonfails immediately rather than halfway through connecting to five servers. - Inspect or modify config before loading — filter servers by environment, inject a header, disable one in CI.
- Accept config from somewhere other than a file — a database, an env var, an API response.
Why this and not just calling load_mcp
Section titled “Why this and not just calling load_mcp”It accepts a path or a dict, wrapped under mcpServers, servers, or mcp — three
spellings in the wild — and returns one shape.
Examples
Section titled “Examples”1. Parse the shared fixture from disk
Section titled “1. Parse the shared fixture from disk”This is examples/mcp.json, the fixture every port is tested against.
from toolnexus import parse_mcp_config
# A path is read and json-parsed for you.config = parse_mcp_config("examples/mcp.json")
# The `mcpServers` wrapper is unwrapped — you get the servers directly.assert sorted(config.keys()) == ["everything", "example-remote"]
local = config["everything"]assert local["type"] == "local"assert local["command"] == ["npx", "-y", "@modelcontextprotocol/server-everything"]assert local["timeout"] == 30000
remote = config["example-remote"]assert remote["type"] == "remote"assert remote["url"] == "https://example.com/mcp"# Disabled servers are still returned — parsing does not filter.assert remote["enabled"] is False
print("ok:", ", ".join(sorted(config.keys())))2. Three wrapper spellings, one result
Section titled “2. Three wrapper spellings, one result”mcpServers, servers and mcp all mean the same thing.
from toolnexus import parse_mcp_config
server = {"type": "local", "command": ["echo", "hi"]}
wrapped = parse_mcp_config({"mcpServers": {"a": server}})alt = parse_mcp_config({"servers": {"a": server}})short = parse_mcp_config({"mcp": {"a": server}})
# All three normalise to the same flat dict.for c in (wrapped, alt, short): assert list(c.keys()) == ["a"] assert c["a"]["command"] == ["echo", "hi"]
print("ok: 3 spellings ->", ",".join(wrapped.keys()))3. Validate before loading, and fail loudly
Section titled “3. Validate before loading, and fail loudly”The pattern this function exists for — check the config, then decide whether to connect.
from toolnexus import parse_mcp_config
def validate(raw): config = parse_mcp_config(raw) enabled, problems = [], []
for name, cfg in config.items(): # Disabled either way round: `enabled: false` or `disabled: true`. if cfg.get("disabled") is True or cfg.get("enabled") is False: continue enabled.append(name)
if cfg.get("type") == "remote": if not cfg.get("url"): problems.append(f"{name}: remote server without a url") elif not cfg.get("command"): problems.append(f"{name}: local server without a command")
return enabled, problems
good_enabled, good_problems = validate({ "mcpServers": { "ok_local": {"type": "local", "command": ["npx", "server"]}, "ok_remote": {"type": "remote", "url": "https://example.com/mcp"}, "off": {"type": "local", "command": ["x"], "enabled": False}, }})assert good_enabled == ["ok_local", "ok_remote"]assert good_problems == []
_, bad_problems = validate({ "mcpServers": { "broken_remote": {"type": "remote"}, "broken_local": {"type": "local", "command": []}, }})assert len(bad_problems) == 2
# A missing file raises rather than returning a half-config.try: parse_mcp_config("does-not-exist.json") raise AssertionError("expected an error")except FileNotFoundError: pass
print("ok:", ",".join(good_enabled), "| problems:", len(bad_problems))Accepted input
Section titled “Accepted input”| Input | Behaviour |
|---|---|
"./mcp.json" |
Read from disk and json.loaded. Raises if missing or malformed. |
{"mcpServers": {...}} |
Unwrapped. |
{"servers": {...}} |
Unwrapped — alternate spelling. |
{"mcp": {...}} |
Unwrapped — alternate spelling. |
Server config
Section titled “Server config”| Field | Applies to | What it is |
|---|---|---|
type |
both | "local" or "remote". |
command |
local | Argv list, e.g. ["npx", "-y", "server"]. |
environment / env |
local | Extra environment for the child process. |
cwd |
local | Working directory for the child. |
url |
remote | Streamable-HTTP endpoint. |
headers |
remote | ${ENV_VAR} values expand at call time and are never logged. |
enabled / disabled |
both | Preserved by parsing; honoured by load_mcp. |
timeout |
both | Per-server connect/call budget in ms. |
tools |
both | Per-server allowlist keyed on the original tool name. |
See also
Section titled “See also”load_mcp— parse and connectlist_mcp_tools— what each server would exposecreate_toolkit— takesmcp_configin exactly these forms