- 1 personapproves every grant on the consent screen
- 21 → 8tools: the full local surface, and the trimmed one shown to Copilot Studio
- 2token issuers the gateway accepts: my server and Microsoft's platform
My tool estate is private, and I wanted commercial AI assistants, including Microsoft Copilot Studio, to connect to it on my terms, alongside my own agents. The access plane is how they get in: an OAuth authorization server, gateway token validation against several issuers, an edge proxy that exposes a trimmed tool surface to a vendor's agent platform, and an operations layer that runs the estate.
The idea
The consent screen is the control. Assistants self-register as OAuth clients, and a human approves each grant. A security review once found anonymous registration with attacker-chosen redirects, I locked registration down, and the next morning every assistant was locked out. I re-ran the threat model: a hostile registration gains nothing unless a human approves it on the consent screen. I reverted my own fix and wrote down why. See The hardening fix that locked everyone out.
Assistants self-register as clients and keep their identity. A hostile registration on its own reaches nothing.
Nothing reaches the tools without that approval.
What I built
An OAuth 2.0 authorization server with PKCE, published keys, dynamic client registration and consent-gated grants, fronted by NGINX with TLS and rate limits in a locked-down service unit.
Gateway token validation against published keys from several issuers, so the tool gateway accepts tokens from my server and from Microsoft's platform.
An edge proxy that exposes an authenticated, trimmed tool surface to Microsoft Copilot Studio without opening the estate behind it. Working out why the Copilot Studio connector stalled after initialisation, from the wire, was part of that.
A product-shaped weather MCP server as a worked example, later ported to .NET on the official MCP C# SDK.
An operations layer: a systemd service fleet, a documented Redis topology, tunnelled ingress and a private mesh with TLS termination, SMS alerting, runbooks and a status dashboard.
Cloud GPU bursting through the execution substrate's budget gate.
A larger set of hardening modules exists beside the server: device fingerprinting, rate limiting, request validation, tamper-evident audit, anomaly monitoring and hardware-key login, about 9,000 lines. The registration path runs only the consent gate and the NGINX front; it does not load those modules.
The judgment in it
Consent over registration as the control point.
The lock looked like security; the consent step is security, because a human sees exactly which client is asking for exactly what.
Reverting my own change the day after making it, because a system nobody can use while believing it is safer is the worse outcome.
Deferring a richer design, redirect allow-listing and client-class signals, rather than rushing it in under pressure.
What it demonstrates
Security judgment under real use, identity engineering for AI clients, and working at a vendor's integration layer from wire evidence where the documentation runs out.
Stories
The hardening fix that locked everyone out
Technologies
OAuth 2.0 · PKCE · JWKS · WebAuthn · Python service · Edge worker runtime · NGINX · systemd · Redis · Tunnelled ingress · Microsoft Copilot Studio integration · .NET MCP SDK