<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: Auth0</title>
    <description>The latest articles on Forem by Auth0 (@auth0).</description>
    <link>https://forem.com/auth0</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Forganization%2Fprofile_image%2F634%2Fc6bfc78f-136d-456b-96dc-bcc4be1c88f9.jpg</url>
      <title>Forem: Auth0</title>
      <link>https://forem.com/auth0</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/auth0"/>
    <language>en</language>
    <item>
      <title>Auth0 MCP Server Extension for Gemini CLI</title>
      <dc:creator>Jessica Temporal</dc:creator>
      <pubDate>Fri, 27 Mar 2026 11:00:00 +0000</pubDate>
      <link>https://forem.com/auth0/auth0-mcp-server-extension-for-gemini-cli-405m</link>
      <guid>https://forem.com/auth0/auth0-mcp-server-extension-for-gemini-cli-405m</guid>
      <description>&lt;p&gt;The Auth0 MCP Server is now listed on the official Gemini CLI extensions page. This means the Auth0 MCP Server is now directly installable through Gemini CLI with one command, allowing you to authenticate to Auth0 directly from your Gemini CLI session and load tenant information automatically.&lt;/p&gt;

&lt;p&gt;  &lt;iframe src="https://www.youtube.com/embed/iiSuv8BnPC0"&gt;
  &lt;/iframe&gt;
 &lt;/p&gt;
&lt;h2&gt;
  
  
  What the Auth0 MCP Server Extension Provides
&lt;/h2&gt;

&lt;p&gt;The extension packages the Auth0 MCP Server for Gemini CLI and adds three integration layers:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Discoverability&lt;/strong&gt;: Listed on &lt;a href="https://geminicli.com/extensions" rel="noopener noreferrer"&gt;geminicli.com/extensions&lt;/a&gt;, searchable by name, installable without manual configuration.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Authentication Commands&lt;/strong&gt;: Built-in slash commands for Auth0 tenant management:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;/auth0:init&lt;/code&gt; - Device authorization flow with tenant selection&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;/auth0:logout&lt;/code&gt; - Session termination&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;/auth0:session&lt;/code&gt; - Current authentication status&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Context Injection&lt;/strong&gt;: After authentication, Gemini gains your tenant information so the AI can query applications, APIs, connections, actions, and logs without requiring manual tenant specification in every prompt.&lt;/p&gt;
&lt;h2&gt;
  
  
  Installation and Setup
&lt;/h2&gt;

&lt;p&gt;Install the extension:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;gemini extensions &lt;span class="nb"&gt;install &lt;/span&gt;https://github.com/auth0/auth0-mcp-server
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once the command successfully finishes you should see a message stating &lt;code&gt;Extension “Auth0” installed successfully&lt;/code&gt;:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffzi13ha0j201bszply11.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffzi13ha0j201bszply11.png" alt="Terminal showing the installation of the Auth0 MCP server as a Gemini CLI extension"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Initialize the Auth0 MCP Server:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;/auth0:init
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Make sure to allow the command to run when prompted. The server will run automatically.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fr7q3o4dmnm02utqeu4ji.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fr7q3o4dmnm02utqeu4ji.png" alt="Gemini CLI terminal showing /auth0:init command with permission prompt to allow command execution"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You'll authenticate via &lt;a href="https://auth0.com/docs/get-started/authentication-and-authorization-flow/device-authorization-flow" rel="noopener noreferrer"&gt;device code flow&lt;/a&gt; to select your tenant:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frnik2h6j061y0y9brksu.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frnik2h6j061y0y9brksu.png" alt="Auth0 device authorization screen displaying device code and instructions to complete authentication"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And confirm the permissions:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0wx8dzfzrmbnntzeaj7b.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0wx8dzfzrmbnntzeaj7b.png" alt="Auth0 authorization screen showing requested permissions for Auth0 MCP Server extension"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Once authenticated, you should see a message within Gemini saying the Auth0 MCP Server is configured and to restart Gemini CLI to see the changes:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F10jdvkxmdmkufvebcm26.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F10jdvkxmdmkufvebcm26.png" alt="Gemini CLI terminal displaying successful Auth0 MCP Server initialization with tenant connection confirmed"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Refresh the MCP server list with the following command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;/mcp refresh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Gemini now has Auth0 context. Ask: "show me my applications" and the AI will receive the structured information about your applications, which Gemini CLI will display as a structured tool call result:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwrab4gmufeqlx8rtwf8m.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwrab4gmufeqlx8rtwf8m.png" alt="Gemini CLI tool call output showing structured JSON data of Auth0 applications from the MCP server"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And since Gemini now understands your tenant structure, existing configurations, and naming conventions, it can also show you the same information in a more readable format:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7n6pl8wgu6ipiazokouq.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7n6pl8wgu6ipiazokouq.png" alt="Gemini CLI displaying formatted, human-readable list of Auth0 applications with names, types, and client IDs"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Why This Matters
&lt;/h2&gt;

&lt;p&gt;Before this extension, using the Auth0 MCP Server with Gemini CLI required manual server configuration, environment variable setup, and custom initialization scripts. The extension collapses that into a single install command and three slash commands.&lt;/p&gt;

&lt;p&gt;More importantly: context persistence. Once authenticated, every Gemini session knows your Auth0 environment. You're not re-explaining your tenant structure or copy-pasting app IDs. The AI assistant operates with the same tenant awareness you have.&lt;/p&gt;

&lt;p&gt;This is the same Auth0 MCP Server that powers VS Code integrations, now packaged for Gemini CLI's extension model. Same capabilities, different CLI.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's Next
&lt;/h2&gt;

&lt;p&gt;The Auth0 MCP Server supports tenant management, application configuration, API setup, and log analysis. For implementation details and the full MCP Server feature set, &lt;a href="https://github.com/auth0/auth0-mcp-server?tab=readme-ov-file#%EF%B8%8F-supported-tools" rel="noopener noreferrer"&gt;see the list on GitHub here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The extension is available now at &lt;a href="https://geminicli.com/extensions" rel="noopener noreferrer"&gt;geminicli.com/extensions&lt;/a&gt;. Install, authenticate, and start managing Auth0 tenants through natural language.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>mcp</category>
      <category>tutorial</category>
      <category>tooling</category>
    </item>
    <item>
      <title>The Future of Coding is Communication, Not Just Code</title>
      <dc:creator>Carla Urrea Stabile</dc:creator>
      <pubDate>Thu, 26 Mar 2026 14:31:00 +0000</pubDate>
      <link>https://forem.com/auth0/the-future-of-coding-is-communication-not-just-code-328p</link>
      <guid>https://forem.com/auth0/the-future-of-coding-is-communication-not-just-code-328p</guid>
      <description>&lt;p&gt;We recently had a great conversation on the Making Software podcast with Bobby Tierney. Bobby is a Principal Architect at Okta and Auth0 who focuses on agentic security, AI governance, and the Model Context Protocol (MCP).&lt;br&gt;
I have been feeling some "AI FOMO" lately because the industry is moving so quickly. Talking to Bobby helped me bridge the gap between the "vibes" of modern AI coding and the security standards we need as professional engineers.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Vibe Coding vs. Mission Critical Production:&lt;/strong&gt; Bobby shared his perspective on where "vibe coding" fits into the software development life cycle and when it becomes a risky proposition for an enterprise. He explained why prototypes are a great way to explore a solution space even if you eventually throw them away.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Managing "YOLO Mode" with Sandboxing:&lt;/strong&gt; We discussed the "YOLO mode" found in many AI tools and how engineers can use progressive security configurations to keep things under control. 
-** The Shift to Spec-Driven Development:** Bobby talked about moving away from probabilistic guesswork and toward "spec coding". He explained how to use tools like "skills" or "slash commands" to align an AI with your team's specific DNA and ways of working.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The "Confused Deputy" Problem:&lt;/strong&gt; We touched on the security risks of AI agents and why you shouldn't just give them machine credentials to act on a user's behalf. Bobby highlighted the importance of finding the right intersection between what a user is allowed to do and what the AI is permitted to do.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The Changing Role of the Engineer:&lt;/strong&gt; We explored how AI is shrinking the development life cycle and why communication might be the most valuable skill for a developer in the future. Bobby also shared how designers and PMs are starting to use these tools to contribute directly to codebases.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Final Thoughts
&lt;/h2&gt;

&lt;p&gt;Bobby reminded me that while tools are changing, the core of engineering is still about requirements and making good decisions. &lt;/p&gt;

&lt;p&gt;The age of AI is not about replacing engineers. It is about helping them. By being good communicators, teachers, and security-minded builders, we can use these amazing tools to make better, safer software.&lt;/p&gt;

&lt;p&gt;What is your take on YOLO mode coding? Let me know in the comments! 🌱&lt;br&gt;
If you want to hear more, check out the full episode: &lt;a href="https://listen.casted.us/public/49/Making-Software-2b1cff7b" rel="noopener noreferrer"&gt;https://listen.casted.us/public/49/Making-Software-2b1cff7b&lt;/a&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>mcp</category>
      <category>security</category>
      <category>vibecoding</category>
    </item>
    <item>
      <title>Common FAPI Misconceptions</title>
      <dc:creator>Andrea Chiarelli</dc:creator>
      <pubDate>Fri, 20 Mar 2026 09:00:30 +0000</pubDate>
      <link>https://forem.com/auth0/common-fapi-misconceptions-121k</link>
      <guid>https://forem.com/auth0/common-fapi-misconceptions-121k</guid>
      <description>&lt;p&gt;For some time now, I've been interested in FAPI from both an Identity practitioner's and a developer's perspective. I've written a few posts on this topic on &lt;a href="https://auth0.com/blog" rel="noopener noreferrer"&gt;the Auth0 blog&lt;/a&gt; and created &lt;a href="https://auth0.com/resources/whitepapers/developers-guide-to-FAPI" rel="noopener noreferrer"&gt;a guide to FAPI&lt;/a&gt; with the support of colleagues who are much more experienced than I am. Surfing the web and talking to developers, however, I couldn't help but notice some misunderstandings about certain aspects of FAPI.&lt;br&gt;&lt;br&gt;
In this article, I'll summarize the most common and recurring ones.&lt;/p&gt;

&lt;h2&gt;
  
  
  Misconception 1: FAPI Is a New Protocol
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;FAPI is a security profile based on OAuth 2.1&lt;/strong&gt;, it is &lt;strong&gt;not a new protocol&lt;/strong&gt;, intended as an alternative to established standards like OAuth 2.0, SAML, or OpenID Connect (OIDC).&lt;/p&gt;

&lt;p&gt;It acts as a prescriptive blueprint that defines exactly which OAuth 2.0 and OIDC extensions must be used and how they must be configured. While the core OAuth 2.0 specification (&lt;a href="https://datatracker.ietf.org/doc/html/rfc6749" rel="noopener noreferrer"&gt;RFC 6749&lt;/a&gt;) is a flexible framework that provides a "toolbox" of flows and leaves security decisions to the implementer, FAPI removes this "dangerous flexibility" to ensure a secure-by-default posture.&lt;/p&gt;

&lt;h2&gt;
  
  
  Misconception 2: FAPI Is for Banks and Financial Organizations
&lt;/h2&gt;

&lt;p&gt;The "F" in FAPI originally stood for "Financial," reflecting its initial goal to protect banking applications. However, the scope has expanded significantly. Today, FAPI is a general-purpose high-security profile for any industry handling sensitive, high-risk data. Major applications include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;E-Health:&lt;/strong&gt; Protecting Patient Health Information (PHI) in standards like &lt;a href="https://hl7.org/fhir/" rel="noopener noreferrer"&gt;HL7 FHIR&lt;/a&gt;.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;E-Signing:&lt;/strong&gt; Securing the underlying API calls for legally binding digital signatures.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Government Services:&lt;/strong&gt; Managing Personally Identifiable Information (PII) and government services.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Misconception 3: FAPI 2.0 Is an Incremental Update of FAPI 1.0
&lt;/h2&gt;

&lt;p&gt;Some people mistakenly believe FAPI 2.0 is a minor version bump that maintains backward compatibility. This is incorrect. FAPI 2.0 is a complete redesign based on lessons learned from FAPI 1.0, specifically aimed at simplifying the developer experience.&lt;/p&gt;

&lt;p&gt;A key change is the removal of the &lt;a href="https://auth0.com/docs/get-started/authentication-and-authorization-flow/hybrid-flow" rel="noopener noreferrer"&gt;OpenID Connect Hybrid Flow&lt;/a&gt; used in FAPI 1.0 "Advanced," which required complex front-channel validations of ID Tokens. FAPI 2.0 replaces this with a hardened &lt;a href="https://auth0.com/docs/get-started/authentication-and-authorization-flow/authorization-code-flow" rel="noopener noreferrer"&gt;Authorization Code Flow&lt;/a&gt; where all tokens are delivered via the back-channel.&lt;/p&gt;

&lt;h2&gt;
  
  
  Misconception 4: OAuth 2.1 Makes FAPI Redundant
&lt;/h2&gt;

&lt;p&gt;As OAuth 2.1 incorporates modern best practices like &lt;a href="https://datatracker.ietf.org/doc/html/rfc7636" rel="noopener noreferrer"&gt;PKCE&lt;/a&gt; and the removal of the &lt;a href="https://auth0.com/docs/get-started/authentication-and-authorization-flow/implicit-flow-with-form-post" rel="noopener noreferrer"&gt;Implicit Flow&lt;/a&gt;, some wonder if FAPI 2.0 remains necessary. However, the difference between the two is significant.&lt;/p&gt;

&lt;p&gt;OAuth 2.1 is a "Best Current Practice" consolidation for the general community, while FAPI 2.0 is a "high-security profile" that mandates features OAuth 2.1 only recommends. Crucially, FAPI 2.0 is built on a &lt;a href="https://openid.net/specs/fapi-attacker-model-2_0-final.html" rel="noopener noreferrer"&gt;Formal Attacker Model&lt;/a&gt;, providing proof of security against specific threats that OAuth 2.1 does not formally address.&lt;/p&gt;

&lt;h2&gt;
  
  
  Misconception 5: You Can Use Any Type of Client with FAPI
&lt;/h2&gt;

&lt;p&gt;Standard OAuth 2.0 allows for both public (e.g., mobile, browser-based) and confidential (server-side) clients. However, while FAPI 1.0 Baseline specification allows for public clients, &lt;strong&gt;FAPI 1.0 Advanced and FAPI 2.0 explicitly excludes support for them&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;FAPI 2.0 focuses on confidential clients&lt;/strong&gt; because they are capable of protecting a private key. This is the cornerstone of FAPI’s security model; a client cannot perform asymmetric authentication or prove possession of a sender-constrained token if it cannot keep its private key secure.&lt;/p&gt;

&lt;h2&gt;
  
  
  Misconception 6: You Can’t Use Public Clients with FAPI
&lt;/h2&gt;

&lt;p&gt;As a consequence of the previous point, you may conclude that you can’t use public clients at all with FAPI. So, you can’t have mobile applications or SPAs in a FAPI-based system.&lt;/p&gt;

&lt;p&gt;Actually, this is not entirely true. You can still have public clients in a FAPI context provided that they don’t manage ID and access tokens. For example, you can have a SPA if you are using an architectural pattern like the &lt;a href="https://auth0.com/blog/the-backend-for-frontend-pattern-bff/" rel="noopener noreferrer"&gt;Backend for Frontend pattern&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Misconception 7: Pushed Authorization Requests Support Simply Shortens URLs
&lt;/h2&gt;

&lt;p&gt;Developers often view &lt;a href="https://auth0.com/blog/what-are-oauth-push-authorization-requests-par/" rel="noopener noreferrer"&gt;Pushed Authorization Requests&lt;/a&gt; (PAR) as merely an optional optimization for long URLs. In reality, FAPI 2.0 makes PAR mandatory because it moves the entire authorization request from the &lt;em&gt;risky&lt;/em&gt; environment of the browser to the secure back-channel.&lt;/p&gt;

&lt;p&gt;Standard GET requests expose sensitive data (scopes, identifiers) to browser history, server logs, and Referer headers. PAR solves this by having the client POST parameters directly to the server, receiving an opaque request_uri in return. This ensures:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Confidentiality:&lt;/strong&gt; Sensitive parameters are never in the URL.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Integrity:&lt;/strong&gt; The server authenticates the request before the user is ever involved.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Reliability:&lt;/strong&gt; It bypasses URL length limits that often break complex authorization requests.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Wrapping Up
&lt;/h2&gt;

&lt;p&gt;The journey through the FAPI landscape often involves navigating a complex mix of technical details and outdated assumptions. As this article has shown, FAPI is far more than an obscure security framework for banks; it is a critical, mature security profile that strips away the risky flexibility of standard OAuth 2.0 to offer a secure foundation for any system handling high-value data.&lt;/p&gt;

&lt;p&gt;From understanding that FAPI is a profile, not a new protocol, to recognizing the fundamental changes in FAPI 2.0 and its mandated use of features like Pushed Authorization Requests (PAR), a clear picture emerges. FAPI’s strict reliance on confidential clients and back-channel communications is a pragmatic response to known threat models, ensuring integrity and confidentiality where it matters most.&lt;/p&gt;

&lt;p&gt;Embracing FAPI is not about adding unnecessary complexity. It is about adopting a proven security posture necessary for protecting assets in e-Health, e-Signing, government services, and beyond. By moving past these common misconceptions, organizations can leverage FAPI to build truly robust and compliant high-security applications.&lt;/p&gt;

&lt;p&gt;Here is an infographic that summarizes the misconceptions discussed in this article. Keep it handy:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdms4lk1ygtas81a32fkw.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdms4lk1ygtas81a32fkw.jpg" alt="Infographic about the seven common misconceptions on FAPI" width="800" height="800"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>fapi</category>
      <category>oauth</category>
      <category>identity</category>
      <category>security</category>
    </item>
    <item>
      <title>Demystifying OAuth Security: State vs. Nonce vs. PKCE</title>
      <dc:creator>Jessica Temporal</dc:creator>
      <pubDate>Fri, 13 Mar 2026 16:17:44 +0000</pubDate>
      <link>https://forem.com/auth0/demystifying-oauth-security-state-vs-nonce-vs-pkce-2eo2</link>
      <guid>https://forem.com/auth0/demystifying-oauth-security-state-vs-nonce-vs-pkce-2eo2</guid>
      <description>&lt;p&gt;Confused by the random strings in your OAuth URLs? You aren't alone. Many developers think &lt;code&gt;state&lt;/code&gt;, &lt;code&gt;nonce&lt;/code&gt;, and &lt;code&gt;code_challenge&lt;/code&gt; (PKCE) are redundant—but skipping just one could leave your users' accounts wide open to attackers like "Eve." In this video, I'll break down why these three parameters are like three different locks on three different doors. We’ll look at real-world attack scenarios and show you exactly how each one keeps your app secure.&lt;/p&gt;

&lt;h2&gt;
  
  
  💡  What You’ll Learn:
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;The State Parameter:&lt;/strong&gt; How to prevent Cross-Site Request Forgery ($CSRF$) attacks.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;The Nonce Parameter:&lt;/strong&gt; Why ID tokens need protection against Replay attacks.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;PKCE (Proof Key for Code Exchange):&lt;/strong&gt; Protecting mobile and single-page apps from Authorization Code Injection.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Implementation Strategy:&lt;/strong&gt; Why you should use all three instead of picking just one.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  🔗 Links:
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://auth0.com/blog/demystifying-oauth-security-state-vs-nonce-vs-pkce" rel="noopener noreferrer"&gt;Read the full blog post by Andrea Chiarelli&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://auth0.com/docs/get-started/authentication-and-authorization-flow/authorization-code-flow-with-pkce" rel="noopener noreferrer"&gt;Auth0 Docs - Why PKCE?&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://oauth.net/2/security-topics/" rel="noopener noreferrer"&gt;OAuth 2.0 Security Best Practices&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;If you enjoy this content and want to learn more about identity, security, and access management, subscribe to our channel! &lt;/p&gt;

&lt;p&gt;Have a topic you'd like to see covered? Let us know if the comments below 👀 &lt;/p&gt;

</description>
      <category>oauth</category>
      <category>security</category>
      <category>software</category>
    </item>
    <item>
      <title>Secure a C# MCP Server with Auth0</title>
      <dc:creator>Andrea Chiarelli</dc:creator>
      <pubDate>Fri, 13 Mar 2026 09:59:59 +0000</pubDate>
      <link>https://forem.com/auth0/secure-a-c-mcp-server-with-auth0-4p0n</link>
      <guid>https://forem.com/auth0/secure-a-c-mcp-server-with-auth0-4p0n</guid>
      <description>&lt;p&gt;As the &lt;a href="https://modelcontextprotocol.io/" rel="noopener noreferrer"&gt;Model Context Protocol (MCP)&lt;/a&gt; gains traction, the transition from "localhost" experimentation to enterprise integration introduces a critical challenge: security. For developers building sophisticated integrations, treating every LLM request as an "admin" action is a significant risk.&lt;/p&gt;

&lt;p&gt;Luckily, the MCP specification supports authorization based on OAuth 2.1. So, basically, to protect your MCP server, you have to treat it as a resource server with some extra challenges in case you want to share it publicly to a wide audience.&lt;/p&gt;

&lt;p&gt;The .NET ecosystem can leverage the &lt;a href="https://csharp.sdk.modelcontextprotocol.io/" rel="noopener noreferrer"&gt;C# SDK for MCP&lt;/a&gt; to easily expose tools and resources to an AI-powered application. The SDK reached maturity recently &lt;a href="https://devblogs.microsoft.com/dotnet/release-v10-of-the-official-mcp-csharp-sdk/" rel="noopener noreferrer"&gt;supporting all the features defined by the specification&lt;/a&gt;, including security.&lt;/p&gt;

&lt;p&gt;This article explores a practical implementation of a secured MCP server. You will walk through a sample project and learn how to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Implement three distinct tools: one available for public consumption and two gated behind specific permission sets.
&lt;/li&gt;
&lt;li&gt;Make the protected MCP server available to an MCP client, such as VSCode, leveraging Dynamic Client Registration (DCR) supported by Auth0.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Why Protect an MCP Server?
&lt;/h2&gt;

&lt;p&gt;In a standard local setup, the MCP server and the client often share the same security boundary. However, as soon as an MCP server is deployed as a shared service or connected to a multi-user LLM platform, that boundary shifts. Protecting the server becomes relevant for at least two reasons:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Preventing prompt injection escalation&lt;/strong&gt;. If a tool allows an LLM to interact with internal databases or APIs, a malicious prompt could attempt to trick the model into executing commands it shouldn't. By implementing strict authorization checks at the server level, you provide a defense in depth strategy. Even if the LLM is convinced to call a restricted tool, the MCP server will reject the request because the underlying user context lacks the necessary permissions.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Resource isolation and multi-tenancy&lt;/strong&gt;. Not every user interacting with an AI agent should have the same capabilities. For example, a junior developer might have access to a &lt;em&gt;ReadDocumentation&lt;/em&gt; tool, but only a lead engineer should be able to trigger a &lt;em&gt;DeployProduction&lt;/em&gt; tool. Without protection, the MCP server treats all requests as equal, which is unacceptable in any regulated or professional environment.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These reasons are what OAuth 2.1 support in the MCP specification aims to prevent. We will use it to protect a sample MCP server built with C# by integrating with Auth0.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;To learn how Auth0 helps you secure MCP clients and servers, check out &lt;a href="https://auth0.com/ai/docs/mcp/intro/overview" rel="noopener noreferrer"&gt;Auth for MCP&lt;/a&gt;. For a detailed explanation of authorization support in MCP, read the &lt;a href="https://modelcontextprotocol.io/specification/2025-11-25/basic/authorization" rel="noopener noreferrer"&gt;Authorization specification&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Build Your MCP Server in C
&lt;/h2&gt;

&lt;p&gt;Let’s begin our exploration by implementing a very simple MCP server that we will later extend and secure using Auth0.&lt;/p&gt;

&lt;h3&gt;
  
  
  Prerequisites
&lt;/h3&gt;

&lt;p&gt;The MCP server project we are going to implement requires the following:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://dotnet.microsoft.com/download/dotnet/10.0" rel="noopener noreferrer"&gt;.NET SDK 10&lt;/a&gt; or later,
&lt;/li&gt;
&lt;li&gt;The &lt;a href="https://csharp.sdk.modelcontextprotocol.io/" rel="noopener noreferrer"&gt;C# SDK for MCP&lt;/a&gt; package,
&lt;/li&gt;
&lt;li&gt;An Auth0 account. If you don’t have it, &lt;a href="https://a0.to/blog_signup" rel="noopener noreferrer"&gt;sign up for free here&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Create the MCP Server
&lt;/h3&gt;

&lt;p&gt;To create a simple MCP server, you can use the MCP server template project provided by Microsoft. Currently, the template is in preview, so you won’t find it in the builtin template set. You can download and install the package by running the following command in a terminal window:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;dotnet new &lt;span class="nb"&gt;install &lt;/span&gt;Microsoft.McpServer.ProjectTemplates
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once you have downloaded the project template, you can create your MCP server project by running a command like the following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;dotnet new mcpserver &lt;span class="nt"&gt;-n&lt;/span&gt; AspNetCoreMcpServer &lt;span class="nt"&gt;-t&lt;/span&gt; remote
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This command creates a new MCP server project named &lt;code&gt;AspNetCoreMcpServer&lt;/code&gt; in a folder with the same name. The option &lt;code&gt;-t remote&lt;/code&gt; specifies to create an MCP server based on HTTP transport, which leads to actually creating an ASP.NET Core application.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;MCP servers can use &lt;a href="https://modelcontextprotocol.io/specification/2025-11-25/basic/transports" rel="noopener noreferrer"&gt;stdio or HTTP as transport protocols&lt;/a&gt;. OAuth protection for MCP servers is only designed for HTTP-based MCP servers. See &lt;a href="https://modelcontextprotocol.io/docs/tutorials/security/authorization#when-should-you-use-authorization" rel="noopener noreferrer"&gt;When Should You Use Authorization?&lt;/a&gt; for more information.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Explore the project
&lt;/h3&gt;

&lt;p&gt;Let’s take a quick look at the project to understand its implementation.&lt;br&gt;&lt;br&gt;
Go to the project’s folder and open the &lt;code&gt;Program.cs&lt;/code&gt; file. You should see the the following code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="c1"&gt;//Program.cs&lt;/span&gt;

&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;builder&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;WebApplication&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;CreateBuilder&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// Add the MCP services: the transport to use (http) and the tools to register.&lt;/span&gt;
&lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Services&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddMcpServer&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WithHttpTransport&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WithTools&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;RandomNumberTools&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;();&lt;/span&gt;

&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;app&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Build&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;MapMcp&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Run&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is an ordinary .NET application that uses the MCP server service (&lt;code&gt;AddMcpServer&lt;/code&gt;). This service uses the HTTP transport (&lt;code&gt;WithHttpTransport&lt;/code&gt;) and exposes the tools implemented by the class &lt;code&gt;RandomNumberTools&lt;/code&gt; (&lt;code&gt;WithTools&amp;lt;RandomNumberTools&amp;gt;&lt;/code&gt;). The invocation of the &lt;code&gt;MapMcp()&lt;/code&gt; method initializes the MCP server.&lt;/p&gt;

&lt;p&gt;The C# SDK for MCP library takes charge of all the complexity of implementing the MCP protocol. You can stay focused on the tool implementation, which is pretty straightforward as well.&lt;br&gt;&lt;br&gt;
Open the &lt;code&gt;RandomNumberTools.cs&lt;/code&gt; file in the &lt;code&gt;Tools&lt;/code&gt; folder and take a look at the code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="c1"&gt;//Tools/RandomNumberTools.cs&lt;/span&gt;

&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;System.ComponentModel&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;ModelContextProtocol.Server&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;internal&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;RandomNumberTools&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;McpServerTool&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;Description&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Generates a random number between the specified minimum and maximum values."&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="nf"&gt;GetRandomNumber&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;Description&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Minimum value (inclusive)"&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;min&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;Description&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Maximum value (exclusive)"&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;max&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;100&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;Random&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Shared&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Next&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;min&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;max&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You see a very simple class implementing the method &lt;code&gt;GetRandomNumber()&lt;/code&gt;, which returns a random number within a range. The attribute &lt;code&gt;McpServerTool&lt;/code&gt; marks the &lt;code&gt;GetRandomNumber()&lt;/code&gt; method as an MCP tool. The &lt;code&gt;Description&lt;/code&gt; attributes describe what the tool does and what its parameters mean, very important to let the LLM understand when and how to use it.  In fact, clear descriptions of the tool's functionality help the LLM select the appropriate tool for a given task.&lt;/p&gt;

&lt;p&gt;That’s all! Your MCP server is ready to run.&lt;/p&gt;

&lt;h3&gt;
  
  
  Test your MCP server
&lt;/h3&gt;

&lt;p&gt;Since the MCP server we implemented is nothing more than an ASP.NET Core application, we can test it in several ways: you can use curl or the &lt;a href="https://learn.microsoft.com/en-us/aspnet/core/test/http-files" rel="noopener noreferrer"&gt;.http file&lt;/a&gt; automatically generated with the project. Or you can use the &lt;a href="https://modelcontextprotocol.io/docs/tools/inspector" rel="noopener noreferrer"&gt;MCP Inspector&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;We'll integrate our MCP server directly into an MCP client like VSCode. This will come in handy later when we implement some advanced features.&lt;/p&gt;

&lt;p&gt;You can add your MCP server to VSCode by creating a &lt;code&gt;.vscode&lt;/code&gt; folder in your project’s root folder, and adding an &lt;code&gt;mcp.json&lt;/code&gt; file with the following JSON content:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="err"&gt;//.vscode/mcp.json&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"servers"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"AspNetCoreMcpServer"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"url"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"http://localhost:PORT"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"http"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="err"&gt;inputs&lt;/span&gt;&lt;span class="s2"&gt;": []
}

&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Make sure to replace the PORT placeholder with the port your MCP server listens to.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Look at &lt;a href="https://code.visualstudio.com/docs/copilot/customization/mcp-servers#_add-an-mcp-server" rel="noopener noreferrer"&gt;alternative ways to add your MCP server to VSCode&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Once you have added your MCP server to VSCode, set its chat window to agent mode and ask to get a random number. You should be prompted to authorize the use of the tool as shown in the following screenshot:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frvyop35awnfo6ejzu73c.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frvyop35awnfo6ejzu73c.png" alt="Authorization request for the random numbers tool in Copilot chat in VSCode" width="283" height="322"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;After you authorize it, you will get a random number:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fssslankyoogecbmzz5gu.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fssslankyoogecbmzz5gu.png" alt="Response of the random numbers tool in Copilot chat" width="283" height="187"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Cool! You have a new working MCP server in your VSCode instance!&lt;/p&gt;

&lt;h2&gt;
  
  
  Protect Your MCP Server
&lt;/h2&gt;

&lt;p&gt;Now let's move on to the real focus of this article: protecting your MCP server from unauthorized access.&lt;/p&gt;

&lt;h3&gt;
  
  
  Define the tools to protect
&lt;/h3&gt;

&lt;p&gt;Currently, anyone installing your MCP server can use its only tool, &lt;code&gt;get_random_number&lt;/code&gt;. This tool is publicly accessible because there is no restriction on it. You will leave it so, but you will also add two new tools that will require some form of authorization.&lt;/p&gt;

&lt;p&gt;Specifically, you will add a tool that gets the current state of a hypothetical system and another one that sets the state of this system. The system is fictitious, of course, but you can think of it as a kind of processor that can be found in different states.&lt;/p&gt;

&lt;p&gt;To implement these tools, add a new file named &lt;code&gt;SystemTools.cs&lt;/code&gt; to the &lt;code&gt;Tools&lt;/code&gt; folder with the following code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="c1"&gt;//Tools/SystemTools.cs&lt;/span&gt;

&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;System.ComponentModel&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;ModelContextProtocol.Server&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;internal&lt;/span&gt; &lt;span class="k"&gt;enum&lt;/span&gt; &lt;span class="n"&gt;SystemState&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;Ready&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;Waiting&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;Running&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;Stopped&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;internal&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;SystemTools&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="n"&gt;SystemState&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="n"&gt;AllStates&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Enum&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GetValues&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;SystemState&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;();&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="n"&gt;SystemState&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="n"&gt;_currentState&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;McpServerTool&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;Description&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Returns the current state of the fictitious system. Possible states are: Ready, Waiting, Running, Stopped."&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="nf"&gt;GetSystemState&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;_currentState&lt;/span&gt; &lt;span class="p"&gt;??=&lt;/span&gt; &lt;span class="n"&gt;AllStates&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Random&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Shared&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Next&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;AllStates&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Length&lt;/span&gt;&lt;span class="p"&gt;)];&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;_currentState&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ToString&lt;/span&gt;&lt;span class="p"&gt;()!;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;McpServerTool&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;Description&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Sets the current state of the fictitious system. Valid states are: Ready, Waiting, Running, Stopped."&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="nf"&gt;SetSystemState&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;Description&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"The new state to set. Must be one of: Ready, Waiting, Running, Stopped."&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt; &lt;span class="n"&gt;SystemState&lt;/span&gt; &lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;_currentState&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s"&gt;$"System state set to '&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;_currentState&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt;'."&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You see that the tool &lt;code&gt;GetSystemState()&lt;/code&gt; picks a random state the first time it is called and stores it into a private static variable, &lt;code&gt;_currentState&lt;/code&gt;. Any subsequent call to the tool will return the value stored in that variable.&lt;/p&gt;

&lt;p&gt;The tool &lt;code&gt;SetSystemState()&lt;/code&gt; changes the value of the state stored in the &lt;code&gt;_currentState&lt;/code&gt; variable.&lt;/p&gt;

&lt;p&gt;You will protect these two new tools by requiring that the user has specific permissions to use each of them.&lt;/p&gt;

&lt;p&gt;According to the &lt;a href="https://modelcontextprotocol.io/specification/2025-11-25/basic/authorization#roles" rel="noopener noreferrer"&gt;MCP specification&lt;/a&gt;, your MCP server is nothing but a resource server in the context of OAuth, and VSCode is just an OAuth client. So actually all you are going to implement is a well-known scenario: you will use Auth0 as the authorization server, which will authenticate the user and provide the access token to the MCP client (VSCode) in order to access the tools exposed by your MCP server. The access token will include the required permissions to access the protected tools.&lt;/p&gt;

&lt;p&gt;However, you may have a little concern now. You want to share your MCP server as a package that anyone can download and use in their VSCode instance. How can they register their VSCode instance with Auth0 as required for any OAuth client? Well, the MCP specification supports &lt;a href="https://modelcontextprotocol.io/specification/2025-11-25/basic/authorization#client-registration-approaches" rel="noopener noreferrer"&gt;a few ways to do this&lt;/a&gt;. You will use &lt;a href="https://datatracker.ietf.org/doc/html/rfc7591" rel="noopener noreferrer"&gt;Dynamic Client Registration (DCR)&lt;/a&gt;, which allows an MCP client (VSCode) to self-register with the authorization server (Auth0). Be aware that DCR raises security concerns, as we will mention later.&lt;/p&gt;

&lt;h3&gt;
  
  
  Configure your Auth0 tenant
&lt;/h3&gt;

&lt;p&gt;First of all, you need to configure Auth0 at the tenant level. In fact, you need to enable &lt;a href="https://auth0.com/docs/get-started/applications/dynamic-client-registration" rel="noopener noreferrer"&gt;Dynamic Client Registration&lt;/a&gt; to allow MCP clients to register. You also need to enable &lt;a href="https://auth0.com/ai/docs/mcp/guides/resource-param-compatibility-profile" rel="noopener noreferrer"&gt;Resource Parameter Compatibility Profile&lt;/a&gt; as required by the &lt;a href="https://modelcontextprotocol.io/specification/2025-11-25/basic/authorization#resource-parameter-implementation" rel="noopener noreferrer"&gt;MCP specification&lt;/a&gt;.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Keep in mind that these are tenant level settings, which means they affect all the applications registered in that tenant.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;To enable those settings, go to &lt;a href="https://manage.auth0.com/#/tenant" rel="noopener noreferrer"&gt;&lt;em&gt;Settings&lt;/em&gt;&lt;/a&gt; on the left menu and then select the &lt;em&gt;Advanced&lt;/em&gt; tab. Scroll down to the &lt;em&gt;Settings&lt;/em&gt; section and enable the &lt;em&gt;Dynamic Client Registration (DCR)&lt;/em&gt; and &lt;em&gt;Resource Parameter Compatibility Profile&lt;/em&gt; toggles, as shown in the image below:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3tlpn79sjl2k25wdkbvs.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3tlpn79sjl2k25wdkbvs.png" alt="Dynamic Client Registration option on the Auth0 dashboard" width="800" height="540"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;As mentioned before, you need to enable Dynamic Client Registration to allow VSCode to self-register with Auth0 the first time a user interacts with your MCP server. This allows any client to register with Auth0 with &lt;strong&gt;potential risks&lt;/strong&gt; such as resource exhaustion or unauthorized access attempts. Read &lt;a href="https://auth0.com/ai/docs/mcp/guides/registering-your-mcp-client-application#dynamic-client-registration-dcr" rel="noopener noreferrer"&gt;Register your MCP Client Application&lt;/a&gt; to learn more about the dangers of an open DCR endpoint and possible solutions.&lt;/p&gt;

&lt;p&gt;Beyond the static registration of an MCP client, an alternative approach is based on &lt;a href="https://auth0.com/blog/cimd-vs-dcr-mcp-registration/" rel="noopener noreferrer"&gt;Client ID Metadata Document (CIMD)&lt;/a&gt;, a recent standard whose implementation is in progress.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;In addition to the previous tenant-level settings, you also need to &lt;a href="https://auth0.com/docs/authenticate/identity-providers/promote-connections-to-domain-level" rel="noopener noreferrer"&gt;promote the connections&lt;/a&gt; the MCP clients will use to domain level. To this purpose, go to the &lt;em&gt;Authentication&lt;/em&gt; menu item of your dashboard and select the connection to configure. For our current example, select the &lt;em&gt;Username-Password-Authentication&lt;/em&gt; connection under &lt;em&gt;Database&lt;/em&gt;, and scroll down until you see the setting &lt;em&gt;Promote Connection to Domain Level&lt;/em&gt;. Enable it as shown below:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frsdbdaugjqa1utxwqihb.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frsdbdaugjqa1utxwqihb.png" alt="Promote Connection to Domain Level setting in the Auth0 dashboard" width="800" height="344"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Great! You have prepared your Auth0 tenant for self-registrations of MCP clients.&lt;/p&gt;

&lt;h3&gt;
  
  
  Register your MCP Server
&lt;/h3&gt;

&lt;p&gt;Now you need to register your MCP server.&lt;/p&gt;

&lt;p&gt;As mentioned earlier, from the OAuth point of view, an MCP server is nothing but a resource server or API server. This means that you can register it with Auth0 &lt;a href="https://auth0.com/docs/get-started/auth0-overview/set-up-apis" rel="noopener noreferrer"&gt;as a standard API&lt;/a&gt;. The only requirement is to make sure to set your MCP server base URL as the audience. This is &lt;a href="https://datatracker.ietf.org/doc/html/rfc9728#section-3.3" rel="noopener noreferrer"&gt;a requirement of the Protected Resource Metadata specification&lt;/a&gt;. So, in your case, if your MCP server listens to &lt;code&gt;http://localhost:5678&lt;/code&gt;, use this URL as the audience.&lt;/p&gt;

&lt;p&gt;Once you have created your API on the Auth0 dashboard, scroll down in the &lt;em&gt;Settings&lt;/em&gt; tab to reach the &lt;em&gt;Access Token Settings&lt;/em&gt; section. Here, select the &lt;em&gt;RFC 9068&lt;/em&gt; format as shown in the following picture:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftzv41xbfc20z2lqn0lrb.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftzv41xbfc20z2lqn0lrb.png" alt="Enabling the RFC 9068 access token profile in the Auth0 dashboard" width="800" height="444"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Read the &lt;a href="https://auth0.com/docs/secure/tokens/access-tokens/access-token-profiles" rel="noopener noreferrer"&gt;documentation to learn more about access token profiles&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Then scroll down more to the &lt;em&gt;RBAC Settings&lt;/em&gt; section and enable both &lt;em&gt;RBAC&lt;/em&gt; and &lt;em&gt;Add Permissions in the Access Token&lt;/em&gt;:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F20yx4s6dcwhxv1n60hzu.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F20yx4s6dcwhxv1n60hzu.png" alt="Enabling RBAC and permissions in access tokens in the Auth0 dashboard" width="800" height="296"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;These settings will include the user’s permissions in the access token so your MCP server will be able to make authorization decisions to let users access the tools.&lt;/p&gt;

&lt;p&gt;Save the settings and go to the &lt;em&gt;Permissions&lt;/em&gt; tab. Here you should add the &lt;code&gt;tool:getsystemstate&lt;/code&gt; and &lt;code&gt;tool:setsystemstate&lt;/code&gt; permissions as shown below:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqqtssnxz7pcm7tn62mhb.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqqtssnxz7pcm7tn62mhb.png" alt="List of permissions associated with the API" width="800" height="206"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Follow the instructions in &lt;a href="https://auth0.com/docs/get-started/apis/add-api-permissions" rel="noopener noreferrer"&gt;this document to learn how to add permissions to an API&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Finally, go to the &lt;em&gt;Application Access&lt;/em&gt; tab and select the &lt;em&gt;Allow&lt;/em&gt; value for the application access policy for user access, as you can see in the picture below:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0vqw170c3ogrj9d5jjxl.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0vqw170c3ogrj9d5jjxl.png" alt="Enabling application access policy on the Auth0 dashboard" width="800" height="334"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This setting allows any self-registered MCP client to access your MCP server on behalf of the user. To learn more about this setting, read the &lt;a href="https://auth0.com/docs/get-started/apis/api-access-policies-for-applications" rel="noopener noreferrer"&gt;API Access Policy documentation&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Now your MCP server is configured on the Auth0 side.&lt;/p&gt;

&lt;h3&gt;
  
  
  Secure your MCP Server
&lt;/h3&gt;

&lt;p&gt;Let's complete the MCP server protection by modifying the current code.&lt;/p&gt;

&lt;p&gt;First, add an &lt;code&gt;appsettings.json&lt;/code&gt; file with the current content:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="err"&gt;//appsettings.json&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"Auth0"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"Domain"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"YOUR_DOMAIN"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"Audience"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"YOUR_MCP_SERVER_BASE_URL"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"McpServer"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"BaseUrl"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"YOUR_MCP_SERVER_BASE_URL"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Replace &lt;code&gt;YOUR_DOMAIN&lt;/code&gt; with your Auth0 tenant domain, and &lt;code&gt;YOUR_MCP_SERVER_BASE_URL&lt;/code&gt; with the actual URL of your MCP server. Remember that &lt;code&gt;YOUR_MCP_SERVER_BASE_URL&lt;/code&gt; &lt;a href="https://datatracker.ietf.org/doc/html/rfc9728#section-3.3" rel="noopener noreferrer"&gt;must also correspond to the audience you registered in Auth0&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Now you will add support for the access token validation and protection on the two MCP tools you added to the initial project.&lt;/p&gt;

&lt;p&gt;Install the middleware to manage JWT tokens with the following command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;dotnet package add Microsoft.AspNetCore.Authentication.JwtBearer
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Replace the current content of the &lt;code&gt;Program.cs&lt;/code&gt; file with the following code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="c1"&gt;//Program.cs&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;Microsoft.AspNetCore.Authentication.JwtBearer&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;Microsoft.IdentityModel.Tokens&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;ModelContextProtocol.AspNetCore.Authentication&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;builder&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;WebApplication&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;CreateBuilder&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;authority&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;$"https://&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Configuration&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"Auth0:Domain"&lt;/span&gt;&lt;span class="p"&gt;]}&lt;/span&gt;&lt;span class="s"&gt;/"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;audience&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Configuration&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"Auth0:Audience"&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;mcpServerBaseUrl&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Configuration&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"McpServer:BaseUrl"&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;

&lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddAuthentication&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;options&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DefaultChallengeScheme&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;McpAuthenticationDefaults&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AuthenticationScheme&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DefaultAuthenticateScheme&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;JwtBearerDefaults&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AuthenticationScheme&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddJwtBearer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;options&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Authority&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;authority&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;TokenValidationParameters&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;TokenValidationParameters&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;ValidateIssuer&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;ValidateAudience&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;ValidateLifetime&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;ValidateIssuerSigningKey&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;ValidAudience&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;audience&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;ValidIssuer&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;authority&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;

    &lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Events&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;JwtBearerEvents&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;OnTokenValidated&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;Console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteLine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Token validated successfully."&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CompletedTask&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="n"&gt;OnAuthenticationFailed&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;Console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteLine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;$"Authentication failed: &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Exception&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Message&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CompletedTask&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddMcp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;options&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ResourceMetadata&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;Resource&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;mcpServerBaseUrl&lt;/span&gt; &lt;span class="p"&gt;??&lt;/span&gt; &lt;span class="n"&gt;audience&lt;/span&gt; &lt;span class="p"&gt;??&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Empty&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;AuthorizationServers&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;authority&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="n"&gt;ScopesSupported&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"tool:getsystemstate"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"tool:setsystemstate"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddAuthorization&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Services&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddMcpServer&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WithHttpTransport&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddAuthorizationFilters&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WithTools&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;RandomNumberTools&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;();&lt;/span&gt;

&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;app&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Build&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;MapMcp&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;RequireAuthorization&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;mcpServerBaseUrl&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let’s break down this code to understand what it does.&lt;/p&gt;

&lt;p&gt;Basically, it adds support for authenticating client requests as in any ordinary ASP.NET Core Web API:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="c1"&gt;//Program.cs&lt;/span&gt;

&lt;span class="c1"&gt;//...existing code...&lt;/span&gt;

&lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddAuthentication&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;options&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DefaultChallengeScheme&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;McpAuthenticationDefaults&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AuthenticationScheme&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DefaultAuthenticateScheme&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;JwtBearerDefaults&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AuthenticationScheme&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddJwtBearer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;options&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Authority&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;authority&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;TokenValidationParameters&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;TokenValidationParameters&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;ValidateIssuer&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;ValidateAudience&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;ValidateLifetime&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;ValidateIssuerSigningKey&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;ValidAudience&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;audience&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;ValidIssuer&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;authority&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;

    &lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Events&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;JwtBearerEvents&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;OnTokenValidated&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;Console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteLine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Token validated successfully."&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CompletedTask&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="n"&gt;OnAuthenticationFailed&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;Console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteLine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;$"Authentication failed: &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Exception&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Message&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CompletedTask&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="c1"&gt;//...existing code...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This code sets up the access token validation logic through &lt;code&gt;AddJwtBearer()&lt;/code&gt;. The &lt;code&gt;OnTokenValidated&lt;/code&gt; and &lt;code&gt;OnAuthenticationFailed&lt;/code&gt; event managers do nothing special apart from showing their message to the console, but you can use them for any customization you may need.&lt;/p&gt;

&lt;p&gt;Just after &lt;code&gt;AddJwtBearer()&lt;/code&gt; invocation, you find &lt;code&gt;AddMcp()&lt;/code&gt;, as highlighted below:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="c1"&gt;//Program.cs&lt;/span&gt;

&lt;span class="c1"&gt;//...existing code...&lt;/span&gt;

&lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddAuthentication&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;options&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;//...existing code...&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddJwtBearer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;options&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;//...existing code...&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddMcp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;options&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ResourceMetadata&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;Resource&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;mcpServerBaseUrl&lt;/span&gt; &lt;span class="p"&gt;??&lt;/span&gt; &lt;span class="n"&gt;audience&lt;/span&gt; &lt;span class="p"&gt;??&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Empty&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;AuthorizationServers&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;authority&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="n"&gt;ScopesSupported&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"tool:getsystemstate"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"tool:setsystemstate"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="c1"&gt;//...existing code&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;AddMcp()&lt;/code&gt; method defines the &lt;a href="https://datatracker.ietf.org/doc/html/rfc9728#name-protected-resource-metadata" rel="noopener noreferrer"&gt;Protected Resource Metadata (PRM)&lt;/a&gt; document for this MCP server. This document provides useful information for the client about the capabilities of the MCP server and which authorization server to contact to access protected tools.&lt;/p&gt;

&lt;p&gt;This document will be dynamically exposed at the URL &lt;code&gt;YOUR_MCP_SERVER_BASE_URL/.well-known/oauth-protected-resource&lt;/code&gt;, where &lt;code&gt;YOUR_MCP_SERVER_BASE_URL&lt;/code&gt; is the base URL of your MCP server. The document will contain the following JSON:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"resource"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"YOUR_MCP_SERVER_BASE_URL"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"authorization_servers"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"https://YOUR_DOMAIN"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"bearer_methods_supported"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"header"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"scopes_supported"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"tool:getsystemstate"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"tool:setsystemstate"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Finally, enforce authorization with the code highlighted below:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="c1"&gt;//Program.cs&lt;/span&gt;

&lt;span class="c1"&gt;//...existing code...&lt;/span&gt;

&lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddAuthorization&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;  &lt;span class="c1"&gt;//👈 new code&lt;/span&gt;

&lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Services&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddMcpServer&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WithHttpTransport&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddAuthorizationFilters&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;  &lt;span class="c1"&gt;//👈 new code&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WithTools&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;RandomNumberTools&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;();&lt;/span&gt;

&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;app&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Build&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;MapMcp&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;RequireAuthorization&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;  &lt;span class="c1"&gt;//👈 changed code&lt;/span&gt;

&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;mcpServerBaseUrl&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;  &lt;span class="c1"&gt;//👈 changed code&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You added the authorization middleware service (&lt;code&gt;AddAuthorization()&lt;/code&gt;) and support for authorization filters (&lt;code&gt;AddAuthorizationFilters()&lt;/code&gt;). Also, you applied authorization to the MCP endpoints (&lt;code&gt;RequireAuthorization()&lt;/code&gt;) and forced the server to use the base URL defined in the &lt;code&gt;appsettings.json&lt;/code&gt; file. The last change is not strictly required, but it ensures that you have one source of truth for the MCP base URL.&lt;/p&gt;

&lt;h3&gt;
  
  
  Add authorization policy
&lt;/h3&gt;

&lt;p&gt;You added a generic support for authorization with &lt;code&gt;AddAuthorization()&lt;/code&gt;. This makes sure that only authenticated users access the protected tools. But our initial intent is to allow users to access each specific tool based on their permissions.&lt;/p&gt;

&lt;p&gt;You can do this by defining a couple of authorization policies based on the existence of specific permissions in the access token. To this purpose, replace the &lt;code&gt;AddAuthorization()&lt;/code&gt; invocation with the following code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="c1"&gt;//Program.cs&lt;/span&gt;

&lt;span class="c1"&gt;//...existing code...&lt;/span&gt;

&lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddAuthorization&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;options&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddPolicy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"GetSystemStatePolicy"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;policy&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;
        &lt;span class="n"&gt;policy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;RequireClaim&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"permissions"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"tool:getsystemstate"&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

    &lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddPolicy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"SetSystemStatePolicy"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;policy&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;
        &lt;span class="n"&gt;policy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;RequireClaim&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"permissions"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"tool:setsystemstate"&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="c1"&gt;//...existing code...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This code defines two authorization policies:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;GetSystemStatePolicy&lt;/code&gt; based on the &lt;code&gt;tool:getsystemstate&lt;/code&gt; value for the claim &lt;code&gt;permissions&lt;/code&gt;.
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;SetSystemStatePolicy&lt;/code&gt; based on the &lt;code&gt;tool:setsystemstate&lt;/code&gt; value for the claim &lt;code&gt;permissions&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Let’s complete the protection of the tools by applying the authorization filter with the specific policy, as shown below:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="c1"&gt;//Tools/SystemTools.cs&lt;/span&gt;

&lt;span class="c1"&gt;//...existing code...&lt;/span&gt;

&lt;span class="k"&gt;internal&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;SystemTools&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;//...existing code...&lt;/span&gt;

    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;McpServerTool&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;Description&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Returns the current state of the fictitious system. Possible states are: Ready, Waiting, Running, Stopped."&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;Authorize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Policy&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"GetSystemStatePolicy"&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;  &lt;span class="c1"&gt;//👈 new code&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="nf"&gt;GetSystemState&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;//...existing code...&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;McpServerTool&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;Description&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Sets the current state of the fictitious system. Valid states are: Ready, Waiting, Running, Stopped."&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;Authorize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Policy&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"SetSystemStatePolicy"&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;  &lt;span class="c1"&gt;//👈 new code&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="nf"&gt;SetSystemState&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;Description&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"The new state to set. Must be one of: Ready, Waiting, Running, Stopped."&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt; &lt;span class="n"&gt;SystemState&lt;/span&gt; &lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;//...existing code...&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Your MCP server is secure now!&lt;/p&gt;

&lt;h2&gt;
  
  
  Test Your Secure MCP Server
&lt;/h2&gt;

&lt;p&gt;Now it’s time to test your brand new secure MCP server!&lt;/p&gt;

&lt;p&gt;Run your server with the &lt;code&gt;dotnet run&lt;/code&gt; command.&lt;/p&gt;

&lt;p&gt;Then, in your VSCode instance, &lt;a href="https://code.visualstudio.com/docs/copilot/customization/mcp-servers#_manage-mcp-servers" rel="noopener noreferrer"&gt;restart your MCP server&lt;/a&gt;:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fszwrz0m7xcozmjor0qzl.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fszwrz0m7xcozmjor0qzl.png" alt="Start an MCP server in VSCode" width="605" height="153"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;After a few seconds, you will see a popup message like the following:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6j9hsx8l701i3fmz18mz.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6j9hsx8l701i3fmz18mz.png" alt="MCP server requests authentication in VSCode" width="316" height="169"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This message is the effect of the &lt;code&gt;RequireAuthorization()&lt;/code&gt; execution you added to the server. It sent a 401 HTTP status message to VSCode with a reference to the Protected Resource Metadata document, where VSCode found all the needed info to start the user authentication and authorization process.&lt;/p&gt;

&lt;p&gt;Once you have allowed the authentication flow, you will get the &lt;a href="https://auth0.com/blog/anatomy-of-an-oauth2-authorization-request/" rel="noopener noreferrer"&gt;authorization request&lt;/a&gt; that VSCode is about to send to Auth0, as shown below:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffnewm5m4s1vx0os0p97o.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffnewm5m4s1vx0os0p97o.png" alt="Authorization request warning for MCP server in VSCode" width="567" height="235"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;After user authentication, you will be requested to authorize VSCode to access your profile:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7mfoyx0nlgcz9fmoy85i.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7mfoyx0nlgcz9fmoy85i.png" alt="Authorization consent prompt for an MCP server on the Auth0 side" width="800" height="834"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Once you accept the request, you will be redirected back to your VSCode instance.&lt;/p&gt;

&lt;p&gt;You didn’t notice it, but under the hood, your VSCode instance automatically registered with Auth0 using DCR. You can verify this by going to your Auth0 dashboard and look in the list of the &lt;a href="https://manage.auth0.com/#/applications" rel="noopener noreferrer"&gt;registered applications&lt;/a&gt;. You will see VSCode among them, and it is marked as a third-party application, as you can see below:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvdapytz8fqtpnypekl1i.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvdapytz8fqtpnypekl1i.png" alt="VSCode is automatically registered as a client application on the Auth0 dashboard" width="800" height="287"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now your VSCode instance has an access token to access the tools implemented by your MCP server.&lt;/p&gt;

&lt;p&gt;Make sure your chat window is in Agent mode and &lt;a href="https://code.visualstudio.com/docs/copilot/agents/agent-tools#_enable-tools-for-chat" rel="noopener noreferrer"&gt;list the tools&lt;/a&gt; provided by your MCP server. You will see only the &lt;code&gt;get_random_number&lt;/code&gt; tool:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fp04v1aj76vk6khf2z539.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fp04v1aj76vk6khf2z539.png" alt="List only Random Numbers tool for the MCP server on VSCode" width="618" height="172"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Why? Doesn’t VSCode have an access token?&lt;/p&gt;

&lt;p&gt;VSCode has an access token, but without the required permissions to access the protected tools. So you can only access the public tool as before the protection implementation.&lt;/p&gt;

&lt;p&gt;To test your MCP server properly, you should assign one or more of the required permissions to your user profile on Auth0. Follow the instructions in &lt;a href="https://auth0.com/docs/manage-users/access-control/configure-core-rbac/rbac-users/assign-permissions-to-users" rel="noopener noreferrer"&gt;Assign Permissions to Users&lt;/a&gt; to assign the &lt;code&gt;tool:getsystemstate&lt;/code&gt; permission to your user profile.&lt;/p&gt;

&lt;p&gt;Use the command palette to &lt;a href="https://code.visualstudio.com/docs/copilot/customization/mcp-servers#_manage-mcp-servers" rel="noopener noreferrer"&gt;sign out and restart&lt;/a&gt; your MCP server in VSCode. Then authenticate and check again the list of tools available to you. This time you should also see the &lt;code&gt;get_system_state&lt;/code&gt; tool:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnq1ma49cpxyj0adzzesc.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnq1ma49cpxyj0adzzesc.png" alt="List two tools for the MCP server on VSCode" width="618" height="172"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Ask something involving the state of the fictitious system in the chat window, as shown in the following screenshot:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8b2jpmoo0xadvfcara4l.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8b2jpmoo0xadvfcara4l.png" alt="Running the Get System State tool of the MCP server in the Copilot chat of VSCode" width="269" height="303"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You should be able to use the tool as expected.&lt;/p&gt;

&lt;p&gt;Now, feel free to play with the permissions to get access to the other tool.&lt;/p&gt;

&lt;h2&gt;
  
  
  Let’s Recap
&lt;/h2&gt;

&lt;p&gt;After a long journey, this article provided a comprehensive guide on securing a C# MCP server using Auth0, transforming it from a local experiment into an enterprise-ready resource server.&lt;/p&gt;

&lt;p&gt;The journey covered the following main steps:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Building the Base MCP Server:&lt;/strong&gt; You quickly set up a basic, unsecured MCP server using the C# SDK for MCP and the MCP server template, exposing a simple &lt;code&gt;GetRandomNumber()&lt;/code&gt; tool.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Defining Protected Tools:&lt;/strong&gt; You introduced two new tools, &lt;code&gt;GetSystemState()&lt;/code&gt; and &lt;code&gt;SetSystemState()&lt;/code&gt;, which require specific authorization, setting the stage for security implementation.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Auth0 Tenant Configuration:&lt;/strong&gt; You configured the Auth0 tenant by enabling &lt;strong&gt;Dynamic Client Registration (DCR)&lt;/strong&gt; to allow MCP clients (like VSCode) to self-register, and enabling the &lt;strong&gt;Resource Parameter Compatibility Profile&lt;/strong&gt; as required by the MCP specification. We also promoted necessary connections to the domain level.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Registering the MCP Server as an API:&lt;/strong&gt; The MCP server was registered with Auth0 as a standard API (Resource Server), ensuring its base URL was set as the audience. You configured it to use &lt;strong&gt;RFC 9068 access tokens&lt;/strong&gt; and enabled &lt;strong&gt;Role-Based Access Control (RBAC)&lt;/strong&gt; to include permissions in the access token.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Securing the Server Code:&lt;/strong&gt; You updated the C# application to:

&lt;ul&gt;
&lt;li&gt;Include necessary configuration via &lt;code&gt;appsettings.json&lt;/code&gt;.
&lt;/li&gt;
&lt;li&gt;Integrate the &lt;code&gt;JwtBearer&lt;/code&gt; authentication middleware for access token validation against Auth0.
&lt;/li&gt;
&lt;li&gt;Define the &lt;strong&gt;Protected Resource Metadata (PRM)&lt;/strong&gt; document using &lt;code&gt;AddMcp()&lt;/code&gt;, providing clients with authorization server and scope information.
&lt;/li&gt;
&lt;li&gt;Implement authorization policies based on specific permissions.
&lt;/li&gt;
&lt;li&gt;Apply these policies to the respective tools using the &lt;code&gt;[Authorize]&lt;/code&gt; attribute.
&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;strong&gt;Testing the Secure Server:&lt;/strong&gt; Finally, you tested the secured server by integrating it with VSCode. The process demonstrated the DCR flow, user authentication, and the crucial step of assigning user permissions within Auth0 to grant access to the newly protected tools.&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;By following these steps, you successfully implemented an OAuth 2.1-compliant security layer, ensuring that access to sensitive tools is gated by proper user permissions validated through Auth0.&lt;/p&gt;

&lt;p&gt;You can download the final version of this project from this &lt;a href="https://github.com/auth0-samples/auth0-ai-samples" rel="noopener noreferrer"&gt;GitHub repository&lt;/a&gt;, looking into the &lt;a href="https://github.com/auth0-samples/auth0-ai-samples/tree/main/auth-for-mcp/aspnetcore-mcp-server" rel="noopener noreferrer"&gt;auth-for-mcp/aspnetcore-mcp-server&lt;/a&gt; folder.&lt;/p&gt;

&lt;p&gt;To learn more about using Auth0 to secure your AI agents check out &lt;a href="http://a0.to/ai-content" rel="noopener noreferrer"&gt;Auth0 for AI Agents&lt;/a&gt;. &lt;/p&gt;

</description>
      <category>mcp</category>
      <category>ai</category>
      <category>security</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>The four-year-old typo that nobody can fix 🧟 and more about building developer tools</title>
      <dc:creator>Carla Urrea Stabile</dc:creator>
      <pubDate>Wed, 25 Feb 2026 14:30:00 +0000</pubDate>
      <link>https://forem.com/auth0/building-tools-that-developers-actually-want-to-use-6j6</link>
      <guid>https://forem.com/auth0/building-tools-that-developers-actually-want-to-use-6j6</guid>
      <description>&lt;p&gt;As the host of &lt;strong&gt;Making Software&lt;/strong&gt; I get to have some pretty real conversations about what it actually takes to build good software. And in my recent chat with &lt;a href="https://rhamzeh.com" rel="noopener noreferrer"&gt;&lt;strong&gt;Raghd Hamzeh&lt;/strong&gt;&lt;/a&gt; (Senior Software Engineer at Okta and a core maintainer of &lt;a href="https://openfga.dev/" rel="noopener noreferrer"&gt;&lt;strong&gt;OpenFGA&lt;/strong&gt;&lt;/a&gt;) hit home for me.&lt;/p&gt;

&lt;p&gt;We talked about a side of engineering that doesn’t get enough airtime: what happens when your users are other engineers? If you’ve ever built an API, an SDK, or even just a shared library, you know that developers are the most rewarding, but also the most "vocal" audience you can have.&lt;/p&gt;

&lt;p&gt;Here are the three things from our conversation that have been living rent-free in my head:&lt;/p&gt;

&lt;h3&gt;
  
  
  1. The "Breaking Change" Itch is Real 😬
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Raghd&lt;/strong&gt; admitted something that I think a lot of us can relate to: when there’s a four-year-old typo in one of the products you're building, that drives you crazy. It could be just a small naming inconsistency, but when it’s &lt;em&gt;there&lt;/em&gt;, it's staring back at you every time.&lt;/p&gt;

&lt;p&gt;We got into that internal battle every maintainer knows: do you break everyone’s build just to satisfy the need for a clean API? Probably not. But it’s a powerful reminder that the decisions we make in Month 1, like how we pass a parameter, become the legacy we have to live with in Year 5.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Stop building "Alien" SDKs 👽
&lt;/h3&gt;

&lt;p&gt;This one got personal! While coding on the Ruby SDK for OpenFGA, I ran into this head-on. Rubyists are &lt;em&gt;opinionated&lt;/em&gt; (I should know, I’m one of them).&lt;/p&gt;

&lt;p&gt;When you’re building multi-language tools, you’re constantly balancing two things:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Consistency:&lt;/strong&gt; Making sure the Go SDK and the Python SDK behave the same way.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Idiom:&lt;/strong&gt; Making sure a Ruby dev doesn't feel like they're writing Java code with extra steps.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Raghd’s&lt;/strong&gt; take? You won't get it perfect 100% of the time. Sometimes, you have to prioritize the long-term health of the platform over a "perfectly idiomatic" implementation just so the project stays maintainable for the team behind it. That tension never fully goes away, you just get better at navigating it.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. The "AI" file that’s actually for humans 🤖
&lt;/h3&gt;

&lt;p&gt;With the rise of AI, everyone is adding &lt;code&gt;agents.md&lt;/code&gt; files to their repos. &lt;strong&gt;Raghd&lt;/strong&gt; had a spicy take: &lt;strong&gt;Write that file for your human contributors first.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If you want people to help you build your open-source dream, lay out your expectations clearly:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;How should commits be structured?&lt;/li&gt;
&lt;li&gt;What does the core architecture look like?&lt;/li&gt;
&lt;li&gt;Should contributors open an issue before submitting a PR?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Documenting these "non-code" decisions up front saves an enormous amount of friction. It turns "This PR isn't what we wanted" into "Here is the blueprint we’re all following."&lt;/p&gt;

&lt;h3&gt;
  
  
  🎙️ Want the full story?
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Check out the full episode of Making Software here: &lt;a href="https://listen.casted.us/public/49/Making-Software-2b1cff7b/35fd6fc3" rel="noopener noreferrer"&gt;Link to episode&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I’d love to hear from you in the comments, what’s the one tiny thing in your codebase that you’re dying to "breaking change" just to feel better? 😅&lt;/p&gt;

</description>
      <category>api</category>
      <category>discuss</category>
      <category>softwareengineering</category>
      <category>tooling</category>
    </item>
    <item>
      <title>Strengthening OAuth 2.0 with FAPI 2.0</title>
      <dc:creator>Andrea Chiarelli</dc:creator>
      <pubDate>Tue, 24 Feb 2026 08:27:26 +0000</pubDate>
      <link>https://forem.com/auth0/strengthening-oauth-20-with-fapi-20-oea</link>
      <guid>https://forem.com/auth0/strengthening-oauth-20-with-fapi-20-oea</guid>
      <description>&lt;p&gt;OAuth 2.0 has long been the cornerstone of modern authorization. It is a consolidated technology that powers everything from social logins to complex enterprise integrations. Developers appreciate its flexibility, which allows them to adapt the framework to various scenarios like traditional web applications, single page applications, desktop and mobile environments.&lt;/p&gt;

&lt;p&gt;However, this flexibility is a double-edged sword. Because OAuth 2.0 is a framework rather than a rigid protocol, it provides a collection of specifications that developers can combine in different ways. In high-risk and regulated sectors like banking, healthcare, and insurance, this freedom creates a significant surface area for configuration errors and interoperability issues. This is where the FAPI profile steps in, providing a strict set of rules to ensure the highest levels of security and trust.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Flexibility Paradox of OAuth 2.0
&lt;/h2&gt;

&lt;p&gt;The fundamental challenge with "vanilla" OAuth 2.0 is the vast number of optional features. While the &lt;a href="https://datatracker.ietf.org/doc/html/rfc6749" rel="noopener noreferrer"&gt;core specification&lt;/a&gt; defines the basic grant types, it leaves many security decisions to the implementer. For a developer building a low-risk internal tool, these choices are manageable. But for an architect designing a system for &lt;a href="https://en.wikipedia.org/wiki/Open_banking" rel="noopener noreferrer"&gt;Open Banking&lt;/a&gt; or sensitive patient records, the stakes are much higher.&lt;/p&gt;

&lt;p&gt;In high-assurance environments, the phrase "&lt;em&gt;with great power comes great responsibility&lt;/em&gt;" applies literally. Security vulnerabilities often arise not from flaws in the individual specifications, but from how they are combined. For example, a system might use the &lt;a href="https://auth0.com/docs/get-started/authentication-and-authorization-flow/authorization-code-flow" rel="noopener noreferrer"&gt;Authorization Code Flow&lt;/a&gt; but fail to implement &lt;a href="https://datatracker.ietf.org/doc/html/rfc7636" rel="noopener noreferrer"&gt;Proof Key for Code Exchange (PKCE)&lt;/a&gt; or use insecure redirect URIs. FAPI 2.0 solves this by removing the "optional" from security best practices and mandating a "secure by default" posture.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Is FAPI 2.0?
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://openid.net/specs/fapi-2_0-security-02.html" rel="noopener noreferrer"&gt;FAPI 2.0&lt;/a&gt; is a security profile developed by the OpenID Foundation. Unlike its predecessor (FAPI 1.0), which was often criticized for its complexity, FAPI 2.0 focuses on simplicity and robust interoperability. It is not a new protocol. Instead, it is &lt;strong&gt;a prescriptive profile&lt;/strong&gt; that defines exactly which OAuth 2.0 and OpenID Connect extensions must be used and how they must be configured.&lt;/p&gt;

&lt;p&gt;The profile is built upon a formal attacker model that assumes a high-threat environment where attackers might control the network or have the ability to intercept front-channel messages. By adhering to FAPI 2.0, organizations ensure their authorization servers and clients meet a standardized level of resistance against modern attack vectors like session injection, token theft, and authorization request tampering.&lt;/p&gt;

&lt;h2&gt;
  
  
  Hardening OAuth 2.0
&lt;/h2&gt;

&lt;p&gt;The key security enhancements introduced by FAPI 2.0 to strengthen the OAuth 2.0 framework focus on three main areas: hardening the authorization request, eliminating bearer token risks, and improving client authentication. Here are the most relevant protection mechanisms FAPI 2.0 enforces:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Pushed Authorization Requests (PAR):&lt;/strong&gt; FAPI 2.0 mandates &lt;a href="https://a0.to/pushed-authorization-6da4c7" rel="noopener noreferrer"&gt;PAR&lt;/a&gt; to mitigate the risks of sending sensitive authorization parameters (like scopes and PKCE challenges) over the insecure front-channel (browser). With PAR, the client sends these parameters via a secure back-channel POST request before redirection. The server then issues a short-lived &lt;code&gt;request_uri&lt;/code&gt; used in the subsequent browser redirect, ensuring sensitive data never appears in browser history or logs.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Sender-Constrained Tokens:&lt;/strong&gt; FAPI 2.0 moves away from vulnerable bearer tokens by enforcing "sender-constrained" tokens, which are cryptographically bound to the client that requested them. This prevents token misuse even if stolen. The primary mechanisms for this are Mutual TLS (mTLS), which binds the token to the client's X.509 certificate, and &lt;a href="https://a0.to/dpop-d188dc" rel="noopener noreferrer"&gt;Demonstration of Proof-of-Possession (DPoP)&lt;/a&gt;, a more flexible, application-level solution where the client proves possession of a private key using a signed JWT with every request.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Asymmetric Client Authentication:&lt;/strong&gt; FAPI 2.0 replaces shared client secrets with asymmetric authentication, typically using Private Key JWT. The client signs a JWT with its private key, which the authorization server verifies with the client's registered public key. This method removes the risk of client secrets being leaked during transmission.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The Role of FAPI Certification
&lt;/h2&gt;

&lt;p&gt;A critical component of the FAPI ecosystem is the &lt;a href="https://openid.net/certification/" rel="noopener noreferrer"&gt;OpenID Foundation's certification program&lt;/a&gt;. Because security depends on correct implementation, the foundation provides a suite of automated tests that vendors and organizations can use to verify their compliance. This certification provides a common ground for interoperability, ensuring that a FAPI-compliant client from one vendor can work seamlessly and securely with a FAPI-compliant authorization server from another.&lt;/p&gt;

&lt;p&gt;Choosing certified components reduces the burden of security auditing and provides assurance that the system adheres to the industry's highest standards.&lt;/p&gt;

&lt;h2&gt;
  
  
  References and Further Reading
&lt;/h2&gt;

&lt;p&gt;For more technical details on the specifications mentioned, consult the following official resources:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://openid.net/specs/fapi-security-profile-2_0-final.html" rel="noopener noreferrer"&gt;OpenID FAPI 2.0 Security Profile - Final Specification&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://datatracker.ietf.org/doc/html/rfc9126" rel="noopener noreferrer"&gt;OAuth 2.0 Pushed Authorization Requests (RFC 9126)&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://datatracker.ietf.org/doc/html/rfc9449" rel="noopener noreferrer"&gt;OAuth 2.0 Demonstration of Proof-of-Possession (RFC 9449)&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="https://openid.net/specs/fapi-attacker-model-2_0-final.html" rel="noopener noreferrer"&gt;FAPI 2.0 Attacker Model&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;To learn more about FAPI 2.0 from a developer’s perspective, download the free ebook &lt;a href="https://a0.to/fapi-book-26d859" rel="noopener noreferrer"&gt;A Developer’s Guide to FAPI&lt;/a&gt;, which provides a detailed overview of the various security measures to make your application FAPI-ready.&lt;/p&gt;

</description>
      <category>oauth</category>
      <category>security</category>
      <category>identity</category>
      <category>fapi</category>
    </item>
    <item>
      <title>Why Your Demo Code Should Be Treated as Production Code (And Other DevRel Secrets) 🎸</title>
      <dc:creator>Carla Urrea Stabile</dc:creator>
      <pubDate>Thu, 29 Jan 2026 10:38:56 +0000</pubDate>
      <link>https://forem.com/auth0/why-your-demo-code-should-be-treated-as-production-code-and-other-devrel-secrets-3332</link>
      <guid>https://forem.com/auth0/why-your-demo-code-should-be-treated-as-production-code-and-other-devrel-secrets-3332</guid>
      <description>&lt;p&gt;Have you ever found a code snippet in a tutorial, spent an hour trying to make it work, only to realize it’s using a deprecated library from 2019?&lt;/p&gt;

&lt;p&gt;We’ve all been there.&lt;/p&gt;

&lt;p&gt;I recently sat down with &lt;a href="https://sambego.tech/" rel="noopener noreferrer"&gt;Sam Bellen&lt;/a&gt;, Principal Developer Advocate at Auth0, for the first episode of the Making Software podcast. We talked about the "diplomatic" nature of Developer Relations, the controversy of "selling" to developers, and why we need to stop treating demo code like a second-class citizen.&lt;/p&gt;

&lt;p&gt;Here are the key takeaways for every developer, whether you’re looking to break into DevRel or just want to build better software.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. DevRel: Part Engineer, Part Diplomat 🤝
&lt;/h2&gt;

&lt;p&gt;Sam and I agreed on a unique definition: DevRel is a mix of developer and diplomat.&lt;/p&gt;

&lt;p&gt;As a developer, you’re used to talking to Product Teams to understand requirements. In DevRel, that flow often reverses. You are the "voice of the developer" inside the company.&lt;/p&gt;

&lt;p&gt;"It’s building relationships with the product teams... at a certain point, they will start trusting that what you’re telling them is actually valuable feedback instead of just randomly pinging people on Slack." — Sam Bellen&lt;/p&gt;

&lt;p&gt;The Lesson: If you want to influence a product's roadmap, don't just throw bugs over the fence. Build a relationship with the people making the product. Trust is the currency of influence.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Authenticity &amp;gt; Sales 🚫💰
&lt;/h2&gt;

&lt;p&gt;Developers are notoriously skeptical of being "sold" to. We can smell a marketing pitch from a mile away. Sam’s approach? Lead with the problem, not the product.&lt;/p&gt;

&lt;p&gt;Instead of saying, "Use our auth tool because it's great," talk about how hard it is to implement DPoP (Demonstrating Proof-of-Possession) or Passkeys manually.&lt;/p&gt;

&lt;p&gt;When you illustrate the complexity of a problem, the solution (your product) becomes a natural part of the conversation rather than a forced advertisement.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Treat Demo Code as Production Code 🛠️
&lt;/h2&gt;

&lt;p&gt;This was Sam’s biggest take: Any code you write (even a tiny sample for a blog post) should be treated as production code.&lt;/p&gt;

&lt;p&gt;If a vulnerability is found in a framework, DevRel teams should be the first to update their samples. Why? Because that sample is often a developer's first "touchpoint" with your tech.&lt;/p&gt;

&lt;p&gt;Why this matters:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Security: Developers often copy-paste samples directly into their projects.&lt;/li&gt;
&lt;li&gt;Trust: If your "Quick Start" doesn't work out of the box, you've lost the user.&lt;/li&gt;
&lt;li&gt;Maintenance: Technical debt exists in docs, too. Set aside time (Sam recommends monthly) to audit your repos for deprecated APIs.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  4. Get Your Hands Dirty with the Spec 📖
&lt;/h2&gt;

&lt;p&gt;Sam has a "superpower": he actually likes reading technical specifications. While most of us fall asleep reading an RFC, Sam uses them to build visual tools that explain what’s happening "under the hood."&lt;/p&gt;

&lt;p&gt;"In order for me to explain something... I build it myself. Not a clone of the product, but a demo that implements the topic. I make loads of mistakes, get lots of errors, and eventually, I figure out what's happening."&lt;/p&gt;

&lt;p&gt;The "Aha!" Moment: You don't truly understand a tool until you've tried to build the logic it simplifies.&lt;/p&gt;

&lt;h2&gt;
  
  
  Summary: Two Steps to Better Developer Experience
&lt;/h2&gt;

&lt;p&gt;If you’re an engineer looking to improve the DX (Developer Experience) of your project today, start here:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Listen to your DevRel team: They aren't just "social developers." They have the technical baggage and the community feedback to tell you what’s actually broken.&lt;/li&gt;
&lt;li&gt;Stop "dropping" code on GitHub: Don't just create a repo and ignore it. If it’s public, it’s "production" for someone else.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  🎧 Listen to the Full Episode
&lt;/h2&gt;

&lt;p&gt;Want to hear the full conversation about guitars, Passkeys, and the early days of DevRel?&lt;/p&gt;

&lt;p&gt;&lt;a href="https://listen.casted.us/public/49/Making-Software-2b1cff7b/75c539aa/share/7a4d465a" rel="noopener noreferrer"&gt;Listen to the full episode here&lt;/a&gt; or in your platform of choice.&lt;/p&gt;

&lt;p&gt;What’s your take? Should demo code be held to the same linting and security standards as the core product? Let’s talk in the comments! 👇&lt;/p&gt;

</description>
      <category>devrel</category>
      <category>softwareengineering</category>
      <category>webdev</category>
      <category>programming</category>
    </item>
    <item>
      <title>Auth0 for AI Agents is now generally available!</title>
      <dc:creator>Jessica Temporal</dc:creator>
      <pubDate>Mon, 24 Nov 2025 16:32:18 +0000</pubDate>
      <link>https://forem.com/auth0/auth0-for-ai-agents-is-now-generally-available-29el</link>
      <guid>https://forem.com/auth0/auth0-for-ai-agents-is-now-generally-available-29el</guid>
      <description>&lt;p&gt;Hey DEV Community! 👋&lt;/p&gt;

&lt;p&gt;If you're building AI agents right now (and honestly, who isn't?), you've probably hit the auth problem. You know the one - where the quickest path to getting your agent working is to just hardcode some API keys and move on. It works great... until you need to actually ship to production.&lt;/p&gt;

&lt;p&gt;Today, we're excited to share that &lt;strong&gt;Auth0 for AI Agents is now generally available&lt;/strong&gt;, and it's designed to solve exactly this problem.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://auth0.com/signup?onboard_app=auth_for_aa&amp;amp;ocid=701KZ000000cXXxYAM_aPA4z0000008OZeGAM?utm_source=devto&amp;amp;utm_campaign=amer_namer_usa_all_ciam_dev_dg_plg_auth0_display_devto_display_aud_Q4_GAlaunch_nov2025_utm2&amp;amp;utm_medium=cpc&amp;amp;utm_id=aNKWR000002Z1ph4AC" class="crayons-btn crayons-btn--primary" rel="noopener noreferrer"&gt;Auth0 for AI Agents&lt;/a&gt;
&lt;/p&gt;

&lt;p&gt;

  &lt;iframe src="https://www.youtube.com/embed/YypMEnCetqI"&gt;
  &lt;/iframe&gt;


&lt;/p&gt;




&lt;h2&gt;
  
  
  The Problem with Hardcoded Credentials
&lt;/h2&gt;

&lt;p&gt;Let's be real: when you're prototyping an AI agent with LangChain or LlamaIndex, hardcoded credentials are the path of least resistance. Your agent needs to access Slack, GitHub, Google Calendar, or your own APIs, and frameworks make it easy to just plug in those keys.&lt;/p&gt;

&lt;p&gt;But production is a different story:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;What happens when your agent needs to act on behalf of different users with different permissions?&lt;/li&gt;
&lt;li&gt;How do you handle token refreshing across 30+ different apps?&lt;/li&gt;
&lt;li&gt;How do you let users approve critical actions (like making purchases) without giving your agent carte blanche?&lt;/li&gt;
&lt;li&gt;How do you ensure your RAG-powered agent only accesses documents the user actually has permission to see?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These aren't edge cases - they're fundamental requirements for any AI agent that's going to interact with real user data and take real actions.&lt;/p&gt;

&lt;h2&gt;
  
  
  What We Built
&lt;/h2&gt;

&lt;p&gt;Auth0 for AI Agents gives you four key capabilities:&lt;/p&gt;

&lt;h3&gt;
  
  
  1. &lt;strong&gt;User Authentication&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Secure and scalable &lt;a href="https://auth0.com/ai/docs/intro/user-authentication" rel="noopener noreferrer"&gt;User Authentication&lt;/a&gt; allows you to identify who's talking to your agent and give it secure access to your first-party APIs. Your agent can access user-specific data like order history, preferences, or chat logs - all scoped to the right permissions.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. &lt;strong&gt;Token Vault&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://auth0.com/features/token-vault" rel="noopener noreferrer"&gt;Token Vault&lt;/a&gt; handles OAuth flows with 30+ pre-integrated apps (GitHub, Slack, Google Workspace, and more) plus any custom OAuth provider you want to connect. It manages access tokens, refresh tokens, and the whole lifecycle automatically. Your agent requests a connection, the user authorizes once, and you never have to think about token management again.&lt;/p&gt;

&lt;p&gt;The SDK detects when a tool call needs authentication, pauses execution, prompts the user to authenticate, stores the token securely, and resumes automatically. On subsequent calls, it just works.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. &lt;strong&gt;Asynchronous Authorization&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Your agent can work in the background and only interrupt the user when it needs approval for critical actions. Using &lt;a href="https://auth0.com/docs/get-started/authentication-and-authorization-flow/client-initiated-backchannel-authentication-flow" rel="noopener noreferrer"&gt;Client-Initiated Backchannel Authentication (CIBA)&lt;/a&gt;, you can send approval requests via email or Auth0 Guardian (SMS coming soon). &lt;/p&gt;

&lt;h3&gt;
  
  
  4. &lt;strong&gt;FGA for RAG&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;When your agent uses Retrieval Augmented Generation to search through documents, it needs to respect access controls. &lt;a href="https://auth0.com/ai/docs/get-started/authorization-for-rag" rel="noopener noreferrer"&gt;Fine-Grained Authorization for RAG&lt;/a&gt; ensures that users only get answers from documents they actually have permission to access.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why This Matters
&lt;/h2&gt;

&lt;p&gt;AI agents are moving from demos to production. The difference between a hackathon project and a real product often comes down to handling auth correctly. We built Auth0 for AI Agents because we kept hearing from developers that this was the hard part - not the LLM integration, not the prompt engineering, but the secure, user-scoped access to real systems.&lt;/p&gt;

&lt;p&gt;This isn't about adding features. It's about removing blockers so you can ship production-ready AI agents without building your own auth infrastructure from scratch.&lt;/p&gt;

&lt;h2&gt;
  
  
  Framework Support
&lt;/h2&gt;

&lt;p&gt;We've built SDKs for the frameworks you're already using:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;LangChain&lt;/strong&gt; (Python &amp;amp; JavaScript)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;LlamaIndex&lt;/strong&gt; (Python &amp;amp; JavaScript)
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Cloudflare AI&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Firebase Genkit&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Vercel AI SDK&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Each SDK handles the OAuth dance automatically, so you can focus on building your agent's capabilities, not wrestling with authentication flows.&lt;/p&gt;




&lt;h2&gt;
  
  
  Get Started
&lt;/h2&gt;

&lt;p&gt;Our free tier includes two connected apps in Token Vault, async authorization, and all the core features you need to start building. As you scale, we have self-service plans that grow with you.&lt;/p&gt;

&lt;p&gt;Early-stage startups can apply for one year of Auth0 free.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://auth0.com/signup?onboard_app=auth_for_aa&amp;amp;ocid=701KZ000000cXXxYAM_aPA4z0000008OZeGAM?utm_source=devto&amp;amp;utm_campaign=amer_namer_usa_all_ciam_dev_dg_plg_auth0_display_devto_display_aud_Q4_GAlaunch_nov2025_utm2&amp;amp;utm_medium=cpc&amp;amp;utm_id=aNKWR000002Z1ph4AC" class="crayons-btn crayons-btn--primary" rel="noopener noreferrer"&gt;Start Building Today&lt;/a&gt;
&lt;/p&gt;

</description>
      <category>auth0</category>
      <category>agents</category>
      <category>security</category>
      <category>ai</category>
    </item>
    <item>
      <title>Let's Sketch Identity: Authentication vs. Authorization</title>
      <dc:creator>Ramona Schwering</dc:creator>
      <pubDate>Wed, 24 Sep 2025 11:01:00 +0000</pubDate>
      <link>https://forem.com/auth0/lets-sketch-identity-authentication-vs-authorization-48kb</link>
      <guid>https://forem.com/auth0/lets-sketch-identity-authentication-vs-authorization-48kb</guid>
      <description>&lt;p&gt;So, you are building an application and need a login form. In it, you’ll get the user's email and password, send them to an API, and... something happens. The user is logged in afterwards. But what is that something? How does your application decide who gets in and what they get to see?&lt;/p&gt;

&lt;p&gt;This is the first article in a series called "Let's Sketch Identity." These blog posts will use my notes from when I started learning about identity concepts as a Developer Advocate. Think of them as my Identity Sketchbook, and join me on my journey back then! ❤️&lt;/p&gt;

&lt;p&gt;In this series, I will show you the core ideas of modern identity using a simple, continuous story: no complex specifications, just clear, practical explanations for web developers. Today, I am starting with the two most important concepts: Authentication and Authorization. You can think of them as a Bouncer checking IDs at a door and a Clipboard listing your permissions.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Is Authentication? The Bouncer Analogy
&lt;/h2&gt;

&lt;p&gt;Do you know about the Persuadable Bouncer? It’s an exploitable four-panel comic series featuring a man in a suit blocking a door and allowing the entrance in the fourth panel. This is how I like to picture Authentication (and I just love to draw memes 😁). Okay, think of authentication like this: Authentication is the bouncer standing at the front door, e.g., of a venue. In real life, the venue is your application. To get in, you have to show your ID, which has your credentials. &lt;/p&gt;

&lt;p&gt;Let's visualize this. In the first panel, we see our Bouncer blocking the door as a user presents their ID with their credentials. Once the Bouncer validates that ID, the lock clicks open, and as we see in the second panel, he finally opens the door.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbo4k94qj71gdh7gdohpd.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbo4k94qj71gdh7gdohpd.jpg" alt="A sketch note illustrating authentication in identity management, where a bouncer checks a user's ID before opening a door." width="800" height="499"&gt;&lt;/a&gt; &lt;/p&gt;

&lt;p&gt;And that’s it: You've been authenticated! The shorthand for this process is &lt;strong&gt;Authn&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;At its core, authentication is proving that somebody or something is who they say they are. It's the lock on the door of your application. Talking about Authn in an Identity scenario usually means verifying credentials. These credentials come in all shapes and sizes; while a username and password combination is the classic example, they can also be a private and public key pair. Modern approaches even include passwordless authentication, which verifies a user’s identity with something other than a password, like a magic link sent to their email or a biometric trait like a fingerprint.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Is Authorization? The Clipboard Analogy
&lt;/h2&gt;

&lt;p&gt;Just because you are inside the room does not mean you can do whatever you want. This is where Authorization comes in, like the bouncer handing you a clipboard. Picture that clipboard as a checklist or ruleset explaining to you your permissions:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fml2a47sm6dtoyk2uts1d.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fml2a47sm6dtoyk2uts1d.jpg" alt="A sketch note illustrating authorization, where a user is handed a clipboard listing their permissions. This visualizes a key part of the authentication vs. authorization flow." width="800" height="499"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You see, the clipboard lists exactly what you are allowed to do. As you can see in the sketch, you might have permission to view rooms and make a guestbook entry, but the permission to change the AC settings is firmly crossed out. One crucial rule is that the bouncer will always check your ID before handing you the clipboard. You must first prove who you are &lt;strong&gt;before&lt;/strong&gt; you can be given a list of things you can do.&lt;/p&gt;

&lt;p&gt;My short take: Once a user enters the door, we must know what they can do. That's Authorization. Authorization checks whether somebody or something has access to a particular resource or is allowed to perform a specific action. The shorthand for Authorization is &lt;strong&gt;Authz&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  How This Works in a Real-World Frontend Application
&lt;/h2&gt;

&lt;p&gt;So, how does this story of the bouncer and the clipboard play out in a real web application? Let's walk through it.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;A new user comes to your site and clicks on their "My Profile" link.&lt;/li&gt;
&lt;li&gt;Your application’s bouncer stops them, sees they do not have a valid ID yet, and sends them to the login page.&lt;/li&gt;
&lt;li&gt;The user provides their credentials (their ID). The system checks them and confirms their identity. Authentication is now successful.&lt;/li&gt;
&lt;li&gt;Now that your application knows who the user is, it prepares their personal clipboard of permissions.&lt;/li&gt;
&lt;li&gt;The user is sent to the "My Profile" page. They can see all their personal information, but the "Admin Panel" button is hidden. Why? Their clipboard says they do not have access to &lt;code&gt;admin_panel&lt;/code&gt; permission. This is Authorization in action.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Understanding this difference is very important for you as a frontend developer, as it directly affects the UI you build daily. Some pseudo-codes show you how that logic might look inside a component. Does this look familiar to you?&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// In some component that renders a navigation bar
function Navbar({ user }) {
  // The bouncer checks for an ID (Authentication)
  if (!user.isAuthenticated) {
    return &amp;lt;LoginButton /&amp;gt;;
  }
  // If authenticated, the bouncer checks the clipboard (Authorization)
  return (
    &amp;lt;div&amp;gt;
      &amp;lt;WelcomeMessage user={user} /&amp;gt;
      &amp;lt;ProfileLink /&amp;gt;
      {/* Check the clipboard for a specific permission */}
      {user.hasPermission('access:admin_panel') &amp;amp;&amp;amp;
        &amp;lt;AdminPanelLink /&amp;gt;
      }
      &amp;lt;LogoutButton /&amp;gt;
    &amp;lt;/div&amp;gt;
  );
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;However, no worries! This blog post will revolve around my sketch notes, so I have some prepared. Let’s take a look at this workflow:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fyw2i4dckgazls0pi7z8v.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fyw2i4dckgazls0pi7z8v.jpg" alt="A sketch showing the identity management flow from Authentication (Authn), represented by a bouncer, to Authorization (Authz), represented by a permissions clipboard." width="800" height="466"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You see, Authn and Authz are not the same thing. However, they belong together: Authentication is the first step (I’d even call it groundwork), so that Authz can take place. That makes sense, as you need to know your user before deciding on their permission, right? &lt;/p&gt;

&lt;h2&gt;
  
  
  But There Are Different Types of Clipboards!?
&lt;/h2&gt;

&lt;p&gt;That simple &lt;code&gt;.hasPermission('...')&lt;/code&gt; check in our code is powerful. However, it makes me think. How does the system decide on the user’s permissions in the first place? The bouncer's clipboard is not just a simple list. Let's take a quick look at the most common variations, as I depicted four types of clipboards in my sketches.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Role-Based Access Control&lt;/em&gt; (RBAC) assigns permissions to users based on their roles, such as “admin,” “editor,” or “viewer.” In the analogy I’m using in my sketches, this is like the “hat” the user wears. Instead of providing a single set of permissions, RBAC offers tailored permissions corresponding to each specific role. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3m4akaj13gw7574f9nyk.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3m4akaj13gw7574f9nyk.jpg" alt="A cartoon drawing illustrating Role-Based Access Control (RBAC). The scene, labeled " width="800" height="518"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Attribute-Based Access Control&lt;/em&gt; (ABAC) is an authorization model that determines access based on user attributes (or characteristics) rather than roles. It’s similar, but not the same as Policy-Based Access Control (PBAC): They are often considered the same, but are not. In our scene, these are the “tags” the user has on their conference badge, such as “Attendee”, “Speaker”, or the time when they check in. ABAC protects resources from unauthorized users and actions that do not align with the approved tags (which are basically attributes) established by an organization’s security policies.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwe80ufgggebv5pervd7r.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwe80ufgggebv5pervd7r.jpg" alt="A cartoon drawing illustrating Attribute-Based Access Control (ABAC). The scene, labeled " width="800" height="529"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Relationship-Based Access Control&lt;/em&gt; (ReBAC) handles access decisions based on a subject's relationships. Such a subject could be a user, device, or application. Or in our sketch, it’s visualized as a guest list, where only the family is added to. When a subject tries to access an event or a resource (in real life), the system evaluates the specific relationships tied to that subject to decide whether to grant access or not. In my analogy, it may look like this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkgjta9egore5ic7lhak8.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkgjta9egore5ic7lhak8.jpg" alt="A cartoon drawing illustrating Relationship-Based Access Control (ReBAC). The scene, labeled " width="800" height="486"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Last but not least, there’s &lt;em&gt;Delegated Authorization&lt;/em&gt;. It allows a user to grant one application permission to access their data from another service, without sharing their password, just like someone presenting their ID and a document issued by someone else asking to let them enter the room on their behalf. The user would have to approve the access requested by the first party to be shared by the third party. This access is limited to the permissions that the user grants. For example, LinkedIn would only get access to our Gmail contacts, but not our inbox or calendar.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fh7d1r9tm0fjir3wmd6e1.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fh7d1r9tm0fjir3wmd6e1.jpg" alt="A cartoon drawing illustrating Relationship-Based Access Control (ReBAC). The scene, labeled " width="800" height="471"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;And that is it for my first sketch! The story is this straightforward:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Authentication (Authn) is the action done by the Bouncer: They check your ID to prove who you are.&lt;/li&gt;
&lt;li&gt;Authorization (Authz) is the action of providing the Clipboard to the person, being the user. It lists what you can do once you are inside.
And you can never get your clipboard until the bouncer has approved your ID. &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Let's zoom out and look at the entire journey in a single picture to tie it all together. From the initial ID check by the Bouncer to the different kinds of Clipboards he uses, here is the full story from my Identity Sketchbook:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhwv1zlgb4qoasip2gnse.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhwv1zlgb4qoasip2gnse.jpg" alt="A complete sketchnote diagram illustrating the full identity management journey, covering the 'authentication vs. authorization' process and various authorization models like RBAC and ABAC." width="800" height="499"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Great, your user is now authenticated and inside the application! ❤️ However, you might have already guessed, Identity does not stop here. How does the app remember them when they navigate from one page to another? They do not have to show their ID for every single click. How is their "clipboard" carried around with them?&lt;br&gt;
In the next article, I will show you the answer: the Digital Passport, also known as the JSON Web Token (JWT). Stay tuned! 🔥&lt;/p&gt;

&lt;p&gt;In the meantime, there's some interesting reads if you want to dive deeper:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://auth0.com/blog/authorization-authentication-developers-global-scale/" rel="noopener noreferrer"&gt;Authentication and Authorization For Developers Who Build at Global Scale&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://auth0.com/blog/authentication-authorization-and-accounting-for-developers/" rel="noopener noreferrer"&gt;Authentication, Authorization, and Accounting For Developers&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://auth0.com/blog/five-ruby-gems-for-authentication-and-authorization/" rel="noopener noreferrer"&gt;Five Ruby Gems for Authentication and Authorization&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>authentication</category>
      <category>sketchnotes</category>
      <category>identity</category>
      <category>webdev</category>
    </item>
    <item>
      <title>How to Configure the Auth0 MCP Server in VS Code for AI Assistant Integration</title>
      <dc:creator>Jessica Temporal</dc:creator>
      <pubDate>Mon, 08 Sep 2025 21:48:24 +0000</pubDate>
      <link>https://forem.com/auth0/how-to-configure-the-auth0-mcp-server-in-vs-code-for-ai-assistant-integration-1edm</link>
      <guid>https://forem.com/auth0/how-to-configure-the-auth0-mcp-server-in-vs-code-for-ai-assistant-integration-1edm</guid>
      <description>&lt;p&gt;Not long ago we released the &lt;a href="https://auth0.com/blog/announcement-auth0-mcp-server-is-here/" rel="noopener noreferrer"&gt;Auth0 MCP Server&lt;/a&gt;, a specialized Model Context Protocol server that brings Auth0's identity management capabilities directly to your AI assistant conversations in your favorite app or IDE. This server enables you to analyze authentication patterns, identify authentication related issues, and manage identity operations through natural language interactions.&lt;/p&gt;

&lt;p&gt;This post will guide you through setting up the Auth0 MCP Server in your VS Code development environment and using it to perform some basic analysis.&lt;/p&gt;

&lt;p&gt;  &lt;iframe src="https://www.youtube.com/embed/z2vqwAA0byo"&gt;
  &lt;/iframe&gt;
&lt;/p&gt;

&lt;h2&gt;
  
  
  What Is the Auth0 MCP Server?
&lt;/h2&gt;

&lt;p&gt;The Auth0 MCP server provides a bridge between your AI assistant and Auth0's identity platform. Instead of manually navigating the Auth0 Dashboard or writing custom API calls, you can ask natural language questions like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;"Create an application for me"&lt;/li&gt;
&lt;li&gt;"Show me recent failed login attempts"&lt;/li&gt;
&lt;li&gt;"What applications are configured in my tenant?"&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The server translates these requests into Auth0 Management API calls and returns the results in a format that your AI assistant can understand and present clearly.&lt;/p&gt;

&lt;h3&gt;
  
  
  Key capabilities
&lt;/h3&gt;

&lt;p&gt;The Auth0 MCP server includes several tools for interacting with your Auth0 tenant:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;User Management&lt;/strong&gt;: Search, retrieve, and analyze user data across your tenant&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Log Analysis&lt;/strong&gt;: Query authentication logs to understand user behavior and troubleshoot issues&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Application Management&lt;/strong&gt;: Review and analyze your configured applications and their settings&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Statistics and Analytics&lt;/strong&gt;: Get insights into user activity, sign-ups, and authentication patterns&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Prerequisites
&lt;/h2&gt;

&lt;p&gt;Before configuring the Auth0 MCP server, ensure you have:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;An Auth0 account&lt;/strong&gt; with a configured tenant;&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;VS Code&lt;/strong&gt;;&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;npx&lt;/code&gt;&lt;/strong&gt;, &lt;a href="https://docs.npmjs.com/cli/v8/commands/npx" rel="noopener noreferrer"&gt;get npx here&lt;/a&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That's everything you need to get started!&lt;/p&gt;

&lt;h2&gt;
  
  
  Initializing the Auth0 MCP Server Configuration
&lt;/h2&gt;

&lt;p&gt;The Auth0 MCP Server needs access to your Auth0 account. To configure it, you need to:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Select the scopes&lt;/strong&gt;: which actions the server can perform for you&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Choose the tenant&lt;/strong&gt;: also define which tenant to perform actions in&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;And add the configuration to your project by creating the &lt;code&gt;mcp.json&lt;/code&gt; file with the settings in the appropriate location. So run the following command on the terminal:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx @auth0/auth0-mcp-server@0.1.0-beta.7 init &lt;span class="nt"&gt;--client&lt;/span&gt; vscode
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;The Auth0 MCP Server is currently in beta and is provided as is, for that reason we recommend pinning the version whenever possible.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This will start the configuration wizard prompting you to select the scopes for the server.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fosspcxga4951dvvyifiv.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fosspcxga4951dvvyifiv.jpg" alt="Scope selection screen for Auth0 MCP server configuration"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Once you select the scopes, you'll receive a code in the terminal. You will use this code to verify your device when logging in to Auth0.&lt;/p&gt;

&lt;h2&gt;
  
  
  Authorize the Auth0 MCP Server for Your Auth0 Tenant
&lt;/h2&gt;

&lt;p&gt;After selecting the scopes and pressing Enter, a browser window will open. This will take you to the Auth0 login page (if you are not logged in yet) and will ask you to confirm the device code you received in the terminal:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fimages.ctfassets.net%2F23aumh6u8s0i%2F1sEnNQ3qmELA3QZ2a282Qx%2F267d24231933de905f0d63e9cc1e4793%2Fdevice-flow-confirmation-code.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fimages.ctfassets.net%2F23aumh6u8s0i%2F1sEnNQ3qmELA3QZ2a282Qx%2F267d24231933de905f0d63e9cc1e4793%2Fdevice-flow-confirmation-code.png" alt="Device flow confirmation code screen"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Once confirmed you'll be able to both select the tenant you want to use and double check the scopes:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fimages.ctfassets.net%2F23aumh6u8s0i%2FWw3NQqnAX29XsuuI4Q2tj%2Ff3709147d2a5e7af312b73cbe12a728e%2Ftenant-selection-auth0-mcp-server.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fimages.ctfassets.net%2F23aumh6u8s0i%2FWw3NQqnAX29XsuuI4Q2tj%2Ff3709147d2a5e7af312b73cbe12a728e%2Ftenant-selection-auth0-mcp-server.png" alt="Tenant selection screen for Auth0 MCP server"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Once the authorization process finishes you'll see a message like the one below.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fimages.ctfassets.net%2F23aumh6u8s0i%2F6IOo5ukK6kcSjx6U2lfEac%2Fba8459944879f6eaf9c399825a3f8cf0%2Fconfirmation-auth0-mcp-server-device-flow.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fimages.ctfassets.net%2F23aumh6u8s0i%2F6IOo5ukK6kcSjx6U2lfEac%2Fba8459944879f6eaf9c399825a3f8cf0%2Fconfirmation-auth0-mcp-server-device-flow.png" alt="Confirmation screen for Auth0 MCP server device flow completion"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you haven't looked into &lt;a href="https://a0.to/auth0mcpserver" rel="noopener noreferrer"&gt;the Auth0 MCP Server source code  on GitHub&lt;/a&gt; you may be wondering, &lt;em&gt;"How does this work?"&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The Auth0 MCP Server uses device authorization flow to enable your access to the Auth0 Management API while running tools.&lt;/p&gt;

&lt;p&gt;Under the hood, when you finish logging in and authorizing the application, the Auth0 MCP Server will store in the system keychain (or similar depending on your operating system) the Auth0 access tokens and a few extra bits of information.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8pi01mx4hxwtd00u6hmt.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8pi01mx4hxwtd00u6hmt.png" alt="Auth0 access tokens stored in system keychain"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This step guarantees that the LLM in your AI assistant cannot access your Auth0 access token. The token is stored securely and is only retrieved by the MCP server when needed to perform an action.&lt;/p&gt;

&lt;p&gt;Stepping back into our configuration: feel free to close the browser with the confirmation screen and return to the terminal to finish the setup.&lt;/p&gt;

&lt;h2&gt;
  
  
  Choose Where to Configure the MCP Server for VS Code
&lt;/h2&gt;

&lt;p&gt;For VS Code, differently than Cursor and Claude, you can set two levels of access:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Workspace&lt;/strong&gt;: Configure for a specific project/repository;&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Global&lt;/strong&gt;: Configure for all VS Code instances&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Following the principle of least privilege, you should ideally configure your MCP Server at the &lt;strong&gt;Workspace&lt;/strong&gt; level. However, if you use the same server across multiple projects, a &lt;strong&gt;Global&lt;/strong&gt; configuration may be more practical.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;In this blog post we are going to use the Workspace approach to make sure only our project has access to the Auth0 MCP Server.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6ks8pjp8aukhx49suflq.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6ks8pjp8aukhx49suflq.png" alt="VS Code configuration scope selection for MCP server"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;When you choose "Workspace" for your MCP server a &lt;code&gt;.vscode/mcp.json&lt;/code&gt; file will be created in your project. If you are performing these steps within VS Code, the file will automatically open, and you'll see inline commands like this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fimages.ctfassets.net%2F23aumh6u8s0i%2FVx92PAxf4WvYvCJe5vAxl%2F0da3922746934aa50ead51f649dc30fd%2Fmcp-configuration-and-inline-commands-for-recently-created-mcp-server.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fimages.ctfassets.net%2F23aumh6u8s0i%2FVx92PAxf4WvYvCJe5vAxl%2F0da3922746934aa50ead51f649dc30fd%2Fmcp-configuration-and-inline-commands-for-recently-created-mcp-server.png" alt="MCP configuration file with inline commands for the Auth0 MCP server"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you open VS Code in the directory you selected during the configuration step, the server should already be up and running.&lt;/p&gt;

&lt;h2&gt;
  
  
  Testing the Auth0 MCP Server Configuration
&lt;/h2&gt;

&lt;p&gt;Once configured and the login is made, you can verify that the server is working properly through the VS Code interface.&lt;/p&gt;

&lt;h3&gt;
  
  
  Checking server status
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Navigate to the Extensions tab&lt;/strong&gt; and look for your Auth0 MCP server in the servers list&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Click on the server entry&lt;/strong&gt; to see its status and available tools&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Check the output logs&lt;/strong&gt; to ensure the server started successfully and connected to Auth0&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Changing the tenant
&lt;/h3&gt;

&lt;p&gt;If you want to change the tenant, run the &lt;code&gt;init&lt;/code&gt; command once again. You’ll go through the authorization flow again and see the page where you can choose a different tenant.&lt;/p&gt;

&lt;h2&gt;
  
  
  Using the Auth0 MCP Server with GitHub Copilot for Analysis
&lt;/h2&gt;

&lt;p&gt;With the server configured, you can now interact with your Auth0 tenant through GitHub Copilot Chat in VS Code. Set the Copilot mode to "&lt;em&gt;Agent&lt;/em&gt;" and start asking questions like the ones below.&lt;/p&gt;

&lt;p&gt;“How many applications do I have in Auth0?”&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fl97ytlfpqa7s511bq2bs.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fl97ytlfpqa7s511bq2bs.png" alt="GitHub Copilot Chat in VS Code displaying a list of applications retrieved via the Auth0 MCP Server."&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;“Give me stats about login activity from the past 1h”&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fimages.ctfassets.net%2F23aumh6u8s0i%2F4AA8PQ9yNPtF7WU79Nanmo%2Fd5fa115a6af07342f6d3861c8bc3888b%2Flogin-stats-with-the-auth-mcp-server.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fimages.ctfassets.net%2F23aumh6u8s0i%2F4AA8PQ9yNPtF7WU79Nanmo%2Fd5fa115a6af07342f6d3861c8bc3888b%2Flogin-stats-with-the-auth-mcp-server.png" alt="GitHub Copilot Chat showing login activity statistics from the last hour, provided by the Auth0 MCP Server"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;“What recent logs do I have?”&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fimages.ctfassets.net%2F23aumh6u8s0i%2F44xQqCyQ5k6OVMnoe7wcWn%2Fd644f20af70c0c9a17bc0296a36e3893%2Frecent-logs-with-the-auth0-mcp-server.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fimages.ctfassets.net%2F23aumh6u8s0i%2F44xQqCyQ5k6OVMnoe7wcWn%2Fd644f20af70c0c9a17bc0296a36e3893%2Frecent-logs-with-the-auth0-mcp-server.png" alt="Recent log data from Auth0 displayed in the GitHub Copilot Chat interface within VS Code"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Keep in mind that if you have other MCP servers that manage logs you might need to be more specific in your question by adding an “Auth0” in it so it knows &lt;em&gt;which&lt;/em&gt; server to use.&lt;/p&gt;

&lt;h2&gt;
  
  
  Next Steps
&lt;/h2&gt;

&lt;p&gt;Setting up the Auth0 MCP server in VS Code creates a seamless bridge between your development environment and your identity infrastructure. Key takeaways include:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Security First&lt;/strong&gt;: Always prioritize secure credential management and follow the principle of least privilege&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Environment Separation&lt;/strong&gt;: Use different configurations for development, staging, and production tenants&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Workflow Integration&lt;/strong&gt;: The real value comes from integrating identity queries into your daily development workflow&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Monitoring and Compliance&lt;/strong&gt;: Maintain awareness of data access patterns and compliance requirements&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Team Collaboration&lt;/strong&gt;: Share common queries and best practices with your development team&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The Auth0 MCP server transforms identity management from a context-switching activity into a natural part of your development conversation. Whether you're troubleshooting user issues, analyzing authentication patterns, or reviewing security configurations, you can now handle these tasks without leaving your IDE.&lt;/p&gt;

&lt;p&gt;This integration represents the future of developer tooling: AI-assisted workflows that bring complex systems closer to the point of development. As MCP adoption grows, we can expect similar integrations across the entire development stack.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Start using the &lt;a href="https://a0.to/auth0mcpserver" rel="noopener noreferrer"&gt;Auth0 MCP Server&lt;/a&gt; and let us know what you think below&lt;/p&gt;
&lt;/blockquote&gt;

</description>
      <category>mcp</category>
      <category>vscode</category>
      <category>ai</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>How to Build a Python MCP Server to Consult a Knowledge Base</title>
      <dc:creator>Jessica Temporal</dc:creator>
      <pubDate>Thu, 28 Aug 2025 13:37:00 +0000</pubDate>
      <link>https://forem.com/auth0/how-to-build-a-python-mcp-server-to-consult-a-knowledge-base-17og</link>
      <guid>https://forem.com/auth0/how-to-build-a-python-mcp-server-to-consult-a-knowledge-base-17og</guid>
      <description>&lt;p&gt;Let's say you have a blog that you want to be able to consult from your AI assistant like Claude, how would you do it? In today's world of AI-powered tools, the easiest way would be to create an MCP (Model Context Protocol) server that can access the blog in some way and find what you are looking for.&lt;/p&gt;

&lt;p&gt;I often use my own blog as a source of information. I think of it as my &lt;em&gt;public field notebook&lt;/em&gt;, the only issue with that is that my blog is not "searchable", it doesn't have a built-in search feature, so I often use the &lt;code&gt;site:jtemporal.com &amp;lt;topic&amp;gt;&lt;/code&gt; Google search trick to find what I'm looking for.&lt;/p&gt;

&lt;p&gt;I have also been using AI assistants like Claude for writing and editing my content, and with the rise of MCP I'd love to search my own content through the chat instead of interrupting my flow to search for information on a separate application.&lt;/p&gt;

&lt;p&gt;In order to make this information available we will build an MCP server in Python and learn how to configure it in Claude Desktop. Let's get started, shall we?&lt;/p&gt;

&lt;h3&gt;
  
  
  How our MCP server works
&lt;/h3&gt;

&lt;p&gt;Our MCP server will run locally and act as a bridge between Claude and the blog content:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fh8w9e8zd062n8840hmw9.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fh8w9e8zd062n8840hmw9.png" alt="Diagram showing the interaction between Claude and the MCP server" width="800" height="491"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This flow shows how Claude can access your blog content using &lt;a href="https://serpapi.com" rel="noopener noreferrer"&gt;SerpAPI&lt;/a&gt; and how it can obtain the content of a given blog post without you ever leaving your conversation by leveraging the MCP server. That means you don't have to switch between interfaces and break your flow.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Is Model Context Protocol (MCP)
&lt;/h2&gt;

&lt;p&gt;The Model Context Protocol (MCP) is an open standard that enables AI applications to securely connect to external data sources and tools. Think of it as a bridge between your AI assistant and various resources. For the GenAI-powered apps using an MCP, it doesn't really matter what the resources are, they could be files, databases, APIs, or in our case, blog posts stored in a GitHub repository.&lt;/p&gt;

&lt;p&gt;MCP allows AI models to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Access external data through standardized interfaces&lt;/li&gt;
&lt;li&gt;Use tools to perform actions on your behalf&lt;/li&gt;
&lt;li&gt;Maintain security boundaries between the AI and your data&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In our case, we'll use MCP to give our AI assistant the ability to search through blog posts, making your personal knowledge base accessible without having to switch contexts.&lt;/p&gt;

&lt;p&gt;To learn about MCP and auth read &lt;a href="https://auth0.com/blog/an-introduction-to-mcp-and-authorization/" rel="noopener noreferrer"&gt;An Introduction to MCP and Authorization&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Model Context Protocol (MCP) SDKs and other libraries
&lt;/h3&gt;

&lt;p&gt;MCP already &lt;a href="https://modelcontextprotocol.io/docs/sdk" rel="noopener noreferrer"&gt;has a few SDKs&lt;/a&gt; you can start using. For this blog post we will use the &lt;a href="https://github.com/modelcontextprotocol/python-sdk" rel="noopener noreferrer"&gt;MCP Python SDK&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;We'll also use the &lt;a href="https://serpapi.com/integrations/python" rel="noopener noreferrer"&gt;SerpApi library&lt;/a&gt; to search for blog posts without needing to manage search infrastructure ourselves. SerpApi provides a simple API for performing site-specific Google searches, which is perfect for finding content on your blog.&lt;/p&gt;

&lt;p&gt;For package management, we'll use &lt;a href="https://docs.astral.sh/uv/" rel="noopener noreferrer"&gt;&lt;code&gt;uv&lt;/code&gt;&lt;/a&gt;, a modern Python package manager that's significantly faster than &lt;code&gt;pip&lt;/code&gt; and handles virtual environments automatically. Using &lt;code&gt;uv&lt;/code&gt; will also make it easier to setup the server within Claude.&lt;/p&gt;

&lt;h2&gt;
  
  
  Setting Up the Application
&lt;/h2&gt;

&lt;p&gt;Let's get our MCP server up and running using &lt;code&gt;uv&lt;/code&gt;:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Install &lt;code&gt;uv&lt;/code&gt;&lt;/strong&gt;: you can learn how to do that on your system on the &lt;a href="https://docs.astral.sh/uv/#installation" rel="noopener noreferrer"&gt;&lt;code&gt;uv&lt;/code&gt; docs&lt;/a&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Clone the repository&lt;/strong&gt;: For this tutorial, you can follow along by cloning &lt;a href="https://a0.to/py-mcp-blog-search" rel="noopener noreferrer"&gt;the repository&lt;/a&gt; which contains the setup for the code we'll build:&lt;br&gt;
&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;   git clone https://github.com/jtemporal/blog-search-mcp-in-python.git
   &lt;span class="nb"&gt;cd &lt;/span&gt;blog-search-mcp-in-python
   git switch base-app
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Initialize the project with &lt;code&gt;uv&lt;/code&gt;&lt;/strong&gt;: This creates a virtual environment and installs dependencies
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;   uv &lt;span class="nb"&gt;sync&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Configure your blog repository settings&lt;/strong&gt; by copying the example configuration file like so:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;   &lt;span class="nb"&gt;cp&lt;/span&gt; .config.example .config
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then edit &lt;code&gt;.config&lt;/code&gt; to match your blog details:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;   &lt;span class="o"&gt;[&lt;/span&gt;MCP_SERVER]
   blog_base_url &lt;span class="o"&gt;=&lt;/span&gt; https://yourblog.com
   serpapi_key &lt;span class="o"&gt;=&lt;/span&gt; your-serpapi-key-here
   server_name &lt;span class="o"&gt;=&lt;/span&gt; Blog Search Server
   log_level &lt;span class="o"&gt;=&lt;/span&gt; INFO
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To get the &lt;a href="https://serpapi.com" rel="noopener noreferrer"&gt;SerpApi key sign up for free here&lt;/a&gt;.&lt;br&gt;
   If you don't have a blog with llms.txt feel free to use mine: &lt;code&gt;jtemporal.com&lt;/code&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Test the setup&lt;/strong&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;   uv run pytest tests/test_config.py &lt;span class="nt"&gt;-v&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;If you set everything up correctly, all 10 tests should pass. This validates that your configuration is loaded properly and the MCP server can access your settings.&lt;/p&gt;

&lt;p&gt;That's it! The code is ready, let's update the MCP server and make sure we can use it in Claude. The &lt;code&gt;uv&lt;/code&gt; approach automatically handles virtual environments and dependency management, making the setup much cleaner and more reliable.&lt;/p&gt;
&lt;h3&gt;
  
  
  Code structure
&lt;/h3&gt;

&lt;p&gt;Our MCP server project has a clean, organized structure like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;mcp-with-python-blog/
├── src/
│   ├── server.py          &lt;span class="c"&gt;# Main MCP server implementation&lt;/span&gt;
│   └── config.py          &lt;span class="c"&gt;# Configuration loader&lt;/span&gt;
├── tests/                 &lt;span class="c"&gt;# All tests&lt;/span&gt;
├── .config                &lt;span class="c"&gt;# Your blog configuration&lt;/span&gt;
├── .config.example        &lt;span class="c"&gt;# Configuration template&lt;/span&gt;
├── pyproject.toml         &lt;span class="c"&gt;# Python project configuration and dependencies&lt;/span&gt;
├── uv.lock                &lt;span class="c"&gt;# Dependency lock file&lt;/span&gt;
└── README.md              &lt;span class="c"&gt;# Development documentation&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note a few important aspects of our codebase structure:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Test Organization&lt;/strong&gt;: The test suite in &lt;code&gt;tests/&lt;/code&gt; holds both &lt;em&gt;unit tests&lt;/em&gt; with mock data and &lt;em&gt;integration tests&lt;/em&gt;. We should run integration tests sparingly as they make real API calls to SerpApi, which count towards your monthly quota and take longer to run.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Configuration Security&lt;/strong&gt;: The &lt;code&gt;.config&lt;/code&gt; file contains sensitive data like your SerpApi key. Keep this file secure and never commit it to version control.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Clean Architecture&lt;/strong&gt;: The &lt;code&gt;src/server.py&lt;/code&gt; file contains the MCP server implementation where we'll define our tools for blog search and content retrieval.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Tool vs. Resources in MCP Servers
&lt;/h2&gt;

&lt;p&gt;If I were to serve this MCP server as part of my website and in the same machine the blog is hosted today, the MCP server would have access to the files used to generate the blog pages. If this were the case we could use a resource like this to retrieve documents:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="nd"&gt;@mcp.resource&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;file://documents/{name}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;read_document&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Read a document by name.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="c1"&gt;# This would normally read from disk
&lt;/span&gt;    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Content of &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;From the MCP documentation:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Resources are how you expose data to LLMs. They're similar to GET endpoints in a REST API - they provide data but shouldn't perform significant computation or have side effects.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;But that's not the case. The blog is a simple &lt;a href="https://jekyllrb.com/" rel="noopener noreferrer"&gt;static generated website using Jekyll&lt;/a&gt;, it is built and served through &lt;a href="https://pages.github.com/" rel="noopener noreferrer"&gt;GitHub Pages&lt;/a&gt;. With that in mind it makes more sense to use &lt;em&gt;tools and leverage tool calling&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Tools are more powerful than resources since you can use them to &lt;em&gt;perform actions&lt;/em&gt; like the one below that sums two numbers:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="nd"&gt;@mcp.tool&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Add two numbers together.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But more than that you can also have tools to search for information in any knowledge base, and in this case a collection of blog posts.&lt;/p&gt;

&lt;p&gt;In more concrete terms &lt;strong&gt;tool calling&lt;/strong&gt;, in the context of GenAI and MCP, allows an AI model to execute specific functions. This is perfect for our use case where we need to search through blog posts based on user questions.&lt;/p&gt;

&lt;p&gt;Read more on &lt;a href="https://auth0.com/blog/genai-tool-calling-intro/" rel="noopener noreferrer"&gt;secure tool calling with Auth for GenAI here&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Creating the MCP Server
&lt;/h2&gt;

&lt;p&gt;Navigate to &lt;code&gt;src/server.py&lt;/code&gt;. You'll see some imports already in place: these are the libraries we'll use to build our MCP server. Now create the MCP server instance by adding this line at the end of the file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;mcp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;FastMCP&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;SERVER_NAME&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;mcp&lt;/code&gt; object is our application instance. We'll use this object to define our tools that Claude can call. Next, we'll implement two tools:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;code&gt;search_posts&lt;/code&gt;: Search blog posts for specific topics or keywords&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;get_post_content&lt;/code&gt;: Retrieve the full content of a specific blog post&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Search posts tool
&lt;/h3&gt;

&lt;p&gt;The first tool we'll implement, searches for blog posts using SerpApi. This tool will leverage Google's search capabilities to find relevant posts on your blog by performing a site-specific search.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Feksm1yct210rdap0wu4x.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Feksm1yct210rdap0wu4x.png" alt="Screenshot of the SerpApi website showing a site-specific Google search for 'site:jtemporal.com pyenv' and the corresponding JSON API response on the right" width="800" height="493"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Add the following code to the end of &lt;code&gt;src/server.py&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="nd"&gt;@mcp.tool&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;search_posts&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;search&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;GoogleSearch&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;q&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;site:&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;BLOG_BASE_URL&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;strip&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;https&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="o"&gt;//&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;api_key&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;SERPAPI_KEY&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="n"&gt;search_result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;search&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get_dict&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;search_result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;search_metadata&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;status&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Success&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;posts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;search_result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;organic_results&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[])&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;posts&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Found &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;posts&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; post(s) matching &lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;:&lt;/span&gt;&lt;span class="se"&gt;\n\n&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
            &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;post&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;posts&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="n"&gt;title&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;post&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;title&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="n"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;post&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;link&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="n"&gt;snippet&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;post&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;snippet&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;**&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;**&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;snippet&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="se"&gt;\n\n&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;No posts found matching &lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let's break down what is happening:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;&lt;code&gt;@mcp.tool()&lt;/code&gt;&lt;/strong&gt;: This decorator registers the function as an MCP tool that your client can call. The decorator automatically makes the function available to the client with proper type information and documentation.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;&lt;code&gt;query&lt;/code&gt; parameter&lt;/strong&gt;: This is the search term that users provide when they want to find blog posts. Claude will automatically extract this from the user's request.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;SerpApi integration&lt;/strong&gt;: We create a &lt;code&gt;GoogleSearch&lt;/code&gt; object that performs a site-specific search using the &lt;code&gt;site:&lt;/code&gt; operator. This limits results to only your blog domain.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Result processing&lt;/strong&gt;: We extract the organic search results and format them into a readable response with titles, URLs, and snippets that give users a preview of each post.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Error handling&lt;/strong&gt;: If no results are found or the API call fails, we return an appropriate message rather than crashing.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;From an user perspective this is what happens when this tool is called:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5euhon7jdmjwkxaewaax.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5euhon7jdmjwkxaewaax.png" alt="Mermaid Sequence Diagram of search_posts tool" width="800" height="491"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Making the MCP Server Runnable
&lt;/h2&gt;

&lt;p&gt;Finally, to make our MCP server runnable we need to add the following at the end of the file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;__name__&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;__main__&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;mcp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;transport&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;stdio&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is the server entrypoint. The &lt;code&gt;transport="stdio"&lt;/code&gt; parameter tells the MCP server to communicate through standard input/output streams. This is the recommended transport method for local MCP servers that integrate with desktop applications like Claude Desktop.&lt;/p&gt;

&lt;p&gt;When Claude Desktop launches your MCP server, it communicates with it through stdin/stdout, making this transport layer perfect for our use case. In future deployments to cloud environments, you might use different transport methods like HTTP.&lt;/p&gt;

&lt;h2&gt;
  
  
  Testing the MCP Server with the MCP Inspector
&lt;/h2&gt;

&lt;p&gt;Now you have a tool to work with. Let's use the MCP Inspector to figure out if everything is running as expected. In your terminal run the following command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx @modelcontextprotocol/inspector uv run src/server.py
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This will use &lt;code&gt;npx&lt;/code&gt; to run the &lt;a href="https://github.com/modelcontextprotocol/inspector" rel="noopener noreferrer"&gt;MCP inspector&lt;/a&gt;, a tool for testing and debugging MCP servers.&lt;/p&gt;

&lt;p&gt;On the left, click &lt;strong&gt;Connect&lt;/strong&gt; so the inspector can connect to your server and then switch to the &lt;strong&gt;Tools&lt;/strong&gt; tab on the right. Then click on &lt;strong&gt;List Tools&lt;/strong&gt;, and you should see something like the image below:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzwdhss0neq8o45ftufw1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzwdhss0neq8o45ftufw1.png" alt="The MCP Inspector interface displaying a successful connection to a local Python MCP server. The 'search_posts' tool is listed in the Tools tab." width="800" height="546"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You can even run a tool from this interface, go on, try it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Get Post Content Tool
&lt;/h2&gt;

&lt;p&gt;Your current tool can get your blog posts, but after finding a blog post you usually want to investigate the content you find interesting, so it is time to implement a second tool that can obtain a post content. This tool will leverage the &lt;code&gt;llms.txt&lt;/code&gt; from the blog.&lt;/p&gt;

&lt;h3&gt;
  
  
  What is llms.txt?
&lt;/h3&gt;

&lt;p&gt;The &lt;code&gt;llms.txt&lt;/code&gt; file is a simple text file that serves as an index of your blog posts in a format optimized for AI consumption. It contains:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A header describing the content&lt;/li&gt;
&lt;li&gt;Other sections about the blog&lt;/li&gt;
&lt;li&gt;Links to raw Markdown files hosted on GitHub&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It is structured in a way that's easy for both humans and AI to parse. Here's what a typical &lt;code&gt;llms.txt&lt;/code&gt; looks like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# LLM Feed for jtemporal.com

## Projects

- [GitFichas](https://gitfichas.com)
...

## All posts

Links to blog posts in Markdown format for easy LLM consumption.

- [How to Handle JWTs in Python](https://raw.githubusercontent.com/jtemporal/jtemporal.github.io/refs/heads/main/_posts/2023-04-06-how-to-handle-jwts-in-python.md)
- [Creating a Travel Diary With Django](https://raw.githubusercontent.com/jtemporal/jtemporal.github.io/refs/heads/main/_posts/2023-03-30-creating-a-travel-diaries-blog-with-django.md)
...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;LLMs prefer Markdown as it is less noisy than an HTML would be, also less expensive to process since you'll have fewer embeddings generated by Markdown than you would by HTML.&lt;/p&gt;

&lt;p&gt;Getting started with GenAI and LLMs? Check out &lt;a href="https://auth0.com/blog/rag-and-access-control-where-do-you-start/" rel="noopener noreferrer"&gt;RAG and Access Control: Where do you start?&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Add the following code after the first tool and before the &lt;code&gt;if __name__ == "__main__":&lt;/code&gt; clause in &lt;code&gt;src/server.py&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get_post_content&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="c1"&gt;# Fetch the llms.txt content from the blog
&lt;/span&gt;        &lt;span class="n"&gt;llm_url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;BLOG_BASE_URL&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;/llms.txt&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
        &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;llm_url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;timeout&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;raise_for_status&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

        &lt;span class="c1"&gt;# Parse the llm.txt content to find the post by title
&lt;/span&gt;        &lt;span class="n"&gt;content&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;
        &lt;span class="n"&gt;lines&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="c1"&gt;# Find the "## All posts" section and extract posts from there
&lt;/span&gt;        &lt;span class="n"&gt;raw_url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;
        &lt;span class="n"&gt;in_all_posts_section&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;

        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;line&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;lines&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="c1"&gt;# Check if we've reached the "## All posts" section
&lt;/span&gt;            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;line&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;strip&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;## All posts&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="n"&gt;in_all_posts_section&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;
                &lt;span class="k"&gt;continue&lt;/span&gt;

            &lt;span class="c1"&gt;# Only search for posts within the "All posts" section
&lt;/span&gt;            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;in_all_posts_section&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;title&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;line&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="c1"&gt;# Extract URL from markdown link format: [title](url)
&lt;/span&gt;                &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;](https://&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;line&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                    &lt;span class="n"&gt;raw_url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;line&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;](&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;strip&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;)&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                    &lt;span class="k"&gt;break&lt;/span&gt;

        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;raw_url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Post with title &lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt; not found in llm.txt&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

        &lt;span class="c1"&gt;# Fetch the raw markdown content from GitHub
&lt;/span&gt;        &lt;span class="n"&gt;content_response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;raw_url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;timeout&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;content_response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;raise_for_status&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;content_response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;

    &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;RequestException&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Error fetching content: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="nb"&gt;Exception&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Error processing content: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let's break down what is happening:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;&lt;code&gt;@mcp.tool()&lt;/code&gt;&lt;/strong&gt;: Once again, this decorator registers the function as an MCP tool that Claude can invoke when users want to read full blog post content.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;&lt;code&gt;title&lt;/code&gt; parameter&lt;/strong&gt;: This is the blog post title that users want to retrieve. Claude will extract this from the user's request or from search results.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;llms.txt integration&lt;/strong&gt;: We fetch the &lt;code&gt;llms.txt&lt;/code&gt; file from your blog, which serves as an index of all your posts with links to their raw Markdown content on GitHub.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Title matching&lt;/strong&gt;: We search through the llms.txt index to find a post that contains the requested title in the "All posts" section. This allows for partial matches, making it easier for users to find posts.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Content retrieval&lt;/strong&gt;: Once we find the matching post, we extract the GitHub raw URL and fetch the full Markdown content directly from GitHub.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Error handling&lt;/strong&gt;: We handle various error cases like network issues, missing posts, or malformed index files gracefully.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;When this tool is called this is what happens:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzjijd6ynwb0nmd4nx2pw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzjijd6ynwb0nmd4nx2pw.png" alt="Mermaid Sequence Diagram of get_post_content tool" width="800" height="491"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Setting Up the MCP Server in Claude Desktop
&lt;/h2&gt;

&lt;p&gt;Now that we have a working MCP server with two tools, let's configure it in Claude Desktop so you can use it for searching your blog posts during your conversations.&lt;/p&gt;

&lt;p&gt;Before setting up the server in Claude Desktop, make sure you have:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Completed the server setup&lt;/strong&gt; from the previous sections (including &lt;code&gt;uv&lt;/code&gt; installation)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A working SerpApi key&lt;/strong&gt; in your &lt;code&gt;.config&lt;/code&gt; file&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Claude Desktop app&lt;/strong&gt; installed on your computer&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Configuring Claude Desktop
&lt;/h3&gt;

&lt;p&gt;Claude Desktop uses a &lt;code&gt;claude_desktop_config.json&lt;/code&gt; file for MCP server configuration. Since we used the MCP SDK there's a easy way to configure our server in Claude, use the following command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;uv run mcp &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="s2"&gt;"src/server.py"&lt;/span&gt; &lt;span class="nt"&gt;--with&lt;/span&gt; google-search-results
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This will create the configuration file for you. Now restart Claude and check in the preferences if the server is running: Open the Settings, then access the Developer section. If your server is running as expected you should see something like the image below:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvbvpttj7zzaeql6y7u05.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvbvpttj7zzaeql6y7u05.png" alt="The Developer settings section within the Claude Desktop app, showing the 'Blog Search Server' is successfully configured and has a 'running' status." width="800" height="852"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Testing the search blog tool in Claude
&lt;/h3&gt;

&lt;p&gt;Now &lt;strong&gt;open a new conversation&lt;/strong&gt; and ask something like: &lt;em&gt;"Can search my blog for posts on pyenv?"&lt;/em&gt; Claude should identify your intent, recognize &lt;code&gt;pyenv&lt;/code&gt; as the query for the &lt;code&gt;search_posts&lt;/code&gt; tool, and offer to use it like so:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1ai2q5tb347222dfadkc.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1ai2q5tb347222dfadkc.png" alt="A user in Claude asking to search for 'pyenv', and the Claude interface showing a confirmation dialog titled 'Claude would like to use this tool' for the search_posts action" width="800" height="855"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;After a second you should see a result like this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqahzv5lz9znwagyzwunt.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqahzv5lz9znwagyzwunt.png" alt="The Claude chat interface displaying formatted search results after running the search_posts tool, listing several blog posts related to 'pyenv'." width="800" height="855"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This means that Claude parsed the result of the search into a readable result for the chat for you.&lt;/p&gt;

&lt;h3&gt;
  
  
  Testing the post content tool in Claude
&lt;/h3&gt;

&lt;p&gt;In the same conversation ask something like: &lt;em&gt;"Can you get the content for the first post?"&lt;/em&gt; This time Claude should identify the &lt;code&gt;get_post_content&lt;/code&gt; tool as well as post title and ofer to use the tool passing along the query like so:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9nopv73ghd2psc0t4cug.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9nopv73ghd2psc0t4cug.png" alt="The Claude interface showing a confirmation dialog to use the get_post_content tool after the user asked to retrieve the content of a specific post" width="800" height="855"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;After a second you should see a result like this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnapifpspkr6xcn5dpgft.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnapifpspkr6xcn5dpgft.png" alt="Claude displaying a summarized version of a blog post's content, including key benefits and main commands, after successfully using the get_post_content tool" width="800" height="852"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This means that Claude parsed the result of the blog post to show it to you. Keep in mind that this is not a copy of the content on the page but what Claude understood from the blog post.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Tip:&lt;/em&gt; If you want you can also configure the server in Cursor, we left instructions in the &lt;a href="https://github.com/jtemporal/blog-search-mcp-in-python/tree/main?tab=readme-ov-file#cursor-desktop-setup" rel="noopener noreferrer"&gt;repository's &lt;code&gt;README.md&lt;/code&gt;&lt;/a&gt; on how to do it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Recap and Future Steps
&lt;/h2&gt;

&lt;p&gt;You've successfully built a complete MCP server that can search through your blog posts using Python! Here's what we accomplished:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Created a functional MCP server&lt;/strong&gt; with two tools:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;search_posts&lt;/code&gt;: Site-specific search using SerpApi
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;get_post_content&lt;/code&gt;: Full content retrieval via llms.txt indexing&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Integrated with modern APIs&lt;/strong&gt; using SerpApi for reliable search functionality&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Implemented content indexing&lt;/strong&gt; using the llms.txt standard for AI-optimized content access&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Added comprehensive error handling&lt;/strong&gt; and logging for robust operation&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Configured Claude Desktop integration&lt;/strong&gt; for seamless AI-powered blog search&lt;/p&gt;

&lt;h3&gt;
  
  
  Key benefits
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Simple local server&lt;/strong&gt;: SerpApi and llms.txt require minimal setup and you can run it locally&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Reliable search&lt;/strong&gt;: Leverages Google's search quality for finding relevant content&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Fast content access&lt;/strong&gt;: Direct GitHub raw content fetching bypasses the GitHub API rate limits&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;AI-optimized&lt;/strong&gt;: llms.txt format is specifically designed for LLM consumption&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  What's next?
&lt;/h3&gt;

&lt;p&gt;The MCP ecosystem is rapidly growing, and your blog search server is just the beginning. In a future blog post we will cover how to protect your MCP server to avoid abuse and how to deploy it to Claude so you can access it anywhere without having to have the code locally.&lt;/p&gt;

&lt;p&gt;The complete code for this blog post is in &lt;a href="https://a0.to/py-mcp-blog-search" rel="noopener noreferrer"&gt;this repository&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;If you are building GenAI applications make sure you are protecting your user's data and giving the right level of access to your LLMs with &lt;a href="https://a0.to/ai-content" rel="noopener noreferrer"&gt;Auth for GenAI&lt;/a&gt;. &lt;a href="https://a0.to/ai-content" rel="noopener noreferrer"&gt;Sign up for the Auth for GenAI Developer Preview program&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;We've been writing a lot on GenAI applications and tool calling, check out the related blog posts section below to continue learning and keep an eye out here or on our &lt;a href="https://community.auth0.com/c/blog-discuss/56" rel="noopener noreferrer"&gt;Community Forum&lt;/a&gt; to learn when the next part of this series go live.&lt;/p&gt;

&lt;h3&gt;
  
  
  Related Posts
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://auth0.com/blog/an-introduction-to-mcp-and-authorization/" rel="noopener noreferrer"&gt;An Introduction to MCP and Authorization&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://auth0.com/blog/mcp-vs-api/" rel="noopener noreferrer"&gt;Why Can't I Just Use an API? Because Your AI Agent Needs MCP&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://auth0.com/blog/announcement-auth0-mcp-server-is-here/" rel="noopener noreferrer"&gt;The Auth0 MCP Server is here!&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>mcp</category>
      <category>python</category>
      <category>ai</category>
      <category>tutorial</category>
    </item>
  </channel>
</rss>
