Personal project · synthetic data · unofficial

An MCP server that checks every tool call against the user the agent acts for

Giving an AI agent tools is the easy part. The hard part is that the agent then holds the keys to systems its user was never allowed to touch. This is a small, working MCP server where the agent never gets its own power over personal data: it trades the user's token for a five-minute token that carries the user's rights and nothing more, and every tool checks it.

…authorization cases behave as specified
…seeded authorization bugs caught by those cases
5 minlongest life of a delegated token, never past the user's session
0tokens, secrets or names in the log lines scanned

How a call gets through

1 · User signs inThe identity provider gives the agent a token addressed to the agent only (aud: agent-gateway). Simulated here.
2 · Token exchangeThe agent proves who it is and swaps that token (RFC 8693) for one addressed to the MCP server: subject = the user, actor = the agent, scopes = what was asked, if the user has them.
3 · MCP serverChecks signature, algorithm, issuer, audience and expiry on every request, then the scope the tool needs, then whether a user is behind the call.
4 · ToolRow-level rule (your own duty only), strict input schema, then the existing API with pagination and 429 back-off. One log line, no payload.

With no user, the agent can still get a token as itself (client credentials), but only for flights:read, which touches no personal data.

Try it

The runner is the demo's agent: it executes on the server through the same token endpoint and MCP handler an outside client reaches at /oauth/token and /mcp, so its client secret never reaches your browser. Signatures are withheld from the display. Limited to 30 runs a minute per address.

The test matrix

Every row is a request with the outcome written down before running it. The suite runs the deployed router in-process with a throwaway signing key and a clock it controls.

Do the tests notice a broken check?

A green suite proves little if it would stay green with the lock removed. So each authorization check was broken on purpose, one at a time, and the suite rerun. The first pass caught 14 of 16: nothing tested a machine token that wrongly carried a user scope, and nothing tested the log filter with a field it should drop. Two tests were added; this is the rerun.

Seeded bugCaughtFailing cases

What this does not show

About

Built by Ulf Fäldt Reifenberg in November 2026 for a job application to an AI integration role (MCP tools, API integrations, OAuth2). Written with coding agents; I chose the design, wrote down the expected outcomes, and reviewed the code and results. Not affiliated with any company; all data is made up.

TypeScript on Cloudflare Pages Functions, WebCrypto only, no dependencies. MCP over Streamable HTTP (protocol 2025-06-18), stateless. Metadata: protected resource · authorization server · JWKS.

Contact: linkedin.com/in/ulffaldt · github.com/AlwaysBlind