Rightshold™ Sign up

Answers · MCP security

How do I test that my MCP server's tool errors don't leak environment variables or secrets?

Short answer: Plant fake canary secrets, make every tool fail on purpose, and search everything that comes back for the canaries. Start the server with each environment variable it reads set to a unique, obviously fake value (for example RS_CANARY_7f3a9c), and point upstream URLs at a stub that returns 401, 500, timeouts and malformed bodies. Then call each tool with bad arguments and through each failure path, and search the full JSON-RPC responses, both protocol errors and isError tool results, plus the server's stderr and logs, for any canary, Authorization header, token-bearing URL or stack trace. The usual leaks are an HTTP client error serialized whole (axios errors carry the request config, headers included, unless you set its redact option), an upstream URL with an api_key query parameter in the message, a stack trace or a dumped config object. Fix it by returning a short message, a stable error code and a request ID to the model, and logging the detail server-side with secrets redacted. Then keep the canary test in CI.

Checklist

  1. Inventory what the server can see: every environment variable, config file and token it loads. Each one needs a canary value in the test environment, and real secrets should never be present there.
  2. Use unique canaries: a distinct, greppable value per secret (a fake sk- key, a fake bearer token, a database URL with a fake password), so a hit tells you exactly which one leaked.
  3. Force every failure path: invalid and missing arguments, wrong types, oversized input, an upstream stub returning 401, 403, 404, 429 and 500, timeouts, DNS failure, malformed JSON, and a missing file or permission error for filesystem tools.
  4. Capture everything the client could see: the full JSON-RPC response for protocol errors and for tool results with isError: true, any structuredContent, progress and log notifications, and stderr for stdio servers, since clients may show or store it.
  5. Search the captures for canaries, Authorization or Cookie headers, URLs with credentials or key parameters, file paths and stack traces; fail the test on any hit.
  6. Fix at the boundary: wrap each tool handler so unexpected errors become a short message, a stable code and a request ID; never return error.toString() of an HTTP client error or a serialized error object.
  7. Redact before logging: strip known secret keys and header values from anything written to logs, set axios's redact option or build your own error object, and drop query strings from logged upstream URLs.
  8. Run it in CI on every change, alongside a test that the server starts without production secrets, so new tools and dependency upgrades can't quietly reintroduce a leak.

Why tool errors are a leak path

Where secrets usually hide in errors

Ask Rightshold (free, no key)

Add the MCP server https://www.rightshold.com/mcp to Claude, Cursor, ChatGPT, Grok or VS Code, then ask: “Before I connect this MCP server to my agent, is it a listed server, what tools does it expose, and have its tool definitions changed since they were recorded?”

One-click setup

{
  "tool": "precall_check",
  "arguments": {
    "url": "<the MCP server's https URL>"
  }
}

Rightshold does not run your server, see its environment or test its error handling. The canary test runs in your own CI; Rightshold checks a server's listing and tool definitions.

Sources

Related

Not legal advice. Updated 2026-10-29. JSON · Markdown · All answers

Get a free API key in one call

No card and no email needed. 100 free credits; the license and policy checks stay free without a key.

curl -s -X POST https://www.rightshold.com/v1/accounts/signup -H "Content-Type: application/json" -d '{"accept_terms":true}'
export RIGHTSHOLD_API_KEY=rs_...   # the api_key from the reply (shown once)
curl -s https://www.rightshold.com/v1/licenses/check -H "Authorization: Bearer $RIGHTSHOLD_API_KEY" \
  -H "Content-Type: application/json" -d '{"license":"MIT","intended_use":{"commercial_use":true}}'