<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: tumberger</title>
    <description>The latest articles on DEV Community by tumberger (@tumberger).</description>
    <link>https://dev.to/tumberger</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3873726%2F39d1244f-3a52-4bc2-af85-90ae5eddb758.png</url>
      <title>DEV Community: tumberger</title>
      <link>https://dev.to/tumberger</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/tumberger"/>
    <language>en</language>
    <item>
      <title>🔐 I Built a Credential Broker for AI Coding Agents in Go 🤖</title>
      <dc:creator>tumberger</dc:creator>
      <pubDate>Tue, 14 Apr 2026 00:00:00 +0000</pubDate>
      <link>https://dev.to/kontext/i-built-a-credential-broker-for-ai-coding-agents-in-go-593a</link>
      <guid>https://dev.to/kontext/i-built-a-credential-broker-for-ai-coding-agents-in-go-593a</guid>
      <description>&lt;p&gt;I built Kontext because AI coding agents need access to GitHub, Stripe, databases, and dozens of other services — and right now most teams handle this by copy-pasting long-lived API keys into .env files, or the actual chat interface, whilst hoping for the best.&lt;/p&gt;

&lt;p&gt;The problem isn't just secret sprawl. It's that there's no identity layer. You don't know which developer launched which agent, what it accessed, or whether it should have been allowed to. The moment you hand raw credentials to a process, you've lost the ability to enforce policy, audit access, or rotate without pain. The credential is the authorization, and that's fundamentally broken when autonomous agents are making hundreds of API calls per session.&lt;/p&gt;

&lt;p&gt;Kontext takes a different approach. You declare what credentials a project needs in a &lt;code&gt;.env.kontext&lt;/code&gt; file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight properties"&gt;&lt;code&gt;&lt;span class="py"&gt;GITHUB_TOKEN&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;{{kontext:github}}&lt;/span&gt;
&lt;span class="py"&gt;STRIPE_KEY&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;{{kontext:stripe}}&lt;/span&gt;
&lt;span class="py"&gt;LINEAR_TOKEN&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;{{kontext:linear}}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then run &lt;code&gt;kontext start --agent claude&lt;/code&gt;. The CLI authenticates you via OIDC, and for each placeholder: if the service supports OAuth, it exchanges the placeholder for a short-lived access token via RFC 8693 token exchange; for static API keys, the backend injects the credential directly into the agent's runtime environment. Either way, secrets exist only in memory during the session — never written to disk on your machine. Every tool call is streamed for audit as the agent runs.&lt;/p&gt;

&lt;p&gt;The closest analogy is a Security Token Service (STS): you authenticate once, and the backend mints short-lived, scoped credentials on-the-fly — except unlike a classical STS, I hold the upstream secrets, so nothing long-lived ever reaches the agent. The backend holds your OAuth refresh tokens and API keys; the CLI never sees them. It gets back short-lived access tokens scoped to the session.&lt;/p&gt;

&lt;p&gt;What the CLI captures for every tool call: what the agent tried to do, what happened, whether it was allowed, and who did it — attributed to a user, session, and org.&lt;/p&gt;

&lt;p&gt;Install with one command: &lt;code&gt;brew install kontext-dev/tap/kontext&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;The CLI is written in Go (~5ms hook overhead per tool call), uses ConnectRPC for backend communication, and stores auth in the system keyring. Works with Claude Code today, Codex support coming soon.&lt;/p&gt;

&lt;p&gt;I'm working on server-side policy enforcement next — the infrastructure for allow/deny decisions on every tool call is already wired, I just need to close the loop so tool calls can also be rejected.&lt;/p&gt;

&lt;p&gt;I'd love feedback on the approach. Especially curious: how are teams handling credential management for AI agents today? Are you just pasting env vars into the agent chat, or have you found something better?&lt;/p&gt;

&lt;p&gt;GitHub: &lt;a href="https://github.com/kontext-dev/kontext-cli" rel="noopener noreferrer"&gt;https://github.com/kontext-dev/kontext-cli&lt;/a&gt;&lt;br&gt;&lt;br&gt;
Site: &lt;a href="https://kontext.security" rel="noopener noreferrer"&gt;https://kontext.security&lt;/a&gt;&lt;/p&gt;

</description>
      <category>agents</category>
      <category>ai</category>
      <category>mcp</category>
      <category>security</category>
    </item>
    <item>
      <title>The API Key is Dead: A Blueprint for Agent Identity in the age of MCP</title>
      <dc:creator>tumberger</dc:creator>
      <pubDate>Sat, 11 Apr 2026 00:00:00 +0000</pubDate>
      <link>https://dev.to/kontext/the-api-key-is-dead-a-blueprint-for-agent-identity-in-the-age-of-mcp-1j4</link>
      <guid>https://dev.to/kontext/the-api-key-is-dead-a-blueprint-for-agent-identity-in-the-age-of-mcp-1j4</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;📖 Read the full post at &lt;a href="https://kontext.security/blog/oauth-for-mcp-agents" rel="noopener noreferrer"&gt;https://kontext.security/blog/oauth-for-mcp-agents&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h1&gt;
  
  
  Introduction: The Impossible Choice
&lt;/h1&gt;

&lt;p&gt;AI agents are becoming increasingly powerful and increasingly connected. Every new tool, API, and service you wire into an agent makes it more capable - but also more dangerous if left unsecured. Right now, we face an impossible choice: give agents broad-based access and accept significant security risks, or limit their capabilities and sacrifice business value.&lt;/p&gt;

&lt;p&gt;This dilemma is exemplified in how we set up MCP (Model Context Protocol) servers today. We generate long-lived API keys, paste them into configuration files and environment variables, and let our agents run with them. It works, at first. But when you scale to hundreds or thousands of agents, each with their own set of broadly-scoped credentials, you have a genuine security problem on your hands.&lt;/p&gt;

&lt;p&gt;The good news? We already know how to fix this. We know how to transition away from static secrets to dynamic access. We know how to implement granular permissions, audit trails, and context-aware authorization. And the solution is built on standards that have been battle-tested across billions of user authentications: &lt;strong&gt;OAuth 2.0&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;TLDR -&lt;/strong&gt; To safely unlock the full potential of autonomous AI agents, we must transition from static API keys to dynamic, standards-based authorization - thoughtfully designed to handle everything from simple chatbots to fully autonomous systems crossing trust boundaries.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to read this guide
&lt;/h2&gt;

&lt;p&gt;This post moves from fundamentals into fairly deep OAuth and MCP design, and then back out to higher‑level architecture:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Part I: OAuth/OIDC refresher. Scopes, tokens, auth code flow, and federation. If you already live in these specs, feel free to skim or skip it.&lt;/li&gt;
&lt;li&gt;Part II: How OAuth maps onto MCP in practice (DCR, Client ID Metadata, spec PRs).&lt;/li&gt;
&lt;li&gt;Parts III–IV: System Design. Levels of agent autonomy, delegation, cross‑boundary agents, and enterprise integration. You can follow these even if you only skim the deep‑dive sections.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you only want “how do I stop giving agents long‑lived API keys?”, read the intro, Part I, and the opening of Part II. If you’re designing authorization for larger agent ecosystems, the later sections are where it gets interesting.&lt;br&gt;
If you need help in navigating the increasingly complex space for agentic authorization, &lt;a href="mailto:jens@kontext.security"&gt;reach out&lt;/a&gt;!&lt;/p&gt;
&lt;h2&gt;
  
  
  Part I: OAuth Fundamentals
&lt;/h2&gt;
&lt;h3&gt;
  
  
  Why OAuth Exists (And Why It Matters)
&lt;/h3&gt;

&lt;p&gt;Before OAuth, APIs were secured in one of two ways: you either authenticated with the API directly (using a username and password), or you used an API key. Both approaches created problems.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Direct authentication&lt;/strong&gt; meant sharing your credentials with every third-party service. If you wanted Calendly to access your Google Calendar, you'd give Calendly your Google username and password. This meant Calendly - and potentially everyone who worked at Calendly - could access all of your Google account. If Calendly was compromised, your entire Google account was compromised. There was no way to revoke Calendly's access without changing your password. There was no way to limit what Calendly could do.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;API keys&lt;/strong&gt; solved some of these problems. Instead of sharing your actual password, you'd generate a special key that an application could use. You could create multiple keys, revoke them individually, and (ideally) limit what each key could do. But API keys still had fundamental limitations: they are typically long-lived, difficult to revoke en masse, and created no audit trail of what was actually done with them.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;OAuth solved this by introducing a new participant into the authentication flow: an &lt;strong&gt;authorization server&lt;/strong&gt;. Instead of you sharing credentials with an application, the application would ask the authorization server for permission to access your resources. You'd authenticate with the authorization server (not the application), you'd grant permission to the application, and the authorization server would issue a short-lived token that the application could use. If something went wrong, you could revoke access immediately. The authorization server could log everything. And critically, the application only got access to what you actually authorized—nothing more.&lt;/p&gt;
&lt;h3&gt;
  
  
  The Three Roles of OAuth
&lt;/h3&gt;

&lt;p&gt;OAuth works because it divides responsibility across three distinct roles:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;The Client&lt;/strong&gt;: This is the application requesting access. In traditional OAuth flows, it's a web app like Calendly. In our case, it's Claude, Cursor, or any other AI agent trying to connect to an MCP server.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;The Resource Server&lt;/strong&gt;: This is the API or service that holds the resources being protected. In our case, it's the MCP server itself. The resource server's job is simple: verify that incoming requests have valid tokens, and if they do, fulfill the request. If they don't, reject the request.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;The Authorization Server&lt;/strong&gt;: This is the intermediary that handles all the complex logic around authentication, consent, and permission. When a client requests access, the authorization server authenticates the user (verifies who they are), presents them with a consent screen (asks what they're willing to let the client do), and issues tokens if they agree. The authorization server also handles token expiration, refresh, and revocation.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This separation of concerns is powerful. It means the resource server doesn't have to know anything about passwords, multi-factor authentication, or consent flows. It just verifies tokens. The authorization server handles all the security complexity. Clients get a clean, standardized way to request access.&lt;/p&gt;
&lt;h3&gt;
  
  
  The Benefit of the Three-Role Architecture
&lt;/h3&gt;

&lt;p&gt;Why does separating these three roles matter so much?&lt;/p&gt;

&lt;p&gt;From the &lt;strong&gt;resource server's perspective&lt;/strong&gt;, life becomes simple. The resource server doesn't need to know anything about how users authenticate, what the password policy is, whether multi-factor authentication is required, or how many times a user has failed to log in. All that complexity is handled by the authorization server. The resource server just checks: "Is this token valid? What scopes does it have?" If the answers are yes and appropriate, the request is fulfilled.&lt;/p&gt;

&lt;p&gt;This is huge for scalability. A single authorization server can protect dozens, hundreds, or thousands of resource servers. All you need to do is configure the resource server to verify tokens from that one authorization server. The authorization server centralizes all authentication and authorization logic, making it easier to enforce consistent policies across your entire system.&lt;/p&gt;

&lt;p&gt;From the &lt;strong&gt;client's perspective&lt;/strong&gt;, OAuth provides a standardized way to request access without hardcoding different integrations for every resource server. A client built to use OAuth can talk to any OAuth-protected resource server. The client doesn't need to know or care how the authorization server authenticates users or issues tokens - it just uses the standardized OAuth flows.&lt;/p&gt;

&lt;p&gt;From the &lt;strong&gt;end user's perspective&lt;/strong&gt; (or in our case, the person managing an AI agent), OAuth provides transparency and control. You can see exactly what permissions you've granted, to whom, and for what. You can revoke access with a click. You can see audit logs showing what was done with your data.&lt;/p&gt;
&lt;h3&gt;
  
  
  The Authorization Code Flow: A Real-World Example
&lt;/h3&gt;

&lt;p&gt;Let's walk through what happens when you connect Calendly to your Google Calendar:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;You navigate to Calendly and click "Connect to Google Calendar"&lt;/strong&gt;. Calendly (the client) needs access to your Google Calendar, but it doesn't have permission yet.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Calendly redirects you to Google's authorization server&lt;/strong&gt;. You're taken to a login page that says something like "Calendly is requesting access to your calendar. Grant permission?"&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;You authenticate with Google&lt;/strong&gt;. You enter your credentials (or Google recognizes you're already logged in) and confirms it's really you.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;You grant permission&lt;/strong&gt;. You see a consent screen showing exactly what Calendly is asking for - in this case, read and write access to your calendar. You click "Allow."&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Google redirects you back to Calendly with an authorization code&lt;/strong&gt;. This is a one-time code that Calendly can use to prove you've granted permission.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Calendly exchanges this code for an access token&lt;/strong&gt;. Behind the scenes, Calendly contacts Google's authorization server with the authorization code and receives an access token in return. This token is short-lived (typically 1 hour) and can only be used for calendar access.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Calendly uses the access token to access your calendar&lt;/strong&gt;. Whenever Calendly needs to read or write to your calendar, it includes this token with the request. Google's resource server (the Calendar API) verifies the token is valid and allows the request.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;When the token expires, Calendly gets a refresh token&lt;/strong&gt;. Along with the access token, Google also issued a refresh token. When the access token expires, Calendly uses the refresh token to quietly get a new access token without you having to re-authenticate. This keeps the integration working seamlessly.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The beauty of this flow is that your actual Google password never leaves Google's servers. Calendly never gets to see it. If Calendly is compromised, hackers don't get your password—they might get an access token, but you can revoke it immediately, and Google can invalidate it. Your Google account remains secure.&lt;/p&gt;
&lt;h3&gt;
  
  
  Scopes, Tokens, and Granular Permissions
&lt;/h3&gt;

&lt;p&gt;In OAuth, &lt;strong&gt;scopes&lt;/strong&gt; define what an application can do. Instead of simply saying "this app has access to your Google account," scopes let you say "this app can read your calendar and create events, but it can't delete events or read your email." Common scopes include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;calendar.read&lt;/code&gt;: Read-only access to calendar&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;calendar.write&lt;/code&gt;: Create and modify calendar events&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;calendar.delete&lt;/code&gt;: Delete calendar events&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;When you grant permission in the consent flow, you're not just granting access, but you're granting access within specific scopes. The access token issued to Calendly includes information about which scopes it has. When Calendly makes a request to create an event, the resource server checks: "Does this token have the &lt;code&gt;calendar.write&lt;/code&gt; scope?" If yes, proceed. If no, reject the request.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Access tokens&lt;/strong&gt; are short-lived (typically 15 minutes to 1 hour) and are cryptographically signed by the authorization server so they can't be forged. They can be revoked immediately.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Refresh tokens&lt;/strong&gt; are longer-lived (typically days or months) and are used exclusively to get new access tokens. If a resource server receives a request with an expired access token, the client should use the refresh token to silently get a new one. This means your agent or application can maintain long-running access without storing passwords, and you can revoke everything immediately by invalidating the refresh token.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  OAuth vs. OpenID Connect: Authentication vs. Authorization
&lt;/h3&gt;

&lt;p&gt;Here's where OAuth gets confusing for many people: almost everyone has used OAuth to sign into an application (e.g., "Sign in with Google"), but OAuth was designed for &lt;strong&gt;authorization&lt;/strong&gt;, not &lt;strong&gt;authentication&lt;/strong&gt;.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Authorization&lt;/strong&gt; is about answering the question: "What can this entity do?" OAuth is specifically designed to answer this.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Authentication&lt;/strong&gt; is about answering the question: "Who are you?" OAuth was not designed to answer this, but people quickly realized they could use it for this purpose.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;When you click "Sign in with Google" on a website, here's what's happening under the hood:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The website (client) redirects you to Google's authorization server, asking for &lt;code&gt;profile&lt;/code&gt; and &lt;code&gt;email&lt;/code&gt; scopes.&lt;/li&gt;
&lt;li&gt;You authenticate and grant permission.&lt;/li&gt;
&lt;li&gt;Google's authorization server returns an access token for the &lt;code&gt;profile&lt;/code&gt; and &lt;code&gt;email&lt;/code&gt; scopes, but it also returns something else: an &lt;strong&gt;ID token&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;The website uses this ID token (which contains claims like &lt;code&gt;sub&lt;/code&gt; (your unique ID), &lt;code&gt;email&lt;/code&gt;, and &lt;code&gt;name&lt;/code&gt;) to create an account or log you in. It's basically using OAuth to answer "Who are you?" by asking "Can I read your profile?"&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This pattern became so common that it was formalized as &lt;strong&gt;OpenID Connect&lt;/strong&gt;, which is essentially an identity layer built on top of OAuth. OpenID Connect standardizes the response format, adds an ID token, and introduces some new terminology:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Identity Provider (IdP)&lt;/strong&gt;: The authorization server (e.g., Google)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Relying Party (RP)&lt;/strong&gt;: The client application&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;ID Token&lt;/strong&gt;: A cryptographically signed JSON Web Token (JWT) containing claims about the user&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The key insight is this: &lt;strong&gt;in the real world, we use OAuth for authorization and OpenID Connect (backed by OAuth) for authentication together&lt;/strong&gt;. They work hand-in-hand.&lt;/p&gt;
&lt;h3&gt;
  
  
  Federation: Enabling Cross-Domain Authentication and Authorization
&lt;/h3&gt;

&lt;p&gt;OpenID Connect enables something powerful that neither OAuth nor traditional authentication systems could easily accomplish: &lt;strong&gt;federation&lt;/strong&gt;. Federation means allowing users to authenticate and access resources across multiple independent organizations without creating separate accounts at each one.&lt;/p&gt;

&lt;p&gt;Here's how OpenID Connect enables federation: Instead of each application maintaining its own user database, applications can trust identity providers in other domains. When you visit a federated application, instead of creating a new account, you authenticate through your home organization's identity provider. The identity provider issues an ID token that vouches for who you are, and the application trusts that token because it was cryptographically signed by a trusted identity provider.&lt;/p&gt;

&lt;p&gt;Consider a practical example: imagine you work at Company A but need to access a collaboration tool used by Company B. Without federation, Company B would need to create a separate account for you, requiring you to remember another username and password. With OpenID Connect federation, Company B can configure trust with Company A's identity provider. When you visit Company B's application, you're redirected to Company A's identity provider to authenticate. Once authenticated, Company A's IdP issues an ID token confirming you're an employee of Company A, and perhaps including your role and department. Company B trusts this token (because it verifies the cryptographic signature), logs you in automatically, and can even use the claims from the token to provision the correct access level or resources for you.&lt;/p&gt;

&lt;p&gt;This is particularly powerful in enterprise environments where users work across multiple organizations or contractors need temporary access to partner systems. Federation eliminates password proliferation, reduces the burden on users to manage multiple credentials, and allows organizations to maintain security policies centrally at the identity provider level. If your employment at Company A ends, the administrator can disable your account in one place, and your access to all federated applications in the ecosystem immediately revoked—without those applications needing to maintain records of your employment status.&lt;/p&gt;

&lt;p&gt;The federation model also scales elegantly. A single identity provider can serve hundreds or thousands of federated applications. Applications don't need to maintain user directories; they simply trust the identity provider's assertions about who users are. This is why OpenID Connect has become the standard for academic and research networks (through services like Shibboleth), enterprise single sign-on (through Azure AD, Okta, and similar services), and increasingly for consumer applications seeking interoperability across domains.&lt;/p&gt;
&lt;h2&gt;
  
  
  Part II: OAuth in MCP - A Journey from No Standards to Standards-Based Design
&lt;/h2&gt;
&lt;h3&gt;
  
  
  The Early Days: MCP Without OAuth
&lt;/h3&gt;

&lt;p&gt;The Model Context Protocol is remarkably young. At the time of this post, it's only 12 months old. When MCP first launched, the specification didn't include any authorization requirements. This wasn't an oversight - it was pragmatic. MCP was designed primarily for local servers running on your own machine, where the security model is "if you have access to the machine, you have access to the tools." Remote servers existed in the spec, but authorization wasn't formalized.&lt;/p&gt;

&lt;p&gt;In practice, this meant people protecting remote MCP servers using the only tool they had: API keys. A long-lived, broadly-scoped API key would be dropped into an environment variable, and the agent would use it to authenticate. It's a solution, but it has all the problems we discussed earlier: keys are long-lived, difficult to rotate, impossible to scope narrowly, and create no audit trail.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"servers"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"github"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"http"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"url"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://api.githubcopilot.com/mcp/"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"headers"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"Authorization"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Bearer ${input:github_mcp_pat}"&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"inputs"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"promptString"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"github_mcp_pat"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"description"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"GitHub Personal Access Token"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"password"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But people saw the promise of MCP and started asking the question: "How do we make this secure?"&lt;/p&gt;

&lt;h3&gt;
  
  
  The First Attempt: MCP Authorization RFC (#133, Jan 2025)
&lt;/h3&gt;

&lt;p&gt;In late January 2025, the project merged an initial authorization RFC for HTTP+SSE transport (PR #133). It grounded MCP in OAuth 2.1 and specified how clients and servers should interact:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Based on OAuth 2.1 draft; PKCE required for public clients.&lt;/li&gt;
&lt;li&gt;Metadata discovery via RFC 8414; if missing, fall back to default endpoints: &lt;code&gt;/authorize&lt;/code&gt;, &lt;code&gt;/token&lt;/code&gt;, &lt;code&gt;/register&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Dynamic Client Registration (RFC 7591) recommended; optional with localhost redirect URIs, expected for non‑localhost.&lt;/li&gt;
&lt;li&gt;Servers respond &lt;code&gt;401 Unauthorized&lt;/code&gt;; clients initiate the OAuth flow in a browser and exchange code for tokens.&lt;/li&gt;
&lt;li&gt;Guidance for token handling, error codes, and security requirements (HTTPS, redirect URI validation, rotation).&lt;/li&gt;
&lt;li&gt;A “third‑party authorization” mode where an MCP server proxies to an external auth server and then issues its own token.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;On the surface, this looked like clear progress. But there was a fundamental architectural problem. The draft effectively suggested that remote MCP servers implement the full OAuth flow themselves. In other words, each MCP server would need to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Implement the client side of OAuth (accepting authorization requests)&lt;/li&gt;
&lt;li&gt;Implement the authorization server side of OAuth (handling login, issuing tokens, managing refresh tokens)&lt;/li&gt;
&lt;li&gt;Implement the resource server side of OAuth (verifying tokens on incoming requests)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Wait — that’s all three roles in one. That collapses the architecture we just established. Think about what this means for MCP server developers: they’d have to build login flows, implement password hashing, handle session management, issue and sign tokens, manage token expiration and revocation, and handle everything else a proper authorization server requires. It’s complex and error‑prone, and it breaks the core benefit of OAuth: centralizing authentication and authorization logic in a dedicated Authorization Server.&lt;/p&gt;

&lt;p&gt;The critique wasn’t that OAuth was the wrong choice—it was the placement of roles. This draft effectively made each remote MCP server act as its own OAuth Authorization Server (AS) for HTTP+SSE while simultaneously being the Resource Server for MCP methods. That coupling creates operational sprawl in enterprises (every MCP server is now an AS), complicates policy centralization, and makes audit/consent inconsistent across servers.&lt;/p&gt;

&lt;p&gt;Community reactions captured these concerns. See Christian Posta’s analysis: &lt;a href="https://blog.christianposta.com/the-updated-mcp-oauth-spec-is-a-mess/" rel="noopener noreferrer"&gt;“The Updated MCP OAuth Spec is a Mess”&lt;/a&gt;, which argues that collapsing AS and resource roles per‑server is operationally brittle and misaligned with standard OAuth architecture.&lt;/p&gt;

&lt;p&gt;Aaron Parecki, who has spent years designing OAuth specifications, offered a complementary perspective in &lt;a href="https://aaronparecki.com/2025/04/03/15/oauth-for-model-context-protocol" rel="noopener noreferrer"&gt;“OAuth for Model Context Protocol”&lt;/a&gt;, outlining how standard OAuth roles map cleanly onto MCP without forcing every server to become an authorization server.&lt;/p&gt;

&lt;p&gt;This sparked a 400+ comment GitHub PR where the community proposed: "What if we just model MCP servers as resource servers and have a separate authorization server handle the complex parts?"&lt;/p&gt;

&lt;h3&gt;
  
  
  Update: Spec fix — MCP servers are only resource servers (PR #338)
&lt;/h3&gt;

&lt;p&gt;That proposal landed. The follow‑up change &lt;a href="https://github.com/modelcontextprotocol/modelcontextprotocol/pull/338" rel="noopener noreferrer"&gt;PR #338&lt;/a&gt; clarifies the architecture: MCP servers are OAuth resource servers only. Clients obtain tokens from a separate Authorization Server (AS), and MCP servers verify those tokens. This restores the standard separation of concerns and enables centralized policy and consent.&lt;/p&gt;

&lt;h3&gt;
  
  
  MCP Authentication Today: Three Ways to Secure Your Servers
&lt;/h3&gt;

&lt;p&gt;Authentication for Model Context Protocol (MCP) servers remains an unsolved problem in practice, even though the solution space is well understood. Today's landscape offers three distinct approaches, each with clear trade-offs. Understanding these options is essential for anyone building or deploying MCP systems.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Long-Lived Credentials: The Convenient Default&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The simplest approach is to use static credentials—API keys, personal access tokens (PATs), or shared client secrets. The agent authenticates by including these credentials in every request, typically as an &lt;code&gt;Authorization: Bearer &amp;lt;token&amp;gt;&lt;/code&gt; header. This is how many MCP deployments work today.&lt;/p&gt;

&lt;p&gt;This approach requires essentially zero setup. You generate a token, embed it somewhere, and authentication works everywhere. For local development on a single machine or air-gapped environments, it is genuinely hard to beat. The barrier to entry is so low that long-lived credentials dominate prototyping and early-stage deployments.&lt;/p&gt;

&lt;p&gt;The security problems, however, are severe and unavoidable. Credentials are broad and persistent—once leaked, they grant full access indefinitely. Rotation hygiene is poor; most teams never rotate these tokens in practice. Auditability suffers because there is no binding between a credential and the specific tool, action, or session that used it. Worse, these secrets tend to leak into configuration files, environment variables, prompt logs, and model contexts where they persist and become discoverable.&lt;/p&gt;

&lt;p&gt;Use long-lived credentials only for prototyping, local setups you fully control, and short-lived demos. Avoid them entirely for servers reachable from the Internet or in any multi-user environment. The convenience today is not worth the liability tomorrow.&lt;/p&gt;

&lt;p&gt;&lt;a id="dcr-deep-dive"&gt;&lt;/a&gt;&lt;br&gt;
&lt;strong&gt;2. Dynamic Client Registration: Standards-Based, but Leaky&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;A second option leverages OAuth's &lt;a href="https://modelcontextprotocol.io/specification/draft/basic/authorization" rel="noopener noreferrer"&gt;Dynamic Client Registration (DCR)&lt;/a&gt; flow. At runtime, the agent posts its metadata to an Authorization Server (AS), which responds with a &lt;code&gt;client_id&lt;/code&gt; and credentials. The agent then runs a standard OAuth flow using those newly minted credentials. For MCP’s draft guidance on DCR within the protocol, see the &lt;a href="https://modelcontextprotocol.io/specification/draft/basic/authorization" rel="noopener noreferrer"&gt;MCP Authorization draft&lt;/a&gt;.&lt;br&gt;
DCR is an OAuth/OIDC mechanism where a client app can “self-onboard” by calling a registration endpoint instead of being manually set up by an administrator. The authorization server exposes a DCR URL; the app sends an HTTP POST with a JSON body describing itself — things like redirect URIs, client name, logo URL, scopes it wants, token endpoint auth method, and so on. If the request is accepted, the authorization server creates a new client record and returns a client_id (and usually a client_secret for confidential clients), plus a copy of the registered metadata. From that point on, the app uses this client_id when doing the normal OAuth flows (authorization code, device flow, etc.). In some deployments, the client can later use a registration access token to update or delete its registration, but the core idea is simple: registration is just a standardized API call that turns “here is my metadata” into “here is your client_id and configuration.”&lt;/p&gt;

&lt;p&gt;The appeal is clear: this is standards-based, meaning the Authorization Server controls issuance policy and you avoid the manual step of provisioning credentials in a web portal beforehand. It feels like progress.&lt;/p&gt;

&lt;p&gt;In open ecosystems (Mastodon, self-hosted apps, etc.), this leads to “client table explosion,” because every app instance or login can create a new client, making databases huge, admin UIs noisy, and it hard to tell which clients are actually in use. There’s also no good lifecycle story: if you delete “inactive” clients, you risk breaking users who still have valid tokens, which in turn encourages developers to re-register on every login and make the explosion worse. DCR also doesn’t give a global, stable identity for “this specific app” across many servers, which is why people favor approaches like client-ID-as-URL with hosted metadata instead. Finally, a public DCR endpoint is another attack surface that must be protected against spam and misleading/phishing registrations, and even with rate limits and approvals it still doesn’t answer the core question: “who are you really?”.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;So what's the problem with DCR?&lt;/strong&gt; DCR in OAuth is great for letting any app POST some metadata and get a client_id, but it’s weak as an identity mechanism and creates a lot of operational and security pain. A client_id doesn’t prove that an app is “the real FooCorp app” or that two client_ids are the same software, so attackers can register look-alike apps (same name, logo, redirect URI) and phish users unless you add extra ecosystem rules like software statements and trusted registries..&lt;br&gt;
The registration request itself is uncredentialed—anyone can call it. Client identifiers churn constantly because each agent instance can register itself anew. This per-instance sprawl creates explosive database growth. More subtly, cleanup jobs that revoke stale credentials will inevitably invalidate clients mid-session, triggering &lt;code&gt;invalid_client&lt;/code&gt; errors and operational confusion. There is no strong binding between a credential and the actual agent that requested it, making forensics harder.&lt;/p&gt;

&lt;p&gt;DCR works best in closed ecosystems where you still control the registration policy and can tolerate churn. For example, within an organization's internal tools, you might use DCR to mint credentials per installation while accepting that some fraction of clients will fail due to cleanup races. Cache aggressively and expire thoughtfully. For public clients, always show consent. Expect your database to grow.&lt;br&gt;
Now all of this leads to many being negative towards DCR - and the MCP ecosystem in general moving towards alternatives such as Client ID Metadata.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. &lt;a href="https://oauth.net/2/client-id-metadata-document/" rel="noopener noreferrer"&gt;Client ID Metadata&lt;/a&gt;: Identity Without Pre-Registration&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The third approach is newer and more elegant. Instead of registering your client credentials in advance, you host your client's metadata at a well-known URL and use that URL as your client ID. When the Authorization Server needs to verify your identity, it fetches your metadata—including your name, logo, redirect URIs, and signing keys (JWKS)—directly from that URL. See the overview spec: &lt;a href="https://oauth.net/2/client-id-metadata-document/" rel="noopener noreferrer"&gt;OAuth Client ID Metadata Document&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Client ID metadata is needed because, in modern “open world” systems like Mastodon, WordPress, BlueSky, or MCP, it’s impossible to pre-register every app with every authorization server, and relying on Dynamic Client Registration (DCR) alone creates database bloat, cleanup nightmares, and weak identity guarantees. Instead of each AS minting its own opaque client_id per registration, the client hosts a JSON metadata document at a stable URL (containing its name, logo, redirect URIs, and a JWKS URI with its public keys), and that URL is the client_id. When an authorization server encounters a new client_id URL, it first authenticates the user, then fetches that metadata to build the consent screen, validate redirect URIs, and know which keys to expect for client authentication. This lets clients “bring their own identity” in a standardized, self-describing way, avoiding endless per-server registrations and making it much easier to reliably recognize “this specific app” across many servers.&lt;/p&gt;

&lt;p&gt;This solves the pre-registration problem entirely. Identifying agents/clients via a URI (usually an HTTPS URL) is beneficial because it turns the client identifier into a globally unique, web-native handle that also works as a discovery endpoint. DNS already gives you a global namespace, so if you control exampleapp.com, you inherently control something like &lt;a href="https://exampleapp.com/oauth-client-metadata.json" rel="noopener noreferrer"&gt;https://exampleapp.com/oauth-client-metadata.json&lt;/a&gt; as a unique ID, just like SAML entityIDs and OAuth/OIDC issuers are URLs. That same URL tells an authorization server exactly where to fetch the client’s metadata—no extra registry or mapping layer from client_id to metadata URL is needed. It also enables powerful policy hooks (“only allow clients under *.mycompany.com”, “treat https://_.trusted-vendor.com/... as high-trust”), and gives ecosystems like the Fediverse or MCP a shared, linkable identifier for the same client across many servers. Conceptually, it lines up nicely with the rest of the architecture: authorization servers are URLs, resource servers can be URLs, and now clients are URLs too—everything has a web-meaningful identifier that can host its own metadata.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"If a client is identified by a URL, how do you stop any random app from just using that URL and pretending to be that client?"&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;To make sure no one can convincingly pretend to be your client, you treat the client_id URL as a name anyone can copy, but bind it to secrets and (where possible) platform attestation that only you control. For web server apps, this is straightforward: your metadata document points to a JWKS with your public keys, your server holds the private keys, and the authorization server (AS) requires private-key JWT client authentication—so only whoever has the private key corresponding to the keys in your metadata can actually act as that client, even if others reuse your client_id URL. For mobile apps, you host the metadata and keys on your website, hardcode the client_id URL into the app, and use OS attestation (Apple/Google integrity APIs) plus your backend: only binaries that pass attestation and talk to your backend get a valid signed client-auth JWT, so fake or repackaged apps that just copy the URL can’t authenticate. Desktop apps remain “public clients” with no strong, standardized attestation story, so you mostly accept the same limitations as today and use enterprise controls (MDM/EDR, controlled distribution) where needed. On top of all this, an AS or enterprise can maintain an allowlist of approved client_id URLs in an admin UI—combined with DNS/HTTPS control over the domain hosting the metadata, that means “who is real” is determined by cryptographic keys and admin approval, not by whoever hits a registration endpoint first.&lt;/p&gt;

&lt;p&gt;Use Client ID Metadata in open ecosystems where clients are unknown in advance but you still want to establish identity and enforce policy. Mastodon, WordPress, and MCP itself are examples. You gain security guarantees without sacrificing the openness that makes these ecosystems valuable.&lt;/p&gt;

&lt;p&gt;Now one problem remains with CIDM:&lt;/p&gt;

&lt;h2&gt;
  
  
  Part III: The Levels of Autonomy - Rethinking Security as Agents Become Smarter
&lt;/h2&gt;

&lt;p&gt;We've covered the basics of OAuth and how it applies to MCP. But here's the crucial insight: OAuth as currently implemented (even in the corrected MCP spec) only handles one use case: a user authorizing an agent to access a resource.&lt;/p&gt;

&lt;p&gt;As AI agents become more autonomous, we'll need OAuth to handle increasingly complex scenarios. To understand what we need to build, we should think through the different levels of autonomy that agents can have—and the different authorization challenges each level presents.&lt;/p&gt;

&lt;h3&gt;
  
  
  Level 1: Basic Chatbots - Simple User Authorization
&lt;/h3&gt;

&lt;p&gt;At the most basic level, you're using Claude or another LLM in a chat interface. You ask it to help with something ("Find my calendar conflicts tomorrow") and it uses an MCP server to get the information.&lt;/p&gt;

&lt;p&gt;In this scenario:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Authentication:&lt;/strong&gt; The authorization server verifies that it's you asking Claude to do something&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Authorization&lt;/strong&gt;: It verifies that you're allowed to access the MCP server and that the MCP server is allowed to perform the action&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The security questions are straightforward: "Who is accessing the MCP server? What is that MCP server allowed to do?"&lt;/p&gt;

&lt;p&gt;For basic chatbots, we can implement coarse-grained access control:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Tool-level blocking&lt;/strong&gt;: The simplest approach is to turn off certain tools entirely for certain users. The Beeper MCP server, for example, lets you connect all your personal messages (iMessages, WhatsApp, Signal) to Claude. But you might not want Claude replying to messages on your behalf - so you'd remove the "send message" tools from the MCP server's configuration for certain users.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Role-based tool selection&lt;/strong&gt;: Different users can see different sets of tools. An intern might see a subset of tools, while a senior engineer sees everything.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Role-based behavior modification&lt;/strong&gt;: This is underappreciated but powerful. Different tools serve different descriptions and instructions based on the user's role. For a user with limited permissions, the tool description might say "Use this only when..." but for a trusted user, it says "Use this liberally." You can even modify the tool's instructions based on role, essentially using role-based access control to shape the LLM's behavior. This can be used for security (ensuring certain operations are never attempted) or for improving the agent's usefulness (making it behave differently for different users).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;All of this is straightforward to implement with OAuth scopes. The authorization server issues different scopes to different users, the MCP server checks scopes on each request, and conditionally serves tools or modifies behavior accordingly.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Real-world example&lt;/strong&gt;: The Beeper MCP server lets you send and read personal messages through Claude. This is incredibly useful for one use case (you with your own data) and incredibly dangerous for others (Claude hallucinating and sending random messages, or an attacker accessing the MCP server and reading your private messages). OAuth with proper scoping lets you control exactly who can do what.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Required technologies for Level 1:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;OAuth 2.0 Authorization Code Flow with PKCE (short‑lived tokens, refresh where appropriate)&lt;/li&gt;
&lt;li&gt;OIDC login and exact redirect_uri matching (state/nonce protection)&lt;/li&gt;
&lt;li&gt;OAuth scopes enforced at the MCP resource server&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Level 2: Background Agents—Dynamic MCP Discovery and Escalation
&lt;/h3&gt;

&lt;p&gt;Let's step up the complexity. Now instead of directly asking Claude to do something, you're asking a background agent to run autonomously. For example: "Continuously monitor my repos for flaky tests and open fixing PRs."&lt;/p&gt;

&lt;p&gt;In this scenario, the agent needs to:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Pull failing test reports and logs (via CI MCP servers)&lt;/li&gt;
&lt;li&gt;Discover and connect at runtime to remote services via MCP (e.g., GitHub for code hosting, Jira/Linear for issues, npm/PyPI for packages)&lt;/li&gt;
&lt;li&gt;Analyze the codebase and propose patches locally using workspace tools (filesystem/shell access, language servers, linters, formatters, and test runners)&lt;/li&gt;
&lt;li&gt;Push branches and open PRs, request reviews, and trigger CI runs via the GitHub and CI MCP servers&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Note: MCP connects the agent to external systems; code edits occur locally in the agent's execution environment.&lt;/p&gt;

&lt;p&gt;The security challenge is: &lt;strong&gt;the agent doesn't know ahead of time which MCP servers it will need&lt;/strong&gt;. It discovers them at runtime. This means you can't pre-authorize it for all the MCP servers it might need.&lt;/p&gt;

&lt;p&gt;And this is where the "dangerously skip permissions problem" comes in.&lt;/p&gt;

&lt;p&gt;If you've used Claude Code, you've probably seen this: Claude Code starts running, encounters a permission it doesn't have ("I need to delete this folder"), and stops to ask for permission. This is good for security - you probably don't want Claude deleting arbitrary folders. But it's terrible for user experience. You kicked off a task expecting it to complete unattended, and now it's stuck waiting for your approval.&lt;/p&gt;

&lt;p&gt;So what do developers do? They set &lt;code&gt;dangerously_skip_permissions: true&lt;/code&gt; and let Claude do whatever it wants. This works great in a development sandbox. But if we want to extend MCP to consumers, are we comfortable with &lt;code&gt;skip_bank_account_permissions&lt;/code&gt; or &lt;code&gt;skip_medical_records_permissions&lt;/code&gt;?&lt;/p&gt;

&lt;p&gt;We need a better solution. OAuth supports several mechanisms for handling this:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Step-up Authentication&lt;/strong&gt;: When an agent encounters an operation that requires elevated permissions, it can send the user a new authorization request (via their browser or a notification) asking for higher-level permissions. The user can grant or deny these elevated permissions in real-time. The authorization server then issues a new token with the elevated scopes, and the agent continues.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Client-Initiated Back-Channel Authentication (CIBA)&lt;/strong&gt;: A more sophisticated approach. Instead of redirecting to a browser, the agent can request escalated permissions and the authorization server sends the user a push notification, SMS, or other out-of-band message asking for approval. The user approves via their phone, and the agent continues.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;MCP Elicitations&lt;/strong&gt;: MCP servers can ask agents to present URLs to users for approval. An agent encountering a permission it doesn't have can present a URL to the user (via a browser, notification, or other means) asking for approval. The user clicks the link, grants permission, and the agent continues.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;All of these approaches let agents run mostly autonomously while still requiring human approval for sensitive or unexpected operations - without requiring developers to resort to the nuclear option of "dangerously skip everything."&lt;/p&gt;

&lt;p&gt;Note: Step‑up mints narrowly scoped tokens that resource servers (e.g., GitHub, CI) enforce. A local "skip permissions" flag relies on the agent to behave; a buggy or compromised agent can ignore its own toggles but cannot bypass server‑side scope checks.&lt;/p&gt;

&lt;p&gt;Required technologies for Level 2:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Session‑bound URL elicitations for just‑in‑time consent (bind to the user’s authenticated session; anti‑phishing checks)&lt;/li&gt;
&lt;li&gt;Optional step‑up flows for elevated scopes (browser prompts) when elicitations aren’t viable&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Level 3: Long-Running Asynchronous Agents - Persistent Access Without Human Approval
&lt;/h3&gt;

&lt;p&gt;Let's add another layer of complexity. Now imagine an agent that runs on a schedule or in response to events, with no human sitting in front of a screen waiting for it to complete.&lt;/p&gt;

&lt;p&gt;For example: A Zapier-like workflow that automatically drafts emails for you based on certain triggers. Or an incident response bot that automatically creates tickets, pulls logs, and drafts solutions without you actively monitoring it.&lt;/p&gt;

&lt;p&gt;In these scenarios, &lt;strong&gt;you can't ask for permission in real-time because there's no human user to ask&lt;/strong&gt;. You need to authorize the agent upfront and let it run.&lt;/p&gt;

&lt;p&gt;This is where OAuth's &lt;strong&gt;client credentials flow&lt;/strong&gt; comes in. Unlike the authorization code flow (which involves user delegation), the client credentials flow allows an application to authenticate on its own behalf and request a token.&lt;/p&gt;

&lt;p&gt;The flow is simple:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The agent (client) authenticates directly to the authorization server using credentials (typically a client ID and secret)&lt;/li&gt;
&lt;li&gt;The authorization server verifies the agent's identity&lt;/li&gt;
&lt;li&gt;The authorization server issues a token directly to the agent (no user approval needed)&lt;/li&gt;
&lt;li&gt;The agent uses this token to access MCP servers&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The key difference from API keys: the tokens are short-lived (minutes to hours), can be revoked immediately, and are issued with specific scopes. If the agent is compromised, the damage is limited to whatever scopes were authorized and whatever the agent can do before the token expires.&lt;/p&gt;

&lt;p&gt;But there's still a friction point here: &lt;strong&gt;agent identity&lt;/strong&gt;. How does the authorization server know which agent it's talking to? Traditionally, with OAuth, you'd go to a developer portal, click "Create New Application," get a client ID and secret, and configure your application with those credentials. But this doesn't scale for MCP. You can't require developers to manually register every agent with an authorization server.&lt;/p&gt;

&lt;p&gt;If you’re considering Dynamic Client Registration (DCR) here, we cover its behavior and trade‑offs in depth in Part II — see the DCR deep dive. In open MCP ecosystems, prefer Client ID Metadata (CIMD) for portable, verifiable identity; reserve DCR for closed environments with tighter controls.&lt;/p&gt;

&lt;p&gt;There are a few solutions being explored:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Pushed Authorization Requests (PAR)&lt;/strong&gt; for public clients: This specification introduces a well-known string that identifies a public client (an application you're willing to let anyone use). Instead of going through a full registration process, agents can just use this well-known string, and the authorization server trusts it. This works for public clients where you don't need to verify identity.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Client ID Metadata (CIMD)&lt;/strong&gt; for non‑manual registration: Use an HTTPS URL as the client_id that points to a metadata document (name, redirect URIs, token auth method, JWKS). Authenticate with private_key_jwt using keys from your JWKS. This provides portable, verifiable client identity without per‑AS registration.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;URLs and PKI for authenticated clients&lt;/strong&gt;: For agents you do want to identify, you can use the agent's URL (e.g., &lt;code&gt;https://agent.example.com&lt;/code&gt;) as its identity, backed by cryptographic keys. The agent signs OAuth requests with its private key, and the authorization server verifies the signature using the agent's public key. This lets you reuse existing identities and security infrastructure.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This brings us to the next challenge: if an agent has access to sensitive data or services, &lt;strong&gt;you probably want to know which AI model is running it&lt;/strong&gt;. An agent running Claude might be trusted to access financial data, but an agent running an unknown open-source LLM might not.&lt;/p&gt;

&lt;p&gt;Finally, even unattended agents sometimes need ad‑hoc approvals. Add contextual authorization hooks so long‑running jobs can request approval mid‑run for high‑risk operations. In unattended contexts this typically means out‑of‑band prompts (e.g., push approvals) rather than browser redirects.&lt;/p&gt;

&lt;p&gt;Required technologies for Level 3:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Client Credentials (M2M) with private_key_jwt or workload OIDC (K8s SA, GHA OIDC, AWS IRSA)&lt;/li&gt;
&lt;li&gt;Contextual Authorization: asynchronous approval hooks for high‑risk actions encountered at runtime&lt;/li&gt;
&lt;li&gt;Client ID Metadata (CIMD) for non‑manual registration of clients (portable identity via HTTPS URL + JWKS)&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Level 4: Delegated Sub‑Agents — Restricting Access Through Trust Boundaries
&lt;/h3&gt;

&lt;p&gt;Things get more interesting when agents call other agents. You have a top-level agent with broad permissions, and you want it to spin up sub-agents for specific tasks—but you want to ensure those sub-agents have only the permissions they need.&lt;/p&gt;

&lt;p&gt;For example: You ask an agent to "redesign my entire application." It spins up sub-agents: one for frontend work, one for backend work, one for database work. Each sub-agent should have permissions limited to its specific domain.&lt;/p&gt;

&lt;p&gt;This is a &lt;strong&gt;scope attenuation&lt;/strong&gt; problem. You have a token with broad scopes, and you need to issue a token with narrower scopes to the sub-agent.&lt;/p&gt;

&lt;p&gt;OAuth provides mechanisms for this:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Token Exchange&lt;/strong&gt;: A specification that allows you to exchange one token for another with a different set of scopes or resource access. The top-level agent, holding a broad token, can request a token with narrower scopes for the sub-agent. The authorization server issues this narrower token, and the sub-agent operates with restricted permissions.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cryptographic Credentials with Attenuation&lt;/strong&gt;: There are more exotic approaches using cryptographic credentials that can be "attenuated" or reduced as they're passed along. These include mechanisms like Biscuits and Macaroons - cryptographic tokens that can be progressively restricted without interaction with an authorization server. An agent can receive a token, add additional restrictions to it (narrowing its scope or limiting it to specific resources), and pass it to a sub-agent. The sub-agent can verify the token and see what it's allowed to do - and that it can't do more because of the restrictions added by the parent agent.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Real-world example&lt;/strong&gt;: Anthropic's Claude docs describe "handcrafted sub-agents" where you manually define sub-agents with restricted scopes. But what if you want to programmatically generate sub-agents based on a goal ("redesign my app")? You need automatic scope attenuation to do this safely.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Transactional Authorization (RAR): Beyond Scopes&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;OAuth scopes are powerful, but they're also blunt instruments. A scope says "this agent can read and write emails" or "this agent can transfer up to $10,000." But what if you want to restrict an agent to transferring exactly 500 dollars to a specific recipient? What if you want to authorize individual transactions based on their content, not just broad capabilities?&lt;/p&gt;

&lt;p&gt;This is the problem of transactional authorization. And it becomes increasingly important as agents make financial and commercial decisions.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Rich Authorization Requests (RAR) is a specification that addresses this. Instead of just requesting scopes, a client can request detailed authorization for specific transactions or operations. For example:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"financial_transfer"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"amount"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"currency"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"USD"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"recipient"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"alice@example.com"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"purpose"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"invoice payment"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The authorization server can evaluate this request in context, potentially asking the user for approval, checking against spending limits, and issuing a token valid only for this specific transaction. Once the transaction completes, the token is worthless for any other transaction.&lt;/p&gt;

&lt;p&gt;Required technologies for Level 4:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Identity and authorization chaining for agent→sub‑agent calls&lt;/li&gt;
&lt;li&gt;Identity assertion grants to access third‑party APIs (e.g., JWT bearer assertions, RFC 7523; or OAuth 2.0 Token Exchange, RFC 8693)&lt;/li&gt;
&lt;li&gt;Attenuation and revocation chains (e.g., Biscuits/Macaroons, or brokered token exchange with narrower scopes and short TTLs)&lt;/li&gt;
&lt;li&gt;CIBA backchannel flows to increase permissions without interrupting the agent’s primary flow (OpenID CIBA: &lt;a href="https://openid.net/specs/openid-client-initiated-backchannel-authentication-core-1_0.html" rel="noopener noreferrer"&gt;https://openid.net/specs/openid-client-initiated-backchannel-authentication-core-1_0.html&lt;/a&gt;)&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Level 5: Fully Autonomous Agents — Attestation and Cross‑Boundary Trust
&lt;/h3&gt;

&lt;p&gt;Now the final frontier: agents crossing trust boundaries. Imagine Salesforce's agent force needing to make requests to ServiceNow's agent to fulfill a customer request. Or an AI service needing to call another AI service to accomplish a task.&lt;/p&gt;

&lt;p&gt;In all the scenarios we've discussed so far, there's been at least one shared authority: a single authorization server or at least a single organization making decisions. But when agents cross trust boundaries, this breaks down.&lt;/p&gt;

&lt;p&gt;Consider the challenge:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You don't have a shared authorization server&lt;/li&gt;
&lt;li&gt;You don't have a shared definition of scopes&lt;/li&gt;
&lt;li&gt;You might not have a shared concept of identity&lt;/li&gt;
&lt;li&gt;You need to enforce permissions and limitations across this boundary&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Traditional OAuth doesn't handle this well. It assumes a trusted relationship—either the resource server trusts the authorization server, or they're in the same organization.&lt;/p&gt;

&lt;p&gt;For cross-boundary agent calls, we need different approaches. Some possibilities:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Payment as Identity&lt;/strong&gt;: Interestingly, the payment industry has solved a version of this problem. When you make a payment, the payment network (Visa, Mastercard, etc.) acts as a trusted intermediary. Payment systems inherently carry identity information because you need to know who's being charged and who's receiving money. New payment protocols could be extended to serve as a trust mechanism for agent-to-agent calls.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Decentralized Identifiers and Public Key Infrastructure&lt;/strong&gt;: More speculative approaches use cryptographic identities (public keys) as the basis for trust. An agent proves its identity cryptographically, and the receiving service decides whether to trust that identity based on other factors (reputation, historical behavior, etc.).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Multi-party attestation&lt;/strong&gt;: For high-value transactions, multiple parties could attest to an agent's identity and behavior before granting access.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Real-world example&lt;/strong&gt;: Salesforce's Agent Force to ServiceNow integration. ServiceNow needs to know: "I'm receiving a request from Salesforce Agent Force. Can I trust it? What should I let it do?" If there's no shared authorization server, ServiceNow needs to verify Salesforce's identity through other means and make trust decisions accordingly.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Agent Attestation: Knowing What LLM Your Data Goes To&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;When an agent accesses your sensitive data, which AI model receives it? You might authorize an agent to access your email, but what LLM is running that agent? Is it Claude in your own VPC? GPT‑4 on a vendor’s servers? An open‑source Llama running on an untrusted host? Each has different privacy implications.&lt;/p&gt;

&lt;p&gt;For agents in controlled environments (your servers, Anthropic’s infrastructure), you may trust the environment itself and configure your MCP servers to only accept agents from those environments. For edge‑deployed agents (laptop, phone), use remote attestation to prove the runtime to the resource server and optionally embed attestation evidence in OAuth tokens. Also consider supply‑chain integrity: model provenance, modification, and authenticity.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Chain of Custody: End‑to‑End Visibility&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Consider a chain of calls: Claude → MCP services → internal API → third‑party API. Each hop touches sensitive data. Maintain end‑to‑end visibility and authorization at every step by using OAuth token exchange so each downstream call gets its own short‑lived, narrowly scoped token (rather than reusing the caller’s token). This yields separate audit trails and reduced blast radius. Across domains, rely on identity assertion grants to carry claims that the receiving system can verify or extend.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Looking Ahead: Voice, Video, and Ambient AI&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;As voice/video/ambient agents proliferate, borrow security patterns from SIP/XMPP/WebRTC for asynchronous, human‑absent contexts: out‑of‑band approvals, streaming policy, and robust session identity.&lt;/p&gt;

&lt;h2&gt;
  
  
  Part IV: Enterprise Requirements - Building AI Into Existing Security Infrastructure
&lt;/h2&gt;

&lt;p&gt;We've talked about the technical requirements for securing agents. But enterprises have additional needs. They have existing identity infrastructure, compliance requirements, and operational challenges that need to be addressed.&lt;/p&gt;

&lt;h3&gt;
  
  
  Enterprise Integration: SSO, SAML, SCIM
&lt;/h3&gt;

&lt;p&gt;Large organizations don't want to manage separate identities for their AI agents. They want to integrate with their existing identity infrastructure.&lt;/p&gt;

&lt;p&gt;This means MCP deployments need to support:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Single Sign-On (SSO)&lt;/strong&gt;: The ability to manage agent identities through your existing identity provider (Azure Entra, Okta, Ping, etc.). When you provision a user in your identity provider, their associated agents should be automatically provisioned. When you deprovision a user, their agents should lose access.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;SAML Assertions&lt;/strong&gt;: A way to assert identity across trust boundaries. Your identity provider can assert "This is Bob from our organization" to an MCP server or third-party service, and that service can trust the assertion because it trusts your identity provider.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;SCIM&lt;/strong&gt; (System for Cross-Domain Identity Management): A standard for provisioning and deprovisioning identities at scale. Organizations use SCIM to automatically sync user and resource identities across multiple systems. For AI agents, SCIM could handle:

&lt;ul&gt;
&lt;li&gt;Creating new agent identities when a user is added&lt;/li&gt;
&lt;li&gt;Modifying agent permissions when a user's role changes&lt;/li&gt;
&lt;li&gt;Deleting agent identities when a user leaves&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;h3&gt;
  
  
  Agent Identity Primitives
&lt;/h3&gt;

&lt;p&gt;This raises an interesting question: &lt;strong&gt;Are agents users, service accounts, or something entirely new?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Traditional identity management systems have two categories:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Users&lt;/strong&gt;: Humans with authentication credentials (passwords, MFA, etc.)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Service Accounts&lt;/strong&gt;: Non-human entities with credentials, typically used for machine-to-machine communication&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Where do AI agents fit? Some characteristics of agents suggest they're like users (they're acting on behalf of a human user). Other characteristics suggest they're like service accounts (they might be running unattended). And they have unique characteristics of their own (they're powered by LLMs, they might need attestation, they might span multiple organizations).&lt;/p&gt;

&lt;p&gt;Enterprise identity providers are starting to address this. Microsoft Entra has introduced agent identity primitives. AWS has similar capabilities in Bedrock Agent Core. SCIM is being extended with schemas for agent identities.&lt;/p&gt;

&lt;p&gt;This is still early, and there's not universal agreement on what "agent identity" means. But it's a critical piece of infrastructure for enterprise deployments.&lt;/p&gt;

&lt;h3&gt;
  
  
  Cross-App Access and Ecosystem Building
&lt;/h3&gt;

&lt;p&gt;Aaron Parecki has proposed something called &lt;a href="https://aaronparecki.com/2025/05/12/27/enterprise-ready-mcp" rel="noopener noreferrer"&gt;&lt;strong&gt;cross-app access&lt;/strong&gt;&lt;/a&gt;: the ability to log into one application and automatically have access to other applications without re-authenticating or re-authorizing for each one.&lt;/p&gt;

&lt;p&gt;Imagine: You log into your company's main platform, and your agents automatically have access to all connected MCP servers without additional authorization. This improves user experience and reduces friction.&lt;/p&gt;

&lt;p&gt;This would be implemented via a new scope (SCP for MCP) that, when granted, gives agents access to a broader ecosystem of services. The authorization server manages which services are included in this ecosystem and what permissions are available.&lt;/p&gt;

&lt;h2&gt;
  
  
  Part V: Practical Considerations and Best Practices
&lt;/h2&gt;

&lt;p&gt;We've covered a lot of theory. Let's get practical: how should you actually implement OAuth security for AI agents?&lt;/p&gt;

&lt;h3&gt;
  
  
  Public vs. Authenticated Clients
&lt;/h3&gt;

&lt;p&gt;First, understand the difference between public and authenticated clients:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Public Clients&lt;/strong&gt; (like single-page applications or native mobile apps) can't safely store secrets. If you include a client secret in JavaScript or a mobile app, anyone could extract it. So public clients use mechanisms like PKCE (Proof Key for Code Exchange) to prove their identity without a secret.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Authenticated Clients&lt;/strong&gt; (like backend services or agents running on your server) can safely store secrets. They authenticate using a client ID and client secret.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For MCP:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;If your MCP client (Claude, Cursor, etc.) is running in a sandboxed environment where you control the software, it should be an authenticated client.&lt;/li&gt;
&lt;li&gt;If your MCP server is running on a public Internet and needs to accept connections from unknown clients, you might use public clients with well-known identifiers.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Avoiding the "Dangerously Skip Permissions" Trap
&lt;/h3&gt;

&lt;p&gt;This is critical: &lt;strong&gt;don't default to dangerously skipping security checks&lt;/strong&gt;. Yes, it's tempting during development. Yes, it makes things faster. But you're building habits that will leak into production.&lt;/p&gt;

&lt;p&gt;Better approach:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Design your workflows to handle permission requests gracefully&lt;/li&gt;
&lt;li&gt;Use escalation flows (step-up auth, CIBA, elicitations) for unexpected operations&lt;/li&gt;
&lt;li&gt;Test with proper permissions enabled&lt;/li&gt;
&lt;li&gt;Only disable permission checks in truly isolated sandboxes (local development, CI/CD testing)&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Configuration and State Management
&lt;/h3&gt;

&lt;p&gt;When managing OAuth for multiple agents, you'll need to handle:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Client Configuration&lt;/strong&gt;: How do agents get their client ID, secret, and scope information? Options include:

&lt;ul&gt;
&lt;li&gt;Configuration files (risky, but simple)&lt;/li&gt;
&lt;li&gt;Environment variables (better, but still visible to developers)&lt;/li&gt;
&lt;li&gt;Secrets management systems (HashiCorp Vault, AWS Secrets Manager, etc.)&lt;/li&gt;
&lt;li&gt;Dynamic provisioning systems (agents request credentials at runtime)&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;strong&gt;Token Management&lt;/strong&gt;: Who manages tokens? Options include:

&lt;ul&gt;
&lt;li&gt;Clients manage their own tokens (complex, error-prone)&lt;/li&gt;
&lt;li&gt;A centralized token manager handles all token operations (simpler, more secure)&lt;/li&gt;
&lt;li&gt;Hybrid approaches where agents manage refresh tokens but a central system manages access tokens&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;strong&gt;State Tracking&lt;/strong&gt;: How do you track which agents have which permissions? You need:

&lt;ul&gt;
&lt;li&gt;Audit logging (every access, every permission grant/revocation)&lt;/li&gt;
&lt;li&gt;Token management dashboards (see what tokens exist, revoke them immediately if needed)&lt;/li&gt;
&lt;li&gt;Permission audit reports (who can access what)&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;h3&gt;
  
  
  Testing and Observability
&lt;/h3&gt;

&lt;p&gt;When you're deploying OAuth-secured agents, you need to think about:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Test Scenarios&lt;/strong&gt;: Test what happens when:

&lt;ul&gt;
&lt;li&gt;A token expires mid-operation&lt;/li&gt;
&lt;li&gt;A token is revoked while an agent is using it&lt;/li&gt;
&lt;li&gt;An agent requests a scope it doesn't have&lt;/li&gt;
&lt;li&gt;The authorization server is unavailable&lt;/li&gt;
&lt;li&gt;The resource server can't verify a token&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;strong&gt;Observability&lt;/strong&gt;: Instrument your systems to track:

&lt;ul&gt;
&lt;li&gt;Token generation and expiration&lt;/li&gt;
&lt;li&gt;Permission checks and denials&lt;/li&gt;
&lt;li&gt;Failed authentication attempts&lt;/li&gt;
&lt;li&gt;Unusual access patterns (agent accessing a resource it hasn't used before, accessing at unusual times, etc.)&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;h2&gt;
  
  
  Conclusion: Dream Big, Design Carefully
&lt;/h2&gt;

&lt;p&gt;We started with an impossible choice: give agents broad access and accept security risks, or limit their capabilities and sacrifice business value.&lt;/p&gt;

&lt;p&gt;OAuth gives us a third path: dynamic, fine-grained, auditable access control that scales from simple chatbots to fully autonomous systems.&lt;/p&gt;

&lt;p&gt;But getting there requires:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Understanding OAuth fundamentals&lt;/strong&gt;: The three-role architecture, scopes, tokens, and flows&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Implementing OAuth correctly in MCP&lt;/strong&gt;: Using separate authorization servers, not collapsing the architecture&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Planning for increasing autonomy&lt;/strong&gt;: Thinking through what each level of agent autonomy requires&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Building the missing pieces&lt;/strong&gt;: Agent identity, agent attestation, transactional authorization, chain of custody, and cross-boundary trust&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Enterprise integration&lt;/strong&gt;: SSO, SCIM, audit logging, and identity primitives&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Practical implementation&lt;/strong&gt;: Avoiding shortcuts, managing configuration and state, and building observability&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This isn't quick. Standards development takes time. Implementation takes even longer. But the alternative - scaling insecure agent deployments - is worse.&lt;/p&gt;

&lt;p&gt;The ultimate goal is elegant: &lt;strong&gt;safely automating work while maintaining human control&lt;/strong&gt;. Not removing humans from the loop, but freeing them to focus on strategy and exceptions rather than routine tasks.&lt;/p&gt;

&lt;p&gt;One core assumption underlines this - throughout this post we’ve treated agents as first-class OAuth clients – that is, as identifiable principals with their own client IDs, policies, and audit trails – even when they’re ultimately acting on behalf of a human user. In practice that doesn’t mean every ephemeral agent run becomes a separate “user” in your directory; it means you model agents (and sub-agents) as distinct, addressable software identities that you can scope, attest, monitor, and deprovision just like any other critical application (More on this in the next post).&lt;/p&gt;

&lt;p&gt;To get there, we need to dream big - imagine what's possible with autonomous agents - but design carefully. Every security decision we make now creates patterns for thousands of agents in the future.&lt;/p&gt;

&lt;p&gt;Darren (from the other room) just wants to automate his job. Let's build the infrastructure to let him do that safely.&lt;/p&gt;

</description>
      <category>security</category>
      <category>ai</category>
      <category>mcp</category>
      <category>resources</category>
    </item>
  </channel>
</rss>
