Repository servers require approval
Cloning an untrusted repository must not grant its author a process on the operator's machine. Decisions live outside the repository and are pinned by digest.
The problem
A repository may include .mcp.json and .pi/mcp.json. Cloning an untrusted repository and opening a session must not grant its author a process on the operator's machine with the operator's credentials. Servers from those scopes remain unavailable until someone on the machine approves them.
/piagent-mcp get internal # inspect first
/piagent-mcp approve internal # or reject, or reset
In a terminal, omit the slash: piagent-mcp get internal. Both surfaces store the same decision in the same file.
approve prints the definition before recording consent. Approving without reading is exactly what this gate is designed to prevent.
Three properties
The decision lives outside the repository
It is stored in ~/.pi/piagent-mcp-approvals.json. If a repository could approve itself, the gate would not exist. A fork, rename, or second checkout starts at pending.
Pinned by digest
Approval means consenting to what the server runs. Changing the command, URL, or arguments creates a new decision and returns the state to approval-changed.
Enforced at tool call
The guard handles both the proxy form (mcp with input.server) and direct tools. A proxy-only gate could be bypassed by enabling directTools.
Limit
The adapter owns connections, so the extension cannot stop one from opening. It blocks every tool call, not the connection itself. The residual risk is the connection and any remote log that records it.
Servers in global and pi-global do not pass through this gate. A repository cannot place a server in those scopes, and asking users to approve their own machine configuration would add no security value.
Servers supplied through imports
MCP configuration supports an imports key that reads server definitions from other tools. Imported servers are not directly declared in one of the four scopes, which creates another path around a naive approval gate.
Of the six import kinds, only vscode points to a path inside the project: .vscode/mcp.json. It travels with a clone and requires approval like any directly declared repository server.
| Situation | Handling |
|---|---|
| Any layer imports a repository-relative kind | The server requires approval even when the import is declared globally, because the loaded content still lives in the repository. |
| A repository scope imports any kind | The server requires approval because the repository selected what the machine should run. |
| A repository scope imports a format that cannot be read, such as Codex TOML | Block every tool call when that file exists. A present but unreadable source is not equivalent to an empty configuration. |
| remove, enable, or disable targets an imported server | Reject the operation and name the actual file to edit. Rewriting another tool's configuration with the platform writer would corrupt ownership and format. |
| Repository configuration combines directTools with toolPrefix: "none" | Block every tool call. Without a prefix, the server identity cannot be recovered for approval lookup, while blocking only the proxy would let the unprefixed direct tools pass. |
piagent-mcp doctor names the importing file, its import kinds, unreadable layers, and import targets that exist but cannot be read. These states must not collapse into "no servers declared".
For full-block states, doctor and list explain the reason and exit 1, including list --json. Exit status answers whether MCP can operate, not whether the command printed rows. The guard, slash command, and CLI consume the same state.
The gate cache is scoped by project and recomputed when the signature of every file used by readers changes, including currently missing paths. This lets a newly created imported file or a personal global change become visible immediately.
The adapter merges settings across all four layers for the session. Therefore directTools in one file and toolPrefix: "none" in another still form the unsafe combination. The report identifies both files.
The SCOPE column names the layer that declared imports, not the location that defined the server. A server imported by global configuration may still require approval when its source is in the clone.
Design decisions
| Decision | Reason |
|---|---|
| Gate repository-supplied servers only | Only .mcp.json and .pi/mcp.json travel with a clone. User-owned machine configuration needs no repository consent. |
| Store decisions outside the repository | ~/.pi/piagent-mcp-approvals.json prevents a repository from approving itself. |
| Pin the definition digest | Consent applies to the exact command, arguments, and URL. A change returns the server to pending. |
| Preview the merged definition but hash the repository-owned fragment | The adapter merges duplicate IDs by key, so preview must show what will run. Hashing only the repository-controlled fragment prevents unrelated global edits from invalidating consent. |
| Block at tool_call | Cover both proxy and direct tools without a bypass. Direct tools are traced through their configured server prefix. |
The ideal point would be before connection, but that requires owning the connection. Piagent chooses the furthest point it actually controls and states the residual risk explicitly.
Migration
Servers already present in .mcp.json or .pi/mcp.json become pending-approval after update. Run /piagent-mcp approve <name> once per project. Shipped templates are empty, so most projects are unaffected; session notices and doctor name every server requiring review.