MCP Strategies on AWS, Part 4: Governance in Code
written by Stefan Christoph
- 7 minutes readThe hosting post bounded who can reach an MCP server. This one goes inside it. Governance, in the AWS guidance, covers authentication and authorization, controlling load, and operational metrics [1]. I am going to make the first two runnable (both demos are on GitHub) because the principle behind them is the single most important idea in the document, and it is easy to nod along to and hard to actually feel until you watch it work.
Token isolation: the story worth running
Here is the canonical example from the guidance, almost verbatim. An admin user asks an agent to clone a production database to pre-prod. That task needs two permissions: read, to copy the data, and create, to make the target. It does not need delete.
But agents are non-deterministic. The model might invent a step that was never asked for: a “let me clean up the old copy” flourish that tries to delete production. If the agentic system reused the admin’s own credentials, which include delete, that hallucination would succeed and production would be gone. The guidance’s principle, which it calls out as the headline: MCP tools must use their own purpose-generated, scoped-down tokens, and user credentials must not propagate through the system [1].
The same hallucinated delete, two outcomes. A scoped read-create token denies it and production survives. Reusing the admin’s credentials executes it and production is gone.
I built this against a mock database service that checks the token’s scope on every operation, then ran the identical hallucinated workflow under two tokens. This is the actual output:
Measured output of token_isolation.py:
[scoped-read-create] scopes=['create', 'read']
- read prod (ok)
- create preprod (ok)
- delete prod (DENIED — token 'scoped-read-create' lacks scope 'delete')
=> SAFE — prod still exists
[admin-all] scopes=['create', 'delete', 'read']
- read prod (ok)
- create preprod (ok)
- delete prod (EXECUTED!)
=> DISASTER — prod was deleted
That is the whole argument in eight lines. The model did the same wrong thing in both runs. The only difference was the token it held, and that difference was the gap between a normal Tuesday and an incident.
The enforcement is unremarkable code, which is the point: it belongs in the tool, on every call:
From token_isolation.py, the tool authorizes every operation:
def _authorize(self, token, scope):
if token.audience != self.server_id:
raise PermissionDenied(f"audience mismatch: token for '{token.audience}'")
if not token.allows(scope):
raise PermissionDenied(f"token '{token.name}' lacks scope '{scope.value}'")
Notice the first check. The guidance, following the MCP specification, says to validate the token’s aud (audience) claim so a token minted for one server cannot be replayed against another [2]. The demo enforces that too: a token addressed to a different server is rejected before any scope check runs.
On AWS, the service that manages these scoped tokens for agents (both machine-to-machine workload tokens and user-delegated tokens) is Amazon Bedrock AgentCore Identity, which keeps them in a token vault encrypted with AWS KMS [3]. The demo is a model of what that service does; you would not hand-roll token storage in production.
The honest limit
I want to be precise about what this buys you, because it is easy to oversell. Token isolation limits the blast radius of a bad model step. It does not prevent the step. The model still tried to delete production. If the workflow had legitimately needed delete (say, a tool whose job is to tear down environments), a scoped token would have happily allowed it, and you would be relying on other controls. Scoping is necessary and it is not sufficient. The complete answer pairs it with read and write separation, human confirmation for destructive actions, and the injection defenses I flagged as gaps in the series overview. Token isolation is the floor, not the ceiling.
Controlling load, so an agent can back off
The second governance lever is rate limiting. The guidance recommends limiting primarily at the MCP-server level, using per-tool limits to protect downstream resources, and (the part agents need) returning the limit state in HTTP headers so the agent can self-manage instead of retrying blindly [1]:
X-RateLimit-Limit: 100
X-RateLimit-Remaining: 45
X-RateLimit-Reset: 1640995200
I implemented a per-tool limiter that emits exactly those headers. Calling a tool limited to three per window, five times, produces:
Measured output of rate_limit.py:
call 1: 200 OK [ok] X-RateLimit-Limit=3 X-RateLimit-Remaining=2 ...
call 2: 200 OK [ok] X-RateLimit-Limit=3 X-RateLimit-Remaining=1 ...
call 3: 200 OK [ok] X-RateLimit-Limit=3 X-RateLimit-Remaining=0 ...
call 4: 429 Too Many Requests [per-tool limit] X-RateLimit-Remaining=0 ...
call 5: 429 Too Many Requests [per-tool limit] X-RateLimit-Remaining=0 ...
The fourth call is rejected with a 429 and a remaining count of zero. A well-behaved agent reads X-RateLimit-Reset and waits, rather than retrying into the wall. The guidance adds a useful rule for tools that fan out: if a tool calls several downstream APIs, set its limit to the lowest those APIs allow, so the tool never becomes the thing that overruns them.
There is also load shedding, which is for overload that no single caller is responsible for. The demo sets a global ceiling across all tools:
Measured output: global load shedding:
call 6 -> b_y_get 200 OK [ok]
call 7 -> a_x_get 429 [load-shed (global limit)]
call 8 -> b_y_get 429 [load-shed (global limit)]
Neither tool hit its own limit, but their combined load tripped the global ceiling. That is the difference between per-tool limiting (protect a downstream resource) and load shedding (protect the fleet).
The third lever: metrics
I did not write code for the third governance area, operational metrics, because it is about what you measure rather than a mechanism you implement, but it is worth naming. The guidance recommends watching token usage per tool, tool-selection accuracy, the number of tools registered (an early warning for the context-window problem from the tool-design post), and tool latency and success rates. AWS describes building golden datasets for regression testing, generated synthetically from historical invocation logs, to measure whether the agent picks the right tool and fills its parameters correctly across turns [1]. That is the operational-excellence half of governance, and it is how you catch tool drift before a user does.
Running it yourself
The companion code for this post is on GitHub at stechr/schristoph-blog-samples › 2026-06-12-mcp-strategies/governance. Both demos are pure standard library: no AWS, no network, no credentials:
python run_demo.py
You will see the token-isolation split and the rate-limit headers in a couple of seconds.
Where the series lands
Four posts, one guide. The overview mapped the three pillars and the build-versus-buy ladder. Tool design measured the token tax and the granularity trade-off. Hosting deployed a real account-bounded server and tore it down. This post made the security model run. If you take one thing from the whole series, take the eight lines above: the model will do the wrong thing eventually, and a scoped token is what decides whether that is a near-miss or an incident.
Sources
- Model Context Protocol strategies on AWS (PDF) — the governance pillar: token isolation, rate limiting and load shedding, operational metrics, and golden datasets.
- MCP specification — Authorization Security Considerations — audience validation and the ban on token passthrough.
- Securing AI agents with Amazon Bedrock AgentCore Identity and the AgentCore Identity docs — scoped workload and user tokens, KMS-encrypted token vault.
About the Author
Stefan Christoph is a Principal Solutions Architect at AWS, focused on agentic AI, media & entertainment, and helping builders move from demo to production. He writes about AI architecture, developer productivity, and the future of software.
This is a personal blog. Opinions expressed here are my own and do not represent the views or positions of my employer.
❤️ Created with the support of AI (Kiro)