diff --git a/scripts/indexnow.mjs b/scripts/indexnow.mjs index 94dc0fc..bd0c0ae 100644 --- a/scripts/indexnow.mjs +++ b/scripts/indexnow.mjs @@ -39,6 +39,10 @@ const URLS = [ `https://${HOST}/guides/claude-code`, `https://${HOST}/guides/secret-exfiltration`, `https://${HOST}/guides/ecc`, + `https://${HOST}/guides/cursor`, + `https://${HOST}/guides/codex`, + `https://${HOST}/guides/vscode`, + `https://${HOST}/guides/gemini-cli`, `https://${HOST}/docs`, `https://${HOST}/docs/design`, `https://${HOST}/docs/hooks`, diff --git a/src/data/guides.ts b/src/data/guides.ts index 3b149b2..9787db2 100644 --- a/src/data/guides.ts +++ b/src/data/guides.ts @@ -66,6 +66,42 @@ export const GUIDES: readonly Guide[] = [ blurb: "ECC checks your setup and makes the agent look before it edits. This checks what each call does and what each result says, and runs next to ECC's hooks without changing them.", }, + { + slug: 'cursor', + h1: 'Screen MCP tool calls in Cursor', + title: 'Screen MCP tool calls in Cursor | agent-chaperone', + description: + "Put Cursor's MCP servers behind agent-chaperone: the mcp.json change, getting the API key to the proxy, checking it works, and what it does not cover.", + blurb: + 'Cursor keeps servers in JSON that wrap can edit. The change, how the key reaches the proxy, and what Cursor runs outside MCP.', + }, + { + slug: 'codex', + h1: 'Screen MCP tool calls in Codex', + title: 'Screen MCP tool calls in Codex | agent-chaperone', + description: + "Put Codex's MCP servers behind agent-chaperone: the config.toml change, forwarding the API key past a cleared environment, and timeouts.", + blurb: + 'Codex keeps servers in TOML and starts them with a cleared environment, so the key has to be forwarded by name. The change, by hand, and the timeouts to set.', + }, + { + slug: 'vscode', + h1: 'Screen MCP tool calls in VS Code', + title: 'Screen MCP tool calls in VS Code | agent-chaperone', + description: + "Put VS Code agent mode's MCP servers behind agent-chaperone: the mcp.json change, the API key, when wrap can edit the file, and what it misses.", + blurb: + 'VS Code lists servers under servers rather than mcpServers and allows comments in the file. The change, the key, and where wrap has to give way to a hand edit.', + }, + { + slug: 'gemini-cli', + h1: 'Screen MCP tool calls in Gemini CLI', + title: 'Screen MCP tool calls in Gemini CLI | agent-chaperone', + description: + "Put Gemini CLI's MCP servers behind agent-chaperone, and get the API key past the filter that strips anything named like a credential.", + blurb: + "Gemini CLI strips variables named like credentials from a server's environment, the API key included. The change, and the env line that gets the key through.", + }, ]; export const SITE = 'https://agentchaperone.dev'; diff --git a/src/data/schema.ts b/src/data/schema.ts index ae41aaa..e5248fc 100644 --- a/src/data/schema.ts +++ b/src/data/schema.ts @@ -207,7 +207,7 @@ export function guidesIndexSchema(): unknown { '@id': GUIDES_ID, name: 'agent-chaperone guides', description: - "Setting up screening for MCP servers, for a client's own shell and file tools, for prompt injection arriving in tool results, for secrets on their way out, and next to ECC's hooks.", + "Setting up screening for MCP servers, including in Cursor, Codex, VS Code and Gemini CLI, for a client's own shell and file tools, for prompt injection arriving in tool results, for secrets on their way out, and next to ECC's hooks.", url: `${SITE}/guides`, isPartOf: { '@id': SITE_ID }, about: { '@id': APP }, diff --git a/src/pages/guides/codex.astro b/src/pages/guides/codex.astro new file mode 100644 index 0000000..34c6985 --- /dev/null +++ b/src/pages/guides/codex.astro @@ -0,0 +1,182 @@ +--- +import Base from '../../layouts/Base.astro'; +import { REPO } from '../../data/benchmark'; +import { findGuide, guideUrl } from '../../data/guides'; +import { guideSchema } from '../../data/schema'; + +const guide = findGuide('codex'); +--- + + +
+

{guide.h1}

+

+ Codex starts each MCP server in its configuration 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. +

+

+ Two things about Codex differ from the JSON-configured clients, and both matter here: the + configuration is TOML, and a server does not see your environment unless you forward it. This + covers MCP servers only. Codex's own shell commands and patches do not go through MCP. +

+
+ +
+

+ Install it{' '} + + # + +

+
npm install -g agent-chaperone
+

+ A global install matters more on Codex than elsewhere. Codex gives a server a fixed time to + start, and running the proxy through npx spends part of it looking the package + up. +

+
+ +
+

+ Put a server behind it{' '} + + # + +

+

+ Servers live in ~/.codex/config.toml, one [mcp_servers.<name>]{' '} + table each, and in .codex/config.toml inside a project Codex trusts. The CLI and + the IDE extension read the same file. agent-chaperone wrap only edits JSON, so on + Codex the change is made by hand. A server before: +

+
{`[mcp_servers.filesystem]
+command = "npx"
+args = ["-y", "@modelcontextprotocol/server-filesystem", "/Users/me/project"]`}
+

And after:

+
{`[mcp_servers.filesystem]
+command = "agent-chaperone"
+args = ["--server", "filesystem", "--",
+        "npx", "-y", "@modelcontextprotocol/server-filesystem", "/Users/me/project"]
+env_vars = ["TYPESAFE_API_KEY"]
+startup_timeout_sec = 30`}
+

+ The old command moves after --, unchanged. --server names the + section of the policy file that applies, and using the table's own name keeps the two easy to + match. Keep any env or cwd the entry already had: they still reach + the server. +

+
+ +
+

+ Give it the API key{' '} + + # + +

+

+ Codex starts a server with a cleared environment and passes back only a short list of + variables: HOME, PATH, USER, the locale and a few + others. A TYPESAFE_API_KEY exported in your shell is not on that list, so without + the env_vars line above the proxy never sees it and every judgment records that + no model was asked. +

+

+ env_vars forwards the variable by name from the environment Codex was started in, + so the key is never written into the file. If Codex was started some other way and the key + does not arrive, env sets it directly, at the cost of the key sitting in{' '} + config.toml in plain text: +

+
{`env = { TYPESAFE_API_KEY = "..." }`}
+

+ The proxy removes its own keys before it starts the server behind it, so the server never + receives TYPESAFE_API_KEY either way. +

+
+ +
+

+ Timeouts{' '} + + # + +

+

+ startup_timeout_sec is how long Codex waits for a server to start, and{' '} + tool_timeout_sec how long it waits for one call. Codex's documentation and its + source disagree on the defaults, 10 and 60 seconds against 30 and 300, so set the startup one + yourself rather than depend on either. +

+

+ Screening adds at most one request to the model backend per call and one per result, and{' '} + the benchmark method records how long those took. A tool timeout + that suited the server before still suits it unless its calls were already close to the limit. +

+
+ +
+

+ Check that it is working{' '} + + # + +

+

+ /mcp inside a Codex session, or codex mcp list from the shell, shows + whether the server started. 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 means the key did not arrive. +

+
+ +
+

+ 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. +

+
+ diff --git a/src/pages/guides/cursor.astro b/src/pages/guides/cursor.astro new file mode 100644 index 0000000..75d61de --- /dev/null +++ b/src/pages/guides/cursor.astro @@ -0,0 +1,185 @@ +--- +import Base from '../../layouts/Base.astro'; +import { REPO } from '../../data/benchmark'; +import { findGuide, guideUrl } from '../../data/guides'; +import { guideSchema } from '../../data/schema'; + +const guide = findGuide('cursor'); +--- + + +
+

{guide.h1}

+

+ Cursor starts each MCP server you configure 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. +

+

+ This covers MCP servers only. Cursor's own terminal commands and file edits do not go through + MCP, so the proxy does not see them. +

+
+ +
+

+ Install it{' '} + + # + +

+
npm install -g agent-chaperone
+

+ A global install means Cursor 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{' '} + + # + +

+

+ Cursor reads servers from ~/.cursor/mcp.json for every project and from{' '} + .cursor/mcp.json inside one project, under mcpServers. The editor + and the Cursor CLI read the same files. +

+

+ agent-chaperone wrap makes the edit. It prints what it would change and writes + nothing until you add --write, and it keeps the original beside the file. +

+
agent-chaperone wrap ~/.cursor/mcp.json
+agent-chaperone wrap ~/.cursor/mcp.json --write
+

A server entry 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"]
+    }
+  }
+}`}
+

+ Everything after -- is the server that would have run anyway.{' '} + --server names the section of the policy file that applies, after the name you + gave the server in Cursor. --unwrap takes it all back out. +

+
+ +
+

+ Give it the API key{' '} + + # + +

+

+ The model screens need TYPESAFE_API_KEY. A server started by Cursor gets the + environment Cursor itself was started with, so a key exported in your shell profile reaches + the proxy once Cursor has been restarted. Cursor's help says the same of any variable a server + needs. +

+

+ To keep the key out of your shell profile, put it in a file of NAME=value lines + and point the entry at it with envFile, which Cursor supports for local servers: +

+
{`"filesystem": {
+  "command": "agent-chaperone",
+  "args": ["--server", "filesystem", "--", "npx", "-y", "@modelcontextprotocol/server-filesystem", "/Users/me/project"],
+  "envFile": "/Users/me/.config/agent-chaperone/env"
+}`}
+

+ The proxy removes its own keys before it starts the server behind it, so the server never + receives TYPESAFE_API_KEY either way. +

+

+ Without a key the proxy still starts, the deterministic rules still run, and every judgment + records that no model was asked. It never refuses to start because a key is missing. +

+
+ +
+

+ Check that it is working{' '} + + # + +

+

+ The server should show as connected under Customize, then MCPs. If it does not, the Output + panel's MCP Logs has what the process printed. In the CLI, agent mcp list{' '} + shows the same status. +

+

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. agent-chaperone log --follow prints them as they happen. A + line ending in [not screened] was decided without a model, which means the key + did not arrive; the proxy also says so in MCP Logs when it starts. +

+
+ +
+

+ 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. +

+
+ diff --git a/src/pages/guides/gemini-cli.astro b/src/pages/guides/gemini-cli.astro new file mode 100644 index 0000000..78c7e63 --- /dev/null +++ b/src/pages/guides/gemini-cli.astro @@ -0,0 +1,175 @@ +--- +import Base from '../../layouts/Base.astro'; +import { REPO } from '../../data/benchmark'; +import { findGuide, guideUrl } from '../../data/guides'; +import { guideSchema } from '../../data/schema'; + +const guide = findGuide('gemini-cli'); +--- + + +
+

{guide.h1}

+

+ 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. It also wraps every server in the file, so use it on a file whose servers + all run a local command. +

+
+ +
+

+ 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. +

+
+ diff --git a/src/pages/guides/index.astro b/src/pages/guides/index.astro index c951064..f1f419f 100644 --- a/src/pages/guides/index.astro +++ b/src/pages/guides/index.astro @@ -6,7 +6,7 @@ import { guidesIndexSchema } from '../../data/schema'; const title = 'Guides | agent-chaperone'; const description = - 'Screening MCP servers, a client’s own shell and file tools, prompt injection in tool results and outbound secrets, and running next to ECC’s hooks.'; + 'Screening MCP servers in Cursor, Codex, VS Code and Gemini CLI, a client’s own tools, prompt injection, outbound secrets, and running next to ECC’s hooks.'; ---
agent-chaperone wrap ~/.claude.json
 agent-chaperone wrap ~/.claude.json --write
+

+ Each client keeps its configuration somewhere different and hands a server a different + environment, which decides whether the API key reaches the proxy at all. The setup for{' '} + Cursor, Codex,{' '} + VS Code and Gemini CLI has a + page each. +

Screening needs an API key for the model backend. Without one the proxy still starts and the deterministic rules still run, and every judgment records that no model was asked. It never diff --git a/src/pages/guides/vscode.astro b/src/pages/guides/vscode.astro new file mode 100644 index 0000000..bfc0f8e --- /dev/null +++ b/src/pages/guides/vscode.astro @@ -0,0 +1,191 @@ +--- +import Base from '../../layouts/Base.astro'; +import { REPO } from '../../data/benchmark'; +import { findGuide, guideUrl } from '../../data/guides'; +import { guideSchema } from '../../data/schema'; + +const guide = findGuide('vscode'); +--- + + +

+

{guide.h1}

+

+ VS Code starts each MCP server you configure for agent mode 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. +

+

+ This covers MCP servers only. The terminal commands and file edits the agent runs through VS + Code's own tools do not go through MCP, so the proxy does not see them. +

+
+ +
+

+ Install it{' '} + + # + +

+
npm install -g agent-chaperone
+

+ A global install means VS Code 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{' '} + + # + +

+

+ VS Code reads servers from .vscode/mcp.json in a workspace and from the{' '} + mcp.json in your user profile, which MCP: Open User Configuration opens. + Both list servers under servers, not mcpServers. A server before and + after: +

+
{`{
+  "servers": {
+    "filesystem": {
+      "type": "stdio",
+      "command": "npx",
+      "args": ["-y", "@modelcontextprotocol/server-filesystem", "\${workspaceFolder}"]
+    }
+  }
+}`}
+
{`{
+  "servers": {
+    "filesystem": {
+      "type": "stdio",
+      "command": "agent-chaperone",
+      "args": ["--server", "filesystem", "--",
+               "npx", "-y", "@modelcontextprotocol/server-filesystem", "\${workspaceFolder}"]
+    }
+  }
+}`}
+

+ Everything after -- is the server that would have run anyway, and VS Code still + fills in variables like {'${workspaceFolder}'} before it starts anything.{' '} + --server names the section of the policy file that applies. +

+

+ agent-chaperone wrap can make the edit, with two limits on VS Code. It reads the + file as strict JSON, and VS Code allows comments in mcp.json, so a file with + comments is refused with an error and left untouched. And it wraps every server in the file, + so use it on a file whose servers all run a local command. Otherwise edit the entry by hand as + above. +

+
agent-chaperone wrap .vscode/mcp.json
+agent-chaperone wrap .vscode/mcp.json --write
+

A new server can also be added from the command line, straight into your user profile:

+
{`code --add-mcp '{"name":"filesystem","command":"agent-chaperone","args":["--server","filesystem","--","npx","-y","@modelcontextprotocol/server-filesystem","/Users/me/project"]}'`}
+
+ +
+

+ Give it the API key{' '} + + # + +

+

+ The model screens need TYPESAFE_API_KEY. A server started by VS Code gets VS + Code's own environment, and on macOS VS Code reads your shell profile when it starts, + including when it is opened from the Dock. A key exported in .zshrc or{' '} + .bashrc reaches the proxy once VS Code has been restarted. If VS Code shows an + error about resolving your shell environment, starting it with code . from a + terminal avoids the problem. +

+

+ To keep the key out of your shell profile, point the entry at a file of{' '} + NAME=value lines with envFile: +

+
{`"filesystem": {
+  "type": "stdio",
+  "command": "agent-chaperone",
+  "args": ["--server", "filesystem", "--", "npx", "-y", "@modelcontextprotocol/server-filesystem", "\${workspaceFolder}"],
+  "envFile": "/Users/me/.config/agent-chaperone/env"
+}`}
+

+ The proxy removes its own keys before it starts the server behind it, so the server never + receives TYPESAFE_API_KEY either way. +

+

+ Without a key the proxy still starts, the deterministic rules still run, and every judgment + records that no model was asked. It never refuses to start because a key is missing. +

+
+ +
+

+ Check that it is working{' '} + + # + +

+

+ A server in .vscode/mcp.json only starts in a trusted workspace. Run{' '} + MCP: List Servers, pick the server, and Show Output has what the process + printed, including a line from the proxy if it started without a key. +

+

Then use the server once from agent mode 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 means the key did not arrive. +

+
+ +
+

+ 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. +

+
+