<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>Security on Model Context Protocol Blog</title><link>https://blog.modelcontextprotocol.io/tags/security/</link><description>Recent content in Security on Model Context Protocol Blog</description><image><title>Model Context Protocol Blog</title><url>https://blog.modelcontextprotocol.io/og-image.png</url><link>https://blog.modelcontextprotocol.io/og-image.png</link></image><generator>Hugo -- 0.148.0</generator><language>en-us</language><copyright>Copyright © Model Context Protocol a Series of LF Projects, LLC.
For web site terms of use, trademark policy and other project policies please see https://lfprojects.org.</copyright><lastBuildDate>Wed, 18 Mar 2026 21:40:20 +0000</lastBuildDate><atom:link href="https://blog.modelcontextprotocol.io/tags/security/index.xml" rel="self" type="application/rss+xml"/><item><title>Tool Annotations as Risk Vocabulary: What Hints Can and Can't Do</title><link>https://blog.modelcontextprotocol.io/posts/2026-03-16-tool-annotations/</link><pubDate>Mon, 16 Mar 2026 00:00:00 +0000</pubDate><guid>https://blog.modelcontextprotocol.io/posts/2026-03-16-tool-annotations/</guid><description>&lt;p>MCP tool annotations were introduced nearly a year ago as a way for servers to describe the behavior of their tools — whether they&amp;rsquo;re read-only, destructive, idempotent, or reach outside their local environment. Since then, the community has filed five independent &lt;a href="https://modelcontextprotocol.io/community/sep-guidelines">Specification Enhancement Proposals&lt;/a> (SEPs) proposing new annotations, driven in part by a sharper collective understanding of where risk actually lives in agentic workflows. This post recaps where tool annotations are today, what they can and can&amp;rsquo;t realistically do, and offers a framework for evaluating new proposals.&lt;/p></description><content:encoded><![CDATA[<p>MCP tool annotations were introduced nearly a year ago as a way for servers to describe the behavior of their tools — whether they&rsquo;re read-only, destructive, idempotent, or reach outside their local environment. Since then, the community has filed five independent <a href="https://modelcontextprotocol.io/community/sep-guidelines">Specification Enhancement Proposals</a> (SEPs) proposing new annotations, driven in part by a sharper collective understanding of where risk actually lives in agentic workflows. This post recaps where tool annotations are today, what they can and can&rsquo;t realistically do, and offers a framework for evaluating new proposals.</p>
<h2 id="what-tool-annotations-are">What Tool Annotations Are</h2>
<p><a href="https://modelcontextprotocol.io/specification/2025-11-25/server/tools">Tool annotations</a> shipped in the <code>2025-03-26</code> spec revision. The current <a href="https://modelcontextprotocol.io/specification/2025-11-25/schema#toolannotations"><code>ToolAnnotations</code> interface</a> looks like this:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-typescript" data-lang="typescript"><span style="display:flex;"><span><span style="color:#66d9ef">interface</span> <span style="color:#a6e22e">ToolAnnotations</span> {
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">title?</span>: <span style="color:#66d9ef">string</span>;
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">readOnlyHint?</span>: <span style="color:#66d9ef">boolean</span>; <span style="color:#75715e">// default: false
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>  <span style="color:#a6e22e">destructiveHint?</span>: <span style="color:#66d9ef">boolean</span>; <span style="color:#75715e">// default: true
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>  <span style="color:#a6e22e">idempotentHint?</span>: <span style="color:#66d9ef">boolean</span>; <span style="color:#75715e">// default: false
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>  <span style="color:#a6e22e">openWorldHint?</span>: <span style="color:#66d9ef">boolean</span>; <span style="color:#75715e">// default: true
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>}
</span></span></code></pre></div><p>Every property is a <strong>hint</strong>. The spec is explicit about this: annotations are not guaranteed to faithfully describe tool behavior, and clients <strong>must</strong> treat them as untrusted unless they come from a trusted server.</p>
<p>These four boolean hints give clients a basic risk vocabulary:</p>
<ul>
<li><strong><code>readOnlyHint</code></strong>: Does the tool modify its environment?</li>
<li><strong><code>destructiveHint</code></strong>: If it does modify things, is the change destructive (as opposed to additive)?</li>
<li><strong><code>idempotentHint</code></strong>: Can you safely call it again with the same arguments?</li>
<li><strong><code>openWorldHint</code></strong>: Does the tool interact with an open world of external entities, or is its domain closed?</li>
</ul>
<p>The first three hints mostly answer a preflight question: should the client ask for confirmation before calling this tool? <code>openWorldHint</code> is different. It&rsquo;s about where the tool reaches and what its output might carry back, which matters after the call as much as before. It&rsquo;s also the hint most sensitive to deployment context. &ldquo;External&rdquo; might mean anything outside a corporate network or anything beyond the local machine, depending on where the server runs. The safest posture is to treat anything a tool considers <strong>external</strong> as a potential source of untrusted content.</p>
<p>The defaults are deliberately cautious: a tool with no annotations is assumed to be non-read-only, potentially destructive, non-idempotent, and open-world. The spec assumes the worst until told otherwise. Making annotations optional kept the barrier to entry low for server authors, but it also means coverage is uneven. Many servers ship without them, and clients vary in how strictly they honor the pessimistic defaults. Closing that gap is part of what the current wave of SEPs is trying to do.</p>
<h2 id="how-we-got-here">How We Got Here</h2>
<p>The <a href="https://github.com/modelcontextprotocol/modelcontextprotocol/pull/185">original proposal discussion</a> surfaced a question that still shapes every annotation proposal today: <strong>what value do hints provide when they can&rsquo;t be trusted?</strong> MCP co-creator Justin Spahr-Summers <a href="https://github.com/modelcontextprotocol/modelcontextprotocol/pull/185#discussion_r2010043988">raised it directly during review</a>:</p>
<blockquote>
<p>I think the information itself, <em>if it could be trusted</em>, would be very useful, but I wonder how a client makes use of this flag knowing that it&rsquo;s <em>not</em> trustable.</p></blockquote>
<p>Basil Hosmer <a href="https://github.com/modelcontextprotocol/modelcontextprotocol/pull/185#discussion_r2010702646">pushed the point further</a>, arguing that clients should ignore annotations from untrusted servers entirely:</p>
<blockquote>
<p>&ldquo;Clients should ignore annotations from untrusted servers&rdquo; applies to <strong>all</strong> annotations, even <code>title</code> — but especially the ones that describe operational properties.</p></blockquote>
<p>The spec landed on a compromise: call everything a <strong>hint</strong>, require clients to treat hints as untrusted by default, and leave it to each client to decide how much weight to give them based on what it knows about the server.</p>
<p>The interface has stayed small since then, and that&rsquo;s been intentional. <a href="https://github.com/modelcontextprotocol/modelcontextprotocol/pull/663"><code>title</code> went in</a> because it&rsquo;s just a display name with no trust implications. <code>taskHint</code> was proposed as an annotation but <a href="https://github.com/modelcontextprotocol/modelcontextprotocol/pull/1854">landed as <code>Tool.execution</code> instead</a>, on the grounds that execution metadata isn&rsquo;t really a behavioral hint. Earlier takes on <a href="https://github.com/modelcontextprotocol/modelcontextprotocol/pull/489">stateless, streaming, and async annotations</a> and <a href="https://github.com/modelcontextprotocol/modelcontextprotocol/issues/1075">security annotations</a> are worth knowing about too, since the same concerns show up again in the SEPs open today.</p>
<h2 id="whats-open-now">What&rsquo;s Open Now</h2>
<p>Five SEPs currently propose new annotations or closely related capabilities:</p>
<table>
  <thead>
      <tr>
          <th>SEP</th>
          <th>Proposal</th>
          <th>Status</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><a href="https://github.com/modelcontextprotocol/modelcontextprotocol/pull/1913">#1913</a></td>
          <td>Trust and Sensitivity Annotations</td>
          <td>Draft</td>
      </tr>
      <tr>
          <td><a href="https://github.com/modelcontextprotocol/modelcontextprotocol/pull/1984">#1984</a></td>
          <td>Comprehensive Tool Annotations for Governance/UX</td>
          <td>Draft</td>
      </tr>
      <tr>
          <td><a href="https://github.com/modelcontextprotocol/modelcontextprotocol/issues/1561">#1561</a></td>
          <td><code>unsafeOutputHint</code></td>
          <td>Proposal</td>
      </tr>
      <tr>
          <td><a href="https://github.com/modelcontextprotocol/modelcontextprotocol/issues/1560">#1560</a></td>
          <td><code>secretHint</code></td>
          <td>Proposal</td>
      </tr>
      <tr>
          <td><a href="https://github.com/modelcontextprotocol/modelcontextprotocol/issues/1487">#1487</a></td>
          <td><code>trustedHint</code></td>
          <td>Proposal</td>
      </tr>
  </tbody>
</table>
<p>The trust and sensitivity work is co-authored by GitHub and OpenAI based on gaps they hit running MCP in production. A Tool Annotations Interest Group is forming to work through these alongside related proposals like <a href="https://github.com/modelcontextprotocol/modelcontextprotocol/pull/1862">tool resolution and preflight checks</a>. Reviewing each one in isolation makes it easy to miss how a given annotation interacts with others, and it&rsquo;s those interactions that determine how risky a tool actually is in a given session.</p>
<h2 id="the-lethal-trifecta-why-combinations-matter">The Lethal Trifecta: Why Combinations Matter</h2>
<p>Simon Willison&rsquo;s <a href="https://simonwillison.net/2025/Jun/16/the-lethal-trifecta/">lethal trifecta</a> names three capabilities that, when combined, create the conditions for data theft: <strong>access to private data</strong>, <strong>exposure to untrusted content</strong>, and <strong>the ability to externally communicate</strong>. The attack is simple: LLMs follow instructions in content, and they can&rsquo;t reliably tell a user&rsquo;s instructions apart from ones an attacker embedded in a web page, email, or calendar event. If the agent has all three capabilities, an attacker who controls one piece of untrusted content can trick the model into reading private data and sending it out.</p>
<p><a href="https://layerxsecurity.com/blog/claude-desktop-extensions-rce/">Researchers have demonstrated this</a> using a malicious Google Calendar event description, an MCP calendar server, and a local code execution tool. The code execution tool is the linchpin in that chain — any agent with unrestrained shell access sits one injected instruction away from exfiltration, and that&rsquo;s true whether the tool arrived via MCP or was built into the host. What MCP adds is the ease of assembling the chain: users routinely combine tools from several servers in one session, so the risk profile is a property of the session, not of any single server.</p>
<p>One commenter on Willison&rsquo;s newsletter connected this directly to tool annotations:</p>
<blockquote>
<p>If the current state is tainted, block (or require explicit human approval for) any action with exfiltration potential&hellip; This also makes MCP&rsquo;s mix-and-match story extra risky unless tools carry metadata like: <code>reads_private_data</code> / <code>sees_untrusted_content</code> / <code>can_exfiltrate</code> — and the runtime enforces &rsquo;never allow all three in a single tainted execution path.'</p></blockquote>
<p>Several of the open SEPs are trying to define that kind of metadata so a client can spot when a session has all three legs of the trifecta available.</p>
<h2 id="what-annotations-can-do">What Annotations Can Do</h2>
<p><strong>Drive confirmation prompts.</strong> A tool marked <code>readOnlyHint: true</code> from a trusted server might be auto-approved, while <code>destructiveHint: true</code> gets a confirmation step. A user asks their agent to clean up old files, the agent reaches for <code>delete_file</code>, and the client shows a dialog listing what&rsquo;s about to be deleted before anything happens. This is the most common use of annotations today.</p>
<p><strong>Enable graduated trust.</strong> An enterprise running its own internal MCP servers behind auth has a very different trust relationship than someone installing a random server off the internet. Annotations from the first can drive policy; from the second they&rsquo;re informational at best. In practice most clients still treat installation itself as the trust signal and don&rsquo;t distinguish further, so this is more of a design opportunity than a widely shipped feature.</p>
<p><strong>Improve UX.</strong> <code>title</code> is just a display name. Annotations that help users understand what tools do without running them are useful regardless of trust. This is largely unexploited today: no MCP client lets users filter tools by annotation values, and none surface annotations as context in approval prompts. GitHub&rsquo;s read-only mode is the closest production analog, enabled by about 17% of users.</p>
<p><strong>Feed policy engines.</strong> Annotations can be one input among several into a policy engine enforcing rules like &ldquo;no destructive tools without approval&rdquo; or &ldquo;open-world tools are blocked in sessions that have accessed private data.&rdquo; The hints don&rsquo;t need to be perfectly trustworthy if the engine cross-references other signals.</p>
<p>Adoption across all of these is uneven, partly because MCP users split into two camps. Developers building autonomous agents treat confirmations as friction and lean on sandboxing instead. Enterprise adopters want more annotations than currently exist. One camp barely notices annotations, the other wants a much richer vocabulary.</p>
<h2 id="what-annotations-cant-do">What Annotations Can&rsquo;t Do</h2>
<p><strong>They don&rsquo;t make the model resist prompt injection.</strong> Annotations are static metadata on a tool definition; nothing in them tells the model to ignore malicious instructions it reads from a calendar event. What an annotation like <code>seesUntrustedData</code> <em>could</em> do is let the client treat the session as tainted once that tool runs and tighten approvals from then on — a defense at the host layer, not inside the model.</p>
<p><strong>An untrusted server can lie.</strong> A server can claim <code>readOnlyHint: true</code> and delete your files anyway. This is why the spec says clients <strong>must</strong> treat annotations from untrusted servers as untrusted.</p>
<p><strong>They aren&rsquo;t enforcement.</strong> If you need a guarantee that a tool can&rsquo;t exfiltrate data, that&rsquo;s a job for network controls or sandboxing, not a boolean hint. We made the <a href="https://blog.modelcontextprotocol.io/posts/2025-11-03-using-server-instructions/">same point about server instructions</a>: don&rsquo;t rely on soft signals for things that need to be hard guarantees.</p>
<p><strong>A tool&rsquo;s risk depends on what else is in the session.</strong> <code>search_emails</code> isn&rsquo;t safe or dangerous on its own; it depends on what other tools the agent has. Annotations on one tool can&rsquo;t tell you that.</p>
<h2 id="questions-for-evaluating-new-annotations">Questions for Evaluating New Annotations</h2>
<p>As a starting point for the Interest Group, we&rsquo;re putting forward a tentative set of questions to ask of each annotation proposal. These will likely change as the group works through the open SEPs.</p>
<h3 id="1-what-client-behavior-does-it-enable">1. What client behavior does it enable?</h3>
<p>Maintainer Jonathan Hefner <a href="https://github.com/modelcontextprotocol/modelcontextprotocol/pull/616#issuecomment-3330296295">put this directly on an early draft</a> of what became the governance/UX annotations proposal:</p>
<blockquote>
<p>It&rsquo;s not clear to me exactly how a client would behave differently when presented with these annotations.</p></blockquote>
<p>If there&rsquo;s no concrete client action that changes based on the annotation, it probably doesn&rsquo;t belong in the protocol. Each of the existing hints maps to at least one decision a client can make:</p>
<table>
  <thead>
      <tr>
          <th>Hint</th>
          <th>Example client behavior</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><code>readOnlyHint: true</code></td>
          <td>Skip the confirmation dialog</td>
      </tr>
      <tr>
          <td><code>destructiveHint: true</code></td>
          <td>Show a warning before executing</td>
      </tr>
      <tr>
          <td><code>idempotentHint: true</code></td>
          <td>Safe to retry on failure</td>
      </tr>
      <tr>
          <td><code>openWorldHint: true</code></td>
          <td>Scrutinize output for untrusted content; flag a trust-boundary cross</td>
      </tr>
  </tbody>
</table>
<h3 id="2-does-it-need-trust-to-be-useful">2. Does it need trust to be useful?</h3>
<p><code>title</code> is useful even from an untrusted server; worst case you show a bad display name. <code>readOnlyHint</code> from an untrusted server isn&rsquo;t actionable, because the decision it informs — whether to skip a confirmation — only makes sense if you believe the hint. Proposals should say where they fall on that spectrum, since it determines which clients can actually use them.</p>
<h3 id="3-could-_meta-handle-it-instead">3. Could <code>_meta</code> handle it instead?</h3>
<p>Tools already have <a href="https://modelcontextprotocol.io/specification/2025-11-25/basic#_meta"><code>_meta</code></a>, which accepts namespaced keys like <code>com.example/my-field</code> for exactly this kind of metadata. If an annotation only matters to one deployment style where the same organization runs both the server and the client, <code>_meta</code> is a reasonable home for it. It&rsquo;s also a good way to prove out an idea before writing a SEP: ship a namespaced field, see how it holds up in production, and come back with a proposal backed by actual usage instead of a design doc. What <code>_meta</code> can&rsquo;t do is drive behavior in off-the-shelf clients — those won&rsquo;t read a key they&rsquo;ve never heard of, so anything aimed at ecosystem-wide UX still needs a real annotation.</p>
<h3 id="4-does-it-help-reason-about-combinations">4. Does it help reason about combinations?</h3>
<p>Annotations that help a client understand what happens when tools are used together are worth more than ones that only describe a tool in isolation. <code>openWorldHint</code> already hints at this: a client could use it to notice that a session mixes closed-world data access tools with open-world communication tools.</p>
<h3 id="5-is-it-a-hint-or-a-contract">5. Is it a hint or a contract?</h3>
<p>Hints inform decisions; contracts enforce them. If a proposal&rsquo;s value depends on the annotation being true, it&rsquo;s asking for a contract, and the right place for that is the authorization layer, the transport, or the runtime rather than <code>ToolAnnotations</code>. Hints work best when they&rsquo;re still useful even if some servers get them wrong.</p>
<h2 id="where-this-is-heading">Where This Is Heading</h2>
<p>The Tool Annotations Interest Group includes participants from Microsoft, OpenAI, AWS, Cloudflare, and Anthropic, among others. These are companies that build both MCP hosts and MCP servers at scale, so they sit on both sides of the annotation contract: they need annotations expressive enough to surface risk to their users, and they need to author annotations that other clients will actually honor. Among the questions on the group&rsquo;s agenda are whether annotations belong on tool responses as well as tool definitions, and whether any annotations should be evaluated at runtime rather than declared statically.</p>
<p>In the meantime, the existing annotations are worth using. If you&rsquo;re writing a server, set <code>readOnlyHint: true</code> on read-only tools, <code>destructiveHint: false</code> on additive operations, and <code>openWorldHint: false</code> on closed-domain tools. If you&rsquo;re writing a client, treat annotations from untrusted servers as informational and lean on them for UX, but keep your actual safety guarantees in deterministic controls. And if you&rsquo;re thinking of proposing a new annotation, the questions above are a good place to start shaping it.</p>
<h2 id="get-involved">Get Involved</h2>
<p>The Tool Annotations Interest Group is forming now. If you&rsquo;re interested in contributing:</p>
<ul>
<li>Review the open SEPs linked above and leave feedback</li>
<li>Join the conversation in <code>#tool-annotations-ig</code> on the <a href="https://modelcontextprotocol.io/community/communication#discord">MCP Contributors Discord</a></li>
</ul>
<h2 id="acknowledgements">Acknowledgements</h2>
<p>This post draws on discussions with the MCP community, particularly the contributors involved in the Tool Annotations Interest Group proposal, including <strong>Sam Morrow</strong> (GitHub), <strong>Robert Reichel</strong> (OpenAI), <strong>Den Delimarsky</strong> (Anthropic), <strong>Nick Cooper</strong> (OpenAI), <strong>Connor Peet</strong> (Microsoft), and <strong>Luca Chang</strong> (AWS).</p>
]]></content:encoded></item><item><title>Evolving OAuth Client Registration in the Model Context Protocol</title><link>https://blog.modelcontextprotocol.io/posts/client_registration/</link><pubDate>Fri, 22 Aug 2025 10:00:00 +0000</pubDate><guid>https://blog.modelcontextprotocol.io/posts/client_registration/</guid><description>&lt;p>The Model Context Protocol (MCP) has adopted OAuth 2.1 as the foundation for its authorization framework. A key part of the authorization flow that MCP is particularly reliant on is &lt;strong>client registration&lt;/strong>.&lt;/p>
&lt;p>This is especially important in a world where clients and servers don&amp;rsquo;t have a pre-existing relationship - we can&amp;rsquo;t assume that we will always know which MCP clients will connect to which MCP servers. This design highlights two challenges that need to be addressed:&lt;/p></description><content:encoded><![CDATA[<p>The Model Context Protocol (MCP) has adopted OAuth 2.1 as the foundation for its authorization framework. A key part of the authorization flow that MCP is particularly reliant on is <strong>client registration</strong>.</p>
<p>This is especially important in a world where clients and servers don&rsquo;t have a pre-existing relationship - we can&rsquo;t assume that we will always know which MCP clients will connect to which MCP servers. This design highlights two challenges that need to be addressed:</p>
<ul>
<li>Operational issues with managing client IDs via <a href="https://datatracker.ietf.org/doc/html/rfc7591">Dynamic Client Registration</a> (DCR)</li>
<li>Preventing client impersonation</li>
</ul>
<p>If you&rsquo;re already familiar with OAuth and the current state of client registration in MCP, skip to <a href="#two-distinct-challenges-in-mcp-client-registration">Two Distinct Challenges in MCP Client Registration</a>.</p>
<h2 id="background-on-oauth">Background on OAuth</h2>
<p>A protected MCP server that implements OAuth 2.1 should allow a user to grant a client access to itself and prevent attempts to trick the user into granting access to a client they didn&rsquo;t intend to use via phishing.</p>
<p>The authorization flow can be best described by looking at this sequence diagram:</p>
<pre class="mermaid">
  sequenceDiagram
   participant Client
   participant User
   participant AuthServer as Authorization Server
   participant Resource as Resource Server

   Client-&gt;&gt;User: 1. Redirect to authorization server
   User-&gt;&gt;AuthServer: Navigate to auth URL
   AuthServer-&gt;&gt;User: 2. Display consent screen
   User-&gt;&gt;AuthServer: Approve access
   AuthServer-&gt;&gt;Client: 3. Redirect with authorization code
   Client-&gt;&gt;AuthServer: 4. Exchange code for access token
   AuthServer--&gt;&gt;Client: Access token (saved)
   Client-&gt;&gt;Resource: 5. Request with access token
   Resource--&gt;&gt;Client: Protected resource
</pre>
<p>This flow requires a few steps to be performed to acquire an access token:</p>
<ol>
<li>The client directs the user to an authorization UI provided by the authorization server</li>
<li>The authorization server displays a consent screen to the user</li>
<li>User approves client access and the authorization server redirects the user back to the client with an access code</li>
<li>Client exchanges the access code for a set of tokens, which are cached locally</li>
<li>Client uses the access token to access the MCP server</li>
</ol>
<p>To be able to initiate this flow, however, the authorization server first needs some basic information about the client that is kicking off the authorization process:</p>
<ol>
<li><strong>Client name</strong>: Human readable text to display in the consent screen to help the user decide whether they want to grant access.</li>
<li><strong>Redirect URL</strong>: The destination to send the authorization code back to if the user consents.</li>
</ol>
<p>In order to prevent a malicious client from tricking a user into granting access they didn&rsquo;t intend to grant, the authorization server must be able to trust the client information it has.</p>
<p>For example, a malicious client could claim to be <code>Claude Desktop</code> on the consent screen while actually being owned by someone not affiliated with Claude Desktop developers. Seeing the client information on the consent screen, users might grant access thinking they&rsquo;re authorizing the legitimate Claude Desktop, not realizing that some malicious client now has access to their account.</p>
<h2 id="improving-client-registration-in-mcp">Improving Client Registration in MCP</h2>
<p>For MCP users, a common pattern is to connect to an MCP server by using its URL directly in an MCP client.</p>
<p>This goes against the typical OAuth authorization pattern because the user is selecting the resource server to connect to rather than the client developer. This problem is compounded by the fact that there is an unbounded number of authorization servers that an MCP server may use, meaning that clients need to be able to complete the authorization flow regardless of the provider used.</p>
<p>Some client developers have implemented pre-registration with a select few authorization servers. In this scenario, the client doesn&rsquo;t need to rely on DCR when it detects an authorization server it knows. However, this is a solution that doesn&rsquo;t scale given the breadth of the MCP ecosystem - it&rsquo;s impossible to have every client be registered with every authorization server there is.
To mitigate this challenge, we set out to outline some of the goals that we wanted to achieve with improving the client registration experience:</p>
<ol>
<li><strong>Clients</strong>: Client developers don&rsquo;t need to implement pre-registration and distribute a client ID for each authorization server MCP servers might be using.</li>
<li><strong>Users</strong>: Users don&rsquo;t need to go through a pre-registration process themselves and manually specify a client ID for every MCP server they connect to.</li>
<li><strong>Authorization servers</strong>:</li>
</ol>
<ul>
<li><strong>Trust in Metadata</strong>: Authorization servers have a way to trust the metadata they associate with a client, such as name and redirect URL.</li>
<li><strong>Single Client ID per App</strong>: Authorization servers can have a single client ID per client for governance and management purposes</li>
<li><strong>Selective Allow/Deny</strong>: Authorization servers can selectively allow or deny clients based on their policies.</li>
<li><strong>Database Management</strong>: Authorization servers do not need to handle an unbounded database or expiration flows for every new client registration.</li>
</ul>
<p>Currently, none of our existing client registration approaches satisfy all of these requirements. Pre-registration requires too much effort in a highly variable setting (unbounded number of clients connecting to unbounded number of servers), while DCR reduces effort but creates operational issues that a lot of the authorization servers are not ready to tackle yet.</p>
<h2 id="two-distinct-challenges-in-mcp-client-registration">Two Distinct Challenges in MCP Client Registration</h2>
<p>After extensive discussion with MCP server implementers, we&rsquo;ve identified that a few competing solutions to the registration problem were addressing two distinct issues:</p>
<ol>
<li><strong>Operational limitations</strong> of Dynamic Client Registration in open environments</li>
<li><strong>Client identity and impersonation</strong> risks across different deployment scenarios</li>
</ol>
<h2 id="challenge-1-operational-limitations-of-dynamic-client-registration"><strong>Challenge 1: Operational Limitations of Dynamic Client Registration</strong></h2>
<h3 id="the-dcr-model-mismatch"><strong>The DCR Model Mismatch</strong></h3>
<p>The DCR design takes the pre-registration pattern available in modern OAuth-based authorization servers and makes it available via an API. In fully open environments like MCP, DCR really puts the spotlight on a few operational challenges that an open registration endpoint introduces:</p>
<p><strong>For authorization servers:</strong></p>
<ul>
<li><strong>Unbounded database growth</strong>: Every time a user connects a client to an MCP server, a new registration is created with the authorization server unless the client already has one. Registrations are also not portable, so using Claude Desktop on your Windows machine, and then jumping to Claude Desktop on macOS will create two distinct client registrations.</li>
<li><strong>Client expiry &ldquo;black hole&rdquo;</strong>: There&rsquo;s no way to tell a client that its ID is invalid without creating an open redirect vulnerability. Clients have to implement their own heuristics for client ID management.</li>
<li><strong>Per-instance confusion</strong>: Each client instance typically gets its own client ID even when using the same application, but on different machines or across different users. From an auditing perspective, an authorization server administrator may see hundreds (if not thousands) of records for the same application without any rhyme or reason.</li>
<li><strong>Denial-of-Service vulnerability</strong>: An unauthenticated <code>/register</code> endpoint writes to a database within the authorization server, meaning that tenant admins now need to worry about rate limiting or policy controls (e.g., hosts allowed to register clients).</li>
</ul>
<p><strong>For clients:</strong></p>
<ul>
<li><strong>Extra overhead</strong>: Managing registration state and another secret beyond access/refresh tokens</li>
<li><strong>No validity checking</strong>: Can&rsquo;t verify if a client ID is still valid</li>
<li><strong>Unclear lifecycle</strong>: No guidance on when to re-register or update credentials</li>
</ul>
<h3 id="solution-client-id-metadata-documents-cimd"><strong>Solution: Client ID Metadata Documents (CIMD)</strong></h3>
<p>Client ID Metadata Documents (CIMD), described in <a href="https://www.ietf.org/archive/id/draft-parecki-oauth-client-id-metadata-document-03.html">OAuth Client ID Metadata Document</a> and implemented by Bluesky, elegantly sidestep these operational issues.</p>
<p>Instead of a registration step, clients use an HTTPS metadata URL as their client ID directly. The server fetches the metadata from the URL at authorization time:</p>
<pre class="mermaid">
  sequenceDiagram
   participant Client
   participant AuthServer
   participant MetadataURL

   Client-&gt;&gt;AuthServer: Authorization request (client_id=https://app.com/oauth.json)
   AuthServer-&gt;&gt;MetadataURL: GET https://app.com/oauth.json
   MetadataURL--&gt;&gt;AuthServer: {name: &#34;App&#34;, redirect_uris: [...]}
   AuthServer-&gt;&gt;Client: Show consent screen &amp; continue flow
</pre>
<p>This addresses all the operational issues:</p>
<ul>
<li><strong>No unbounded database growth</strong>: Servers fetch metadata on-demand (can cache for performance)</li>
<li><strong>No expiry management</strong>: The URL is the ID - it doesn&rsquo;t expire</li>
<li><strong>Natural per-app model</strong>: One URL per application, not per user</li>
<li><strong>No registration endpoint</strong>: No unauthenticated write operations</li>
</ul>
<p>The cost? Clients need to host a metadata document at an HTTPS URL. For web applications, this is trivial. For desktop applications, this typically means hosting on their backend infrastructure.</p>
<h2 id="challenge-2-client-identity-and-impersonation"><strong>Challenge 2: Client Identity and Impersonation</strong></h2>
<p>The second challenge is orthogonal to the DCR vs. CIMD debate - it&rsquo;s about trusting that a client is who it claims to be. This problem will exist regardless of how the registration process is implemented.</p>
<p>For web-based clients, trust is more straightforward, as we have an HTTPS domain that&rsquo;s tied to a certificate authority. For desktop clients, if the client can&rsquo;t offload its authorization to existing backend infrastructure, there is difficulty trusting the client is legitimate and unmodified.</p>
<h3 id="the-trust-spectrum">The Trust Spectrum</h3>
<p>We can map impersonation scenarios on two axes: attacker cost and mitigation complexity.</p>
<p><img alt="Mitigation Cost vs Attack Cost" loading="lazy" src="/posts/client_registration/mitigation-attack-cost.png"></p>
<p><strong>Low attacker cost/Low mitigation complexity: Domain-based attacks</strong></p>
<ul>
<li><strong>Attack</strong>: Register malicious callback URI and claim to be <code>Claude Desktop</code></li>
<li><strong>Cost</strong>: Trick user into clicking a link and consenting</li>
<li><strong>Mitigation</strong>:
<ul>
<li>Restrict trusted domains/URLs</li>
<li>Show warnings for unknown domains</li>
<li>Works with both DCR and CIMD</li>
</ul>
</li>
</ul>
<p><strong>Medium attacker cost/Medium mitigation complexity: <code>localhost</code> impersonation</strong></p>
<ul>
<li><strong>Attack</strong>: Run malicious app on <code>localhost:8080</code>, impersonate legitimate client</li>
<li><strong>Cost</strong>: Trick user into running a malicious application (plus consenting for that app to have data access)</li>
<li><strong>Problem</strong>: Desktop apps can&rsquo;t hold secrets, hard to prove identity</li>
</ul>
<p><strong>High attacker cost/High mitigation complexity: Platform-attested applications</strong></p>
<ul>
<li><strong>Attack</strong>: Get malicious client signed by a trusted authority</li>
<li><strong>Cost</strong>: Extremely high - requires compromising certification vendor processes</li>
<li><strong>Mitigation</strong>: platform system-level attestation (future work)</li>
</ul>
<h3 id="solution-software-statements-for-desktop-applications"><strong>Solution: Software Statements for Desktop Applications</strong></h3>
<p>To broadly solve the client impersonation for the middle tier as well as to prevent <code>localhost</code> impersonation we need signed software statements. Implementing this would require:</p>
<ol>
<li>Client hosts a JSON Web Key Set (JWKS) on their backend</li>
<li>Client authenticates the user through their own flow</li>
<li>The client-owned backend service issues a short-lived, signed JWT attesting to the client&rsquo;s identity</li>
<li>Client includes this JWT in the OAuth flow</li>
<li>Authorization server verifies the JWT against the trusted JWKS</li>
</ol>
<p>This dramatically raises the bar for client impersonation, as an attacker would need to:</p>
<ul>
<li>Compromise the client&rsquo;s backend infrastructure, or</li>
<li>Successfully impersonate the client&rsquo;s authentication flow</li>
</ul>
<p>Crucially, <strong>software statements work with both DCR and CIMD</strong>. They&rsquo;re not a competing solution - they&rsquo;re a complementary security layer.</p>
<h3 id="future-platform-level-attestation"><strong>Future: Platform-Level Attestation</strong></h3>
<p>The strongest protection would be platform-level attestation, e.g. having macOS, Windows, or Android attest that a piece of software is legitimate.</p>
<p>Having OS-level attestation would make client impersonation unreasonably expensive. While the exact way this ties into a software statement is yet to be prototyped, the general direction is threading platform-level application identity validation through to the OAuth flow.</p>
<h2 id="the-complementary-path-forward"><strong>The Complementary Path Forward</strong></h2>
<p>While we&rsquo;re looking at all available options, it&rsquo;s important to note that we&rsquo;re not choosing between solutions. We&rsquo;re exploring complementary approaches for distinct problems:</p>
<p><strong>For operational issues</strong>: We are looking at adding CIMD support in favor of DCR</p>
<ul>
<li>Keep DCR for backward compatibility</li>
<li>Recommend CIMD for new implementations</li>
<li>Both achieve the same authorization goal</li>
</ul>
<p><strong>For trust issues</strong>: Layering software statements on top</p>
<ul>
<li>Optional enhancement for both DCR and CIMD</li>
<li>Required only when <code>localhost</code> impersonation is a concern</li>
<li>Authorization servers choose their required trust level</li>
</ul>
<h2 id="security-considerations"><strong>Security Considerations</strong></h2>
<p>Both CIMD and software statements require authorization servers to make outbound HTTPS requests, potentially to untrusted domains. Implementations must:</p>
<ul>
<li>Prevent SSRF attacks by blocking internal network access</li>
<li>Implement timeouts and size limits</li>
<li>Consider caching strategies for performance</li>
<li>Validate response formats strictly</li>
</ul>
<p>If we adopt these approaches, we’ll need good best practices and SDK support to help avoid vulnerabilities and provide a easy path for implementors.</p>
<h2 id="next-steps"><strong>Next Steps</strong></h2>
<p>Discussions for these approaches are happening in the <a href="https://modelcontextprotocol.io/community/sep-guidelines">Specification Enhancement Proposals</a> (SEP):</p>
<ul>
<li><a href="https://github.com/modelcontextprotocol/specification/discussions/991">SEP-991: Client ID Metadata Documents</a></li>
<li><a href="https://github.com/modelcontextprotocol/specification/discussions/1032">SEP-1032: Software Statements with DCR</a></li>
</ul>
<p>Get involved: Join the conversation in <a href="https://discord.gg/6CSzBmMkjX">Discord</a> (the #auth-wg-client-registration channel) or comment on the SEPs directly.</p>
<p>A big thank to the following folks for help with this blog post: Den Delimarsky, Aaron Parecki, Geoff Goodman, Andrew Block, Pieter Kasselman, Abhishek Hingnikar, and Bobby Tiernay.</p>
]]></content:encoded></item></channel></rss>