Screen MCP tool calls in Gemini CLI

Gemini CLI starts each MCP server in its settings as a local process and talks to it over stdio. agent-chaperone goes in front of that process. It reads each tool call before the server gets it and each result before the agent does, and nothing about the server changes.

One thing about Gemini CLI matters more than the rest here: it strips variables that look like secrets from a server's environment, and TYPESAFE_API_KEY looks like one. This covers MCP servers only. Gemini CLI's own shell commands and file edits do not go through MCP.

Install it #

npm install -g agent-chaperone

A global install means Gemini CLI starts the proxy directly. Running it through npx works too, but adds a package lookup every time a server starts.

Put a server behind it #

Servers live under mcpServers in ~/.gemini/settings.json, and in .gemini/settings.json inside a project. A server before and after:

{
  "mcpServers": {
    "filesystem": {
      "command": "npx",
      "args": ["-y", "@modelcontextprotocol/server-filesystem", "/Users/me/project"]
    }
  }
}
{
  "mcpServers": {
    "filesystem": {
      "command": "agent-chaperone",
      "args": ["--server", "filesystem", "--",
               "npx", "-y", "@modelcontextprotocol/server-filesystem", "/Users/me/project"],
      "env": { "TYPESAFE_API_KEY": "$TYPESAFE_API_KEY" }
    }
  }
}

Everything after -- is the server that would have run anyway. --server names the section of the policy file that applies. The env line is not optional on Gemini CLI, and the next section says why.

agent-chaperone wrap ~/.gemini/settings.json can make the command change, but not the env line, which you add afterwards. It reads the file as strict JSON, and Gemini CLI allows comments in its settings, so a file with comments is refused with an error and left untouched. A server Gemini CLI reaches by url is wrapped too, from 0.3.2. Gemini CLI reaches a url entry over Streamable HTTP, the transport the proxy uses too, unless the entry says "type": "sse", and a "type": "http" becomes stdio once the entry runs a command. An entry that declares SSE, or carries its own headers, is left alone and the output says why. So is one written with httpUrl, the older spelling Gemini CLI has deprecated in favour of url with "type": "http"; make that change and run wrap again.

Give it the API key #

Gemini CLI removes variables from a server's inherited environment when their names suggest a credential, and a name containing KEY is one of the patterns. So a TYPESAFE_API_KEY exported in your shell never reaches the proxy on its own, and every judgment would record that no model was asked.

A variable set in the server's own env is not removed. Writing it as "$TYPESAFE_API_KEY" makes Gemini CLI fill in the value from its own environment when it reads the settings, so the key itself is never written into the file. It has to be exported in the shell Gemini CLI is started from; a missing variable becomes an empty string.

The proxy removes its own keys before it starts the server behind it, so the server never receives TYPESAFE_API_KEY, even though Gemini CLI handed it over.

Check that it is working #

Gemini CLI only starts a local server in a folder you have trusted, and shows it as disconnected anywhere else. /mcp inside a session, or gemini mcp list from the shell, shows each server's state.

Then use the server once from the agent and read what was decided:

agent-chaperone log

Each line is one call or result, with the decision and, in shadow mode, what enforcing would have done instead. A line ending in [not screened] was decided without a model, which on Gemini CLI almost always means the env line is missing.

What this does not cover #

Next #

It starts in shadow mode, which blocks nothing. The MCP guide covers what each screen asks, how to read a shadow-mode log, and when to switch to enforce. The exact wording of every question is in the design document.