# Scope MCP Keys to the Human, Not to the Team

The first time a support engineer asked for MCP access so she could pull customer records without bugging the on-call, we handed her a gateway key. The second PM asked for the same thing, we gave him the same key. By the end of the month, five non-engineers were sharing one long-lived credential with full read on every operation in our OpenAPI surface.

That worked until the support person left. The incident response instinct was to rotate the gateway secret — and we did, at 9am on a Tuesday. Every Cursor session, every Cline setup, every internal agent that had that key baked in broke at the same time. We spent the next two hours cycling keys back into configs that various engineers kept in various places.

We had built a blast radius sized for a five-person team on top of a credential we expected to live forever.

## The primitive is wrong, not the access

The mistake wasn't giving support and PMs agent access. That's a good thing — a support rep who can ask an agent to pull a refund status is a support rep who isn't paging me at 2am. The mistake was treating the gateway key as a team artifact.

A team-wide key conflates four things that should be separate:

- **Identity.** Which human is actually making the call? An audit log that says "support-team" when a refund was issued is not an audit log.
- **Scope.** A support rep looking up a ticket does not need the operation that issues a full refund. A PM reading a metrics endpoint does not need the one that triggers a reindex.
- **Rate.** A PM exploring in Cursor for an afternoon can accidentally generate 800 calls in 20 minutes if the agent loops on a bad query. A team-wide key has no way to absorb that.
- **Revocation.** When someone leaves, you need to kill *their* access without touching anyone else's. Rotating a shared key is a coordinated outage.

## What we changed

We moved to issuing one key per human, with three fields attached:

- A list of allowed operations, drawn from the OpenAPI spec by the role — support gets customer lookup + refund status, PM gets metrics read, nobody gets write except on-call.
- A per-key rate limit (100 req/min) so an agent loop can't take down prod by itself.
- A key name that is the human's email, so the audit log reads like a log.

When someone leaves, we delete their key. Nothing else moves. When a key leaks, we rotate only that one. When support needs a new operation, we add it to their scope — we don't widen the team key.

This is the same reason a gateway should sit in front of the MCP surface at all: the gateway is the place where the OpenAPI spec is the source of truth for what operations exist, and where per-key scoping is enforced against that spec rather than reconstructed in every agent config. We built this into [Powerduck](https://www.powerduck.com/) because we kept re-implementing the same allowlist by hand — the spec already knows which operations are which, the gateway should be the only place that enforces it.

## The non-obvious cost of team-wide keys

The visible cost is the outage when you rotate. The quieter cost is that you stop auditing. Once the key is shared, the audit log is useless — you see 4,000 calls from "support-team" and you have no idea which of the five humans made the refund that ended up on a customer's invoice. The next time support needs access to a sensitive operation, you can't point to a history of who actually used the read-only path, so you say no. That's how non-engineer agent access quietly dies: not with a bang, but with a slow refusal to expand scope because nobody trusts the shared credential.

Per-human keys are a small operational overhead — you issue one when someone joins, delete one when they leave — but they're the difference between "agent access for the whole company" and "agent access for the three engineers who are comfortable rotating keys."
