Airlock can proxy MCP (Model Context Protocol) tool servers alongside LLM providers. All MCP tool calls flow through the same guardrail pipeline as LLM requests.
Add entries to mcp_servers in config.yaml. Each server names a transport
(usually stdio), a command, its args, and any env it needs:
mcp_servers:
newscatcher:
transport: stdio
command: python3
args: ["-m", "airlock.mcp_servers.newscatcher_server"]
env:
NEWS_CATCHER_API_KEY: os.environ/NEWS_CATCHER_API_KEYTwo LiteLLM constraints govern every stdio server — read before adding one. They are not Airlock rules; they come from the LiteLLM proxy that runs the servers, and they bite at tool-discovery time (often silently) rather than at startup. See Command allowlist and No variable expansion below.
LiteLLM only launches stdio MCP servers whose command basename is on a fixed allowlist:
deno docker node npx python python3 uvx
The check is os.path.basename(command), so /opt/venvs/x/bin/python passes
(basename python) but a custom launcher like ado-mcp or my-tool is rejected
with a 403 ... not in the allowlist when a client first lists tools.
To allow extra commands, list their basenames (comma-separated) in
LITELLM_MCP_STDIO_EXTRA_COMMANDS in .env:
# .env — allow the `ado-mcp` and `my-tool` launchers
LITELLM_MCP_STDIO_EXTRA_COMMANDS=ado-mcp,my-toolPrefer routing through an allow-listed launcher when you can — e.g. point
command at a venv's python/python3 and run the server as a module or script
(below) instead of adding a bespoke binary to the allowlist.
LiteLLM does not expand ${HOME}, ~, or any environment variable inside an
MCP command or args entry. ${HOME}/projects/x/server.py is taken literally
and fails to resolve (the launcher tries to open a path with a literal ${HOME}
segment). Always use literal absolute paths:
# WRONG — ${HOME} is not expanded, the server fails to start
command: ${HOME}/projects/my-tool/.venv/bin/python
# RIGHT — literal absolute path
command: /home/alice/projects/my-tool/.venv/bin/pythonBecause absolute host paths are machine-specific, keep servers that use them in
config.local.yaml, not in the
shared config.yaml; leave those local additions uncommitted.
Module via python3 -m — cwd-independent, requires the package installed in
the command's interpreter. python3 is allow-listed:
my_server:
transport: stdio
command: python3
args: ["-m", "my_package.server"]Script with its own venv — point command at the venv interpreter (basename
python, allow-listed) so the script's dependencies (e.g. the mcp SDK) resolve.
Use absolute paths for both the interpreter and the script:
my_server:
transport: stdio
command: /home/alice/projects/my-server/.venv/bin/python
args: ["/home/alice/projects/my-server/mcp-server/server.py"]Installed script via uvx — uvx is allow-listed and resolves the tool in an
ephemeral environment (note: uv alone is not allow-listed — use uvx, or
python3/python from a venv):
my_server:
transport: stdio
command: uvx
args: ["my-mcp-tool"]Custom launcher binary — allowed only after you add its basename to
LITELLM_MCP_STDIO_EXTRA_COMMANDS (see Command allowlist):
ado_mcp:
transport: stdio
command: /home/alice/projects/ado-mcp/.venv/bin/ado-mcp # basename `ado-mcp`
args: ["--profile", "full"]
env:
ADO_ORG_URL: os.environ/ADO_ORG_URL
ADO_PAT: os.environ/ADO_PATOther runtimes (node, npx, deno, docker) are allow-listed and supported.
Use os.environ/VAR_NAME to pass environment variables from Airlock's .env to
the MCP server. Unlike command/args, these env values are resolved by
Airlock from the process environment. Airlock validates the references at startup
and exits with a clear error if any referenced variable is unset (MCP env
errors are fatal; see Operations → Startup Validation).
MCP servers with absolute host paths (a venv interpreter or a custom binary under
someone's home directory) do not belong in the tracked config.yaml. Keep
them in config.local.yaml and pull it in with LiteLLM's include: (start from
config.local.yaml.example). The repository ships an empty mapping as the
default; keep machine-specific additions as uncommitted local edits:
# config.yaml — tracked include; config.local.yaml defaults to {}
include: ["config.local.yaml"]# config.local.yaml (machine-specific local override; do not commit additions)
mcp_servers:
ado_mcp:
transport: stdio
command: /home/alice/projects/ado-mcp/.venv/bin/ado-mcp
args: ["--profile", "full"]
env:
ADO_ORG_URL: os.environ/ADO_ORG_URL
ADO_PAT: os.environ/ADO_PAT
newscatcher: # copied from config.yaml — see the replace caveat
transport: stdio
command: python3
args: ["-m", "airlock.mcp_servers.newscatcher_server"]
env:
NEWS_CATCHER_API_KEY: os.environ/NEWS_CATCHER_API_KEYThree things to get right, or servers silently disappear on the next restart:
- The included file
mcp_serversreplaces the main one — it does not merge. LiteLLM's_process_includesextends list values but overwrites dict values likemcp_servers. Soconfig.local.yamlmust list every MCP server you want at runtime, including ones already inconfig.yaml(e.g.newscatcher) — anything you leave out is dropped. - Keep machine-specific content uncommitted. The tracked
config.local.yamlstarts as{}so the include is safe on a fresh checkout. Local server definitions are a deliberate working-tree customization and must not contain credentials; useos.environ/VAR_NAMEfor secrets. - Use absolute paths and mind the allowlist in
config.local.yamlexactly as inconfig.yaml— the constraints above apply equally to included files.
All MCP tool calls flow through PII redaction, keyword blocking, and threat detection automatically. The MCP Tool Guard adds tool-specific allowlist/blocklist and argument sanitization (AIRLOCK_MCP_ALLOWED_TOOLS / AIRLOCK_MCP_BLOCKED_TOOLS). No extra configuration needed.
Airlock ships a NewsCatcher CatchAll MCP server
(airlock.mcp_servers.newscatcher_server, stdio transport) for news
search. It exposes two tools:
newscatcher_search— submit a query, poll for results (up to ~3 min), return recordsnewscatcher_search_quick— same, with a shorter 60-second timeout
mcp_servers:
newscatcher:
transport: stdio
command: python3
args: ["-m", "airlock.mcp_servers.newscatcher_server"]
env:
NEWS_CATCHER_API_KEY: os.environ/NEWS_CATCHER_API_KEYInstall the extra with pip install airlock-llm[search] and set
NEWS_CATCHER_API_KEY in .env. For general web search exposed as a
chat model, see the Tavily provider in
Configuration.
Use the TUI Config screen (key 4) to view MCP server status and start/stop/restart managed servers.
MCP servers are launched lazily and most failures surface only when a client
first lists tools — so a clean startup log does not prove a server works.
After adding/editing a server or restarting the proxy, confirm tool discovery
directly (the REST helper is mounted at /mcp-rest; the bare /mcp path is the
streaming transport and only speaks SSE):
# Lists the aggregated tools across all healthy MCP servers (auth required)
curl -s -H "Authorization: Bearer $AIRLOCK_MASTER_KEY" \
http://localhost:4000/mcp-rest/tools/list | python3 -m json.toolIf a server is misconfigured you will see it in the proxy stderr log as one of:
403 ... not in the allowlist→ add its basename toLITELLM_MCP_STDIO_EXTRA_COMMANDS.No such file or directorywith a literal${HOME}/~in the path → replace with an absolute path.Connection closed/ModuleNotFoundErrorat init → the interpreter lacks the server's dependencies; pointcommandat a venv that has them.