Context
Zipy wanted customers to be able to ask Claude questions about their own session-replay and error data instead of navigating a dashboard. The Model Context Protocol is how an AI client connects to external tools, so that meant a remote MCP server exposing Zipy's data.
The problem
An AI agent reading a customer's production data on their behalf is security-critical. It needed per-customer authorization, per-plan access control, and it had to work with clients we didn't know in advance. There was no internal prior art.
What I did
I built a remote MCP server exposing tools like get_session, search_errors and get_user_journey, secured with full OAuth 2.0:
- PKCE
- Dynamic client registration (RFC 7591)
- Authorization server metadata discovery (RFC 8414)
- Plan-based tool access control
Decisions
OAuth, not API keys. API keys are bearer secrets with no scoping, expiry or per-user revocation, and they end up in config files. OAuth gives consent, scoped tokens, expiry and revocation — what you need when something acts on a user's behalf.
PKCE, because MCP clients are public. They can't safely hold a client secret. The client sends a hash first and reveals the secret only at the end, so an intercepted authorization code is useless:
1. client generates code_verifier (random, 43–128 chars)
2. code_challenge = BASE64URL(SHA256(code_verifier))
3. /authorize with code_challenge, method=S256
4. server stores challenge (short TTL), returns auth_code
5. /token with auth_code + code_verifier
6. server checks SHA256(verifier) == stored challenge
7. access_token + refresh_token
Dynamic registration and discovery. You don't know MCP clients ahead of time, so hand-registering client IDs doesn't scale. Discovery plus registration lets clients configure themselves — which makes exact redirect-URI matching and rate limiting on registration load-bearing.
Enforce on every call. Scopes derive from the customer's plan at token issuance but are enforced at tool invocation, not just at connection. Otherwise a long-lived token outlives a plan downgrade.
What it cost
Significant protocol complexity for a small team — several RFCs, token lifecycle management, and a registration endpoint open to unknown clients, which is itself attack surface to defend.
Outcome
Claude could securely query a customer's Zipy session and error data, scoped to what their plan allowed, in production.
What I learned
Designing for AI agents as first-class API consumers is a different problem from designing for people. Auth, discovery and access control all have to work with nobody at a consent screen at call time. And the model's intent is never the authorization.