The MCP proxy
Sunglasses runs your MCP server as a child process and sits between it and your client. It enforces only once a person has approved that server's tool snapshot at an interactive terminal. Until then nothing is forwarded and nothing is inspected, and installing it is not protection.
Two states, and only one of them protects anything
The proxy has a before and an after, and they are not degrees of the same thing. Before approval it refuses every call and scans nothing. After approval it inspects both directions and withholds a message the engine blocks. Every sentence on this page belongs to one state or the other, and says which.
| State | What your client gets | What is inspected |
|---|---|---|
| Wrapped, not approved | Every tools/list and tools/call comes back as a typed refusal, APPROVAL_REQUIRED. initialize is answered normally. | Nothing. status: not_run, inspected_utf8_bytes: 0 — the call was never forwarded, so the server was never even asked. |
| Approved at a terminal | Ordinary traffic passes and returns its real result. A message carrying a credential comes back as SUNGLASSES_WITHHELD with the reason, the rule, the bytes inspected and the rule ids. | Both directions, in the credential lane — what your client sends, and what the server returns. |
The refusal is the honest part. A gate that opened itself would be a gate you never agreed to, so the first state is the default and it is reached by every client that is not a person at a keyboard.
1) Install — it edits your config, it never creates one
sunglasses install <name> rewrites one entry in an MCP config so that server launches through Sunglasses instead of directly, and records enough on disk to put it back exactly. Aimed at a path that does not exist it refuses and names the file, because a client's server list is that client's file and a guessed one would sit where the client is not looking.
sunglasses install github — edits ./.mcp.json by default. --config <path> names a different one.
The entry's command becomes your interpreter running -m sunglasses.proxy, with your original server command after --. It also stores the artifact path and its SHA-256 alongside, so the wrapping can be checked later rather than assumed.
SUNGLASSES install failed — cannot read /path/to/.mcp.json: [Errno 2] No such file or directory. It names the target and changes nothing.
sunglasses uninstall github reads the record, checks the copy still matches the digest taken at install time, and restores it byte for byte. If the record or the copy is not something it can vouch for, it refuses rather than writing bytes it cannot verify.
Wrapping is not approval. The command exits 0 and your entry is wrapped, and at that moment nothing is being inspected.
2) Approve — a person, at a terminal, for one server
Start the server once from your client. The first tool listing writes a snapshot of the server's tool descriptors, and the refusal your client receives carries the two values you need to approve it — server_id and snapshot_sha256, in the error payload itself, so you never go hunting in a state directory.
sunglasses proxy approve <server_id> --snapshot <snapshot_sha256>, run at a real terminal. It shows you the tool descriptors and asks.
Run it without a terminal and it exits 1, unrecorded: approving records that a human viewed this capture, and this is not an interactive terminal, so nobody did. Answering no at a real prompt also exits 1 and records nothing.
The snapshot hash covers the descriptors, so two different servers exposing the same tools carry the same snapshot_sha256. The approval is stored against the server_id, which differs. Approving one leaves the other refusing until it is approved on its own.
Changing the command behind a familiar tool list does not inherit the approval you already gave. A server that presents the same descriptors as one you trust is still a server you have not approved.
The install record, the capture, the approval and the receipts all sit under one state root, ~/.sunglasses/proxy/.
3) What is blocked, once approved
With the snapshot approved the mediator inspects messages in both directions and withholds one whose content the engine blocks, returning a typed JSON-RPC error rather than a silent drop. The reason code, the rule, the bytes it inspected and the rule ids that fired are all in the payload.
| Direction | What happens | Reason code |
|---|---|---|
| Your client → the server | A credential in a tool call does not reach the server. Verified by reading the receiving server's own input, not by asking the proxy. | PROHIBITED_SECRET |
| The server → your client | A credential in a tool result is withheld from your client. The -API rule ids are the tool-result channel, so the direction is readable from the payload. | PROHIBITED_CONTENT |
| Ordinary traffic | tools/list returns the real list and a benign call returns its real result. | — |
The last row is not filler. Without it, the two above would be equally satisfied by something that refused every message it was given.
What this does not claim
What ships is the credential lane, not general inspection of everything a tool returns. The honest boundary is a property of one rule's anchor, and it is wider than a single channel.
The credential lane covers the known credential formats. Naming a tool result as somewhere an attack arrives is a description of the threat, not a claim about what is inspected on the way back.
GLS-SD-010It is line-anchored, and that is a limit in every channel. It matches an assignment at the start of a line, so a KEY=value sitting inside a JSON string, behind a quote, or simply indented is not matched on any channel — not in a tool result, and not in a file or a message either, which are channels it does declare.
When the value is in a known credential format the GLS-SD-001 family still catches it everywhere — GLS-SD-001 on file, message and web content, GLS-SD-001-API in a tool result. What is actually uncovered is an assignment whose value has no recognisable shape: a password, a DSN, an internal token, once it is embedded.
Needs a different anchor, which is a new rule with its own fixtures rather than a channel added to this one.
Nothing here is a claim about any other tool.