Securing MCP Servers and AI Tool Chains with KeyZero
How to use KeyZero to manage secrets for MCP servers, prevent credential leakage in tool responses, and secure AI agent tool chains.
The Model Context Protocol (MCP) is becoming the standard interface between AI agents and external tools. MCP servers expose capabilities — web requests, database queries, file operations, API integrations — that AI agents invoke through a structured protocol. Every MCP server that touches an external service needs credentials. How those credentials are managed determines whether your AI tool chain is secure or a secret leakage pipeline.
What MCP Is
MCP (Model Context Protocol) is a protocol for AI agents to discover and invoke tools provided by external servers. An MCP server runs as a separate process and communicates with the AI agent over stdio or HTTP. The server exposes a set of tools with typed parameters, and the agent calls them by name.
A typical MCP setup in Claude Code's .mcp.json:
{
"mcpServers": {
"database": {
"command": "node",
"args": ["./mcp-servers/database-server.js"]
},
"github": {
"command": "python",
"args": ["./mcp-servers/github-server.py"]
}
}
}
Each of these servers likely needs credentials: a database connection string, a GitHub personal access token, API keys for external services.
The Secret Management Problem in MCP
MCP servers need credentials, and the current default approaches all have weaknesses:
Hardcoded in Server Code
The worst option. Credentials in source code end up in Git history, code reviews, and every developer's local clone.
Passed as Environment Variables via .env
Better than hardcoding, but .env files persist on disk as plaintext. On shared machines or CI runners, any process running as the same user can read them.
Passed as Command-Line Arguments
Visible in process listings (ps aux). Stored in shell history. Logged by process supervisors.
The Deeper Problem: Tool Response Leakage
Even when credentials are managed correctly at the server level, there is a subtler issue. If an MCP server resolves a credential and includes it in a tool response — intentionally or through an error message — that credential enters the AI agent's context window. From there it is sent to the LLM provider on every subsequent turn.
Example: A database MCP server executes a query that fails. The error message includes the connection string: connection to postgres://admin:s3cret@prod-db:5432/app failed. The agent receives this error, includes it in its context, and the database password is now in the LLM provider's request logs.
How KeyZero Integrates with MCP
KeyZero provides two integration patterns for MCP servers:
Pattern 1: Wrapping MCP Servers with kz run
The simplest approach. Use kz run to resolve secrets and inject them as environment variables when starting the MCP server process:
{
"mcpServers": {
"database": {
"command": "kz",
"args": ["run", "--", "node", "./mcp-servers/database-server.js"]
}
}
}
The MCP server receives resolved secrets as environment variables. No .env file on disk, no hardcoded credentials.
For AI agent workloads, add blind mode:
{
"mcpServers": {
"database": {
"command": "kz",
"args": ["run", "--blind", "--", "node", "./mcp-servers/database-server.js"]
}
}
}
Now the MCP server process receives masked tokens instead of real credentials. The MITM proxy swaps them for real values on outbound connections. For a detailed explanation of how this proxy works, see blind mode explained.
Pattern 2: KeyZero as an MCP Server
KeyZero itself can run as an MCP server, exposing resolve and fetch tools directly to the AI agent:
{
"mcpServers": {
"keyzero": {
"command": "kz",
"args": ["server", "start", "--bundle", "./bundle.yaml", "--mcp"]
}
}
}
This exposes two tools:
resolve: Runs the full pipeline (JWT verification, policy evaluation, secret resolution) and returns a masked token (KZ_TOK_xxxx) — the real credential is only available through the blind-mode proxy at the network boundary, never directly to the agentfetch: Resolves the credential and makes an authenticated HTTP request on behalf of the agent — the agent receives the API response but never sees the raw credential
The fetch tool is the preferred approach for AI agents because the credential never enters the agent's context at any point. The resolve tool is useful for non-agent workloads (CI scripts, startup hooks) where the calling process is trusted.
Example: Securing a GitHub MCP Server
Consider a GitHub MCP server that needs a personal access token to interact with the GitHub API.
Step 1: Create .keyzero.toml
[secrets]
GITHUB_TOKEN = { provider = "keychain", ref = "github-pat" }
[production.secrets]
GITHUB_TOKEN = { provider = "aws", ref = "sm://prod/github-pat" }
Step 2: Store the Secret
kz put --missing
# Prompts: Enter value for GITHUB_TOKEN (provider: keychain, ref: github-pat):
Step 3: Configure MCP with KeyZero
{
"mcpServers": {
"github": {
"command": "kz",
"args": ["run", "--blind", "--", "python", "./mcp-servers/github-server.py"]
}
}
}
Step 4: Add Connection Control
Restrict the MCP server to only communicate with GitHub's API:
[blind]
[[blind.connections]]
allow = true
host = "api.github.com"
[[blind.connections]]
allow = false
host = "*"
Now the GitHub MCP server:
- Receives a masked token (
kz_masked_...) instead of the real PAT - Can only make outbound connections to
api.github.com - Has its masked tokens swapped for real credentials by the MITM proxy at the network edge
- Cannot exfiltrate the token to any other host
Blind Mode for MCP: Preventing Tool Response Leakage
The combination of blind mode and MCP creates a layered defense:
Layer 1 — Secret resolution: Secrets are fetched from the vault at runtime, never stored on disk.
Layer 2 — Token masking: The MCP server process receives masked tokens. Even if the server logs its environment or includes environment variables in tool responses, only masked tokens are exposed.
Layer 3 — Network-edge swapping: The MITM proxy swaps masked tokens for real values in outbound requests. The real credential appears only on the wire between the proxy and the upstream service.
Layer 4 — Connection control: The MCP server can only reach approved hosts. Even if a malicious tool or dependency attempts to send data to an attacker-controlled server, the connection is blocked.
Error Message Safety
With blind mode active, if a database driver includes the connection string in an error message, the message contains only the masked token:
connection to postgres://admin:kz_masked_a1b2c3...@prod-db:5432/app failed
The agent sees this error, but the real password is never exposed.
Using KeyZero's fetch Tool for Maximum Isolation
For the highest level of secret isolation, use KeyZero's MCP fetch tool instead of passing credentials to MCP servers at all:
{
"mcpServers": {
"keyzero": {
"command": "kz",
"args": ["server", "start", "--bundle", "./bundle.yaml", "--mcp"]
}
}
}
The agent calls fetch with a resource reference and a target URL:
fetch(
jwt: "<agent-jwt>",
resource: "github/pat",
resolver: "gh",
url: "https://api.github.com/repos/myorg/myrepo/issues",
method: "GET"
)
KeyZero resolves the credential, injects it based on credential_location (e.g., header:Authorization:Bearer), makes the HTTP request, and returns only the response body and status. The agent never holds any form of the credential — not even a masked token.
This is the zero-trust ideal: the agent has the capability to make authenticated API calls without possessing any credential material. To enforce fine-grained rules about which agents can access which secrets, layer in policy-based access control for machine identities.
Summary of Integration Options
| Approach | Secret Exposure to MCP Server | Secret Exposure to Agent | Setup Complexity |
|---|---|---|---|
.env file | Full plaintext | Via tool responses | Low |
kz run (direct) | Plaintext in env vars | Via tool responses | Low |
kz run --blind | Masked tokens only | Masked tokens via tool responses | Low |
KeyZero MCP fetch | None — KeyZero makes the request | None — agent sees only response | Medium |
For AI agent workloads, kz run --blind wrapping MCP servers provides strong isolation with minimal setup. For maximum security, KeyZero's own MCP fetch tool eliminates credential exposure entirely.