<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Authentication on lo0 — Tech Blog</title><link>https://blog.lo0.es/en/tags/authentication/</link><description>Recent content in Authentication on lo0 — Tech Blog</description><generator>Hugo -- gohugo.io</generator><language>en</language><lastBuildDate>Thu, 18 Jun 2026 08:30:00 +0200</lastBuildDate><atom:link href="https://blog.lo0.es/en/tags/authentication/index.xml" rel="self" type="application/rss+xml"/><item><title>When MCP grows up: giving it authentication with Keycloak</title><link>https://blog.lo0.es/en/posts/mcp-grows-up-authentication-keycloak/</link><pubDate>Thu, 18 Jun 2026 08:30:00 +0200</pubDate><guid>https://blog.lo0.es/en/posts/mcp-grows-up-authentication-keycloak/</guid><description>&lt;p>When a child is small, it is enough to make sure they do not hurt themselves. You put a gate on the stairs, you cover the sockets and little else. But the child grows. They start going out alone, handling money, making decisions that have consequences. And then raising them stops being &amp;ldquo;don&amp;rsquo;t let them fall&amp;rdquo; and turns into something far more demanding: teaching them to identify themselves, to ask permission, to know how far they can go and to account for what they do.&lt;/p>
&lt;p>That is exactly what has happened with our MCP servers.&lt;/p>
&lt;h2 id="mcp-is-no-longer-a-toy">MCP is no longer a toy&lt;/h2>
&lt;p>Not long ago, a &lt;a href="https://modelcontextprotocol.io/">Model Context Protocol&lt;/a> server was an experiment running on someone&amp;rsquo;s laptop so a model could read a couple of files or call an internal API. It did not matter who used it: it was us, locally, with nothing at stake.&lt;/p>
&lt;p>That is over. MCP servers have become infrastructure pieces that AI uses to touch real systems. And, like any child who grows up, they have developed new needs they did not have before:&lt;/p>
&lt;ul>
&lt;li>&lt;strong>Multitenancy&lt;/strong>: there is no longer a single user but many, and one user&amp;rsquo;s data cannot be visible to another.&lt;/li>
&lt;li>&lt;strong>Cost control&lt;/strong>: every model call consumes tokens, compute and, sometimes, paid APIs. You have to measure, attribute and set limits.&lt;/li>
&lt;li>&lt;strong>Authentication and authorisation&lt;/strong>: knowing who is on the other side, what permissions they hold and which specific server their credential is good for.&lt;/li>
&lt;/ul>
&lt;p>These are three separate fronts. Today we focus on the third, which is the one that opens the door to the other two: &lt;strong>authentication&lt;/strong>. Without knowing who is calling, there is no tenant to separate and no cost to attribute.&lt;/p>
&lt;h2 id="why-mcp-authentication-is-a-problem-with-a-name-of-its-own">Why MCP authentication is a problem with a name of its own&lt;/h2>
&lt;p>The good news is that nothing has to be invented here. The MCP specification did not pull a security mechanism of its own out of a hat: it builds on &lt;strong>OAuth&lt;/strong>, the same standard that has been behind half the internet&amp;rsquo;s &amp;ldquo;Sign in with…&amp;rdquo; for years. MCP defines how an MCP server acts as a &lt;em>protected resource&lt;/em> and delegates credential issuance to an &lt;em>authorisation server&lt;/em>.&lt;/p>
&lt;p>And that authorisation server can be &lt;a href="https://www.keycloak.org/">Keycloak&lt;/a>, the open source identity manager from the Cloud Native Computing Foundation that you probably already run for the rest of your applications. The idea is not to add one more identity silo just because we are now talking to models: reuse the one that already governs your users.&lt;/p>
&lt;p>The important nuance is that &lt;strong>MCP is not a single frozen standard&lt;/strong> but a living specification with several versions, and each one asks for different things. As of today four coexist:&lt;/p>
&lt;ul>
&lt;li>&lt;code>2025-11-25&lt;/code> (the latest)&lt;/li>
&lt;li>&lt;code>2025-06-18&lt;/code>&lt;/li>
&lt;li>&lt;code>2025-03-26&lt;/code>&lt;/li>
&lt;li>&lt;code>2024-11-05&lt;/code> (the initial one, which did not even contemplate authorisation)&lt;/li>
&lt;/ul>
&lt;p>Just as with raising a child what they need at 6 is not what they need at 16, what Keycloak has to satisfy depends on the MCP version you work with. Let us look at it.&lt;/p>
&lt;h2 id="what-mcp-demands-from-an-authorisation-server-and-what-keycloak-provides">What MCP demands from an authorisation server (and what Keycloak provides)&lt;/h2>
&lt;p>The specification lists a series of OAuth standards the authorisation server must support, with different levels of requirement (&lt;code>MUST&lt;/code>, &lt;code>SHOULD&lt;/code>, &lt;code>MAY&lt;/code>). This is the picture, cross-referenced with what Keycloak supports:&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Standard&lt;/th>
&lt;th>2025-11-25&lt;/th>
&lt;th>2025-06-18&lt;/th>
&lt;th>2025-03-26&lt;/th>
&lt;th>Keycloak&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>OAuth 2.1 Authorization Framework (draft)&lt;/td>
&lt;td>MUST&lt;/td>
&lt;td>MUST&lt;/td>
&lt;td>MUST&lt;/td>
&lt;td>Supported&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>OAuth 2.0 Authorization Server Metadata (RFC 8414)&lt;/td>
&lt;td>MUST&lt;/td>
&lt;td>MUST&lt;/td>
&lt;td>MUST&lt;/td>
&lt;td>Supported&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Resource Indicators for OAuth 2.0 (RFC 8707)&lt;/td>
&lt;td>MUST&lt;/td>
&lt;td>MUST&lt;/td>
&lt;td>—&lt;/td>
&lt;td>&lt;strong>Not supported&lt;/strong>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Dynamic Client Registration (RFC 7591)&lt;/td>
&lt;td>MAY&lt;/td>
&lt;td>SHOULD&lt;/td>
&lt;td>SHOULD&lt;/td>
&lt;td>Supported&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>OAuth Client ID Metadata Document (draft)&lt;/td>
&lt;td>SHOULD&lt;/td>
&lt;td>—&lt;/td>
&lt;td>—&lt;/td>
&lt;td>Supported (experimental)&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>One clarification: MCP also adopts &lt;em>OAuth 2.0 Protected Resource Metadata&lt;/em> (RFC 9728), but that standard is the &lt;strong>MCP server&amp;rsquo;s&lt;/strong> business, not the authorisation server&amp;rsquo;s, so it does not appear in the Keycloak table.&lt;/p>
&lt;p>If we take as our conformance criterion that Keycloak &amp;ldquo;supports&amp;rdquo; a version when it meets all its &lt;code>MUST&lt;/code> and &lt;code>SHOULD&lt;/code> requirements, the result is this:&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>MCP version&lt;/th>
&lt;th>Conformance&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>&lt;code>2025-03-26&lt;/code>&lt;/td>
&lt;td>&lt;strong>Supported&lt;/strong>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>2025-06-18&lt;/code>&lt;/td>
&lt;td>Partial, without Resource Indicators (RFC 8707)&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>2025-11-25&lt;/code>&lt;/td>
&lt;td>Partial, without Resource Indicators (RFC 8707)&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>The only piece Keycloak lacks today is &lt;strong>Resource Indicators for OAuth 2.0 (RFC 8707)&lt;/strong>, the &lt;code>resource&lt;/code> parameter that ties a token to a specific server. The Keycloak community already has support for it on the roadmap; in the meantime there is a perfectly valid workaround we look at below. Following the analogy, it is that subject the child still struggles with but can pass with a study trick until they fully mature.&lt;/p>
&lt;h2 id="down-to-work-setting-up-keycloak-for-your-mcp-version">Down to work: setting up Keycloak for your MCP version&lt;/h2>
&lt;h3 id="for-mcp-2025-03-26">For MCP 2025-03-26&lt;/h3>
&lt;p>Nothing special has to be configured. This version does not require the &lt;code>resource&lt;/code> parameter, so Keycloak complies out of the box. The child is still small and the gate on the stairs is enough.&lt;/p>
&lt;h3 id="for-mcp-2025-06-18-and-2025-11-25-tying-the-token-to-its-audience">For MCP 2025-06-18 and 2025-11-25: tying the token to its audience&lt;/h3>
&lt;p>Here is where it gets interesting. For security, these versions require an access token to be &lt;strong>bound to the audience&lt;/strong> it was issued for. In plain terms:&lt;/p>
&lt;ul>
&lt;li>The MCP client &lt;strong>must&lt;/strong> include the &lt;code>resource&lt;/code> parameter (from RFC 8707) in the authorisation and token requests, with the value that identifies the MCP server it is going to use.&lt;/li>
&lt;li>The MCP server &lt;strong>must&lt;/strong> validate that the tokens it receives were issued specifically for it.&lt;/li>
&lt;/ul>
&lt;p>This prevents a stolen or reused token from getting into a server other than the intended one. The problem, as we said, is that &lt;strong>Keycloak does not yet understand the &lt;code>resource&lt;/code> parameter&lt;/strong>.&lt;/p>
&lt;p>The solution until native RFC 8707 support arrives is to use OAuth&amp;rsquo;s &lt;strong>&lt;code>scope&lt;/code>&lt;/strong> parameter to achieve the same effect. Picture this situation:&lt;/p>
&lt;ul>
&lt;li>The MCP server is at &lt;code>https://example.com/mcp&lt;/code>.&lt;/li>
&lt;li>It supports three scopes: &lt;code>mcp:tools&lt;/code>, &lt;code>mcp:prompts&lt;/code> and &lt;code>mcp:resources&lt;/code>.&lt;/li>
&lt;li>The client asks for a token with &lt;code>resource = https://example.com/mcp&lt;/code> and a combination of those scopes.&lt;/li>
&lt;li>We want Keycloak to issue a token whose &lt;code>aud&lt;/code> (audience) is precisely &lt;code>https://example.com/mcp&lt;/code>.&lt;/li>
&lt;/ul>
&lt;p>To achieve it, we configure Keycloak like this, repeating the pattern for each scope:&lt;/p>
&lt;ul>
&lt;li>Create a &lt;strong>client scope&lt;/strong> &lt;code>mcp:tools&lt;/code> of type &lt;em>Optional&lt;/em> and add an &lt;strong>Audience mapper&lt;/strong> whose &lt;em>Included Custom Audience&lt;/em> is &lt;code>https://example.com/mcp&lt;/code>.&lt;/li>
&lt;li>Create a &lt;strong>client scope&lt;/strong> &lt;code>mcp:prompts&lt;/code> of type &lt;em>Optional&lt;/em> with its &lt;strong>Audience mapper&lt;/strong> pointing at &lt;code>https://example.com/mcp&lt;/code>.&lt;/li>
&lt;li>Create a &lt;strong>client scope&lt;/strong> &lt;code>mcp:resources&lt;/code> of type &lt;em>Optional&lt;/em> with its &lt;strong>Audience mapper&lt;/strong> pointing at &lt;code>https://example.com/mcp&lt;/code>.&lt;/li>
&lt;/ul>
&lt;p>The key is that the &lt;em>Included Custom Audience&lt;/em> of each client scope is &lt;strong>identical&lt;/strong> to the value of the request&amp;rsquo;s &lt;code>resource&lt;/code> parameter and to the MCP server URL. With this, if the client asks for a token with those three scopes, Keycloak issues something like:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-json" data-lang="json">&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;aud&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;https://example.com/mcp&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;scope&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;mcp:resources mcp:tools mcp:prompts&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>The MCP server can now check the &lt;code>aud&lt;/code> and reject any token that was not meant for it. We have tied the credential to its recipient without waiting for RFC 8707.&lt;/p>
&lt;h3 id="if-you-use-mcp-inspector">If you use MCP Inspector&lt;/h3>
&lt;p>&lt;a href="https://github.com/modelcontextprotocol/inspector">MCP Inspector&lt;/a> is the official tool for debugging MCP servers, and it registers clients dynamically against Keycloak by running JavaScript from its backend. For it to work you have to adjust &lt;strong>CORS&lt;/strong> in the client registration &lt;em>anonymous access policies&lt;/em>:&lt;/p>
&lt;ul>
&lt;li>&lt;strong>Allowed Client Scopes&lt;/strong>: include the scopes your MCP server supports.&lt;/li>
&lt;li>&lt;strong>Allowed Registration Web Origins&lt;/strong>: include the web origin of the MCP Inspector backend.&lt;/li>
&lt;li>&lt;strong>Trusted Hosts&lt;/strong>: include the host or IP of the machine sending the dynamic registration request, that is, where your browser runs.&lt;/li>
&lt;/ul>
&lt;h3 id="for-mcp-2025-11-25-client-registration-with-client-id-metadata-document">For MCP 2025-11-25: client registration with Client ID Metadata Document&lt;/h3>
&lt;p>The latest version adds a new chapter: how clients register. It contemplates three approaches, and you choose according to your scenario:&lt;/p>
&lt;ul>
&lt;li>&lt;strong>Client ID Metadata Documents&lt;/strong>: when client and server do not know each other beforehand (the most common case).&lt;/li>
&lt;li>&lt;strong>Pre-registration&lt;/strong>: when a prior relationship already exists.&lt;/li>
&lt;li>&lt;strong>Dynamic Client Registration&lt;/strong>: for backward compatibility or specific requirements.&lt;/li>
&lt;/ul>
&lt;p>Keycloak supports the first, &lt;strong>OAuth Client ID Metadata Document (CIMD)&lt;/strong>, although it is worth knowing that it is an &lt;strong>experimental feature&lt;/strong>: it may bring breaking changes in future versions. To enable it, start Keycloak with the flag:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">bin/kc.sh start --features&lt;span class="o">=&lt;/span>cimd
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>The idea behind CIMD is that the &lt;code>client_id&lt;/code> stops being an opaque identifier and becomes &lt;strong>a URL&lt;/strong> pointing at a document with the client&amp;rsquo;s metadata. Keycloak downloads that document and processes the request with what it finds there. For it to do so, you have to create a &lt;em>client policy profile&lt;/em> and a &lt;em>policy&lt;/em> that trigger it.&lt;/p>
&lt;p>&lt;strong>Profile.&lt;/strong> In &lt;em>Realm Settings → Client Policies → Profiles&lt;/em>, create a profile (e.g. &lt;code>cimd-profile&lt;/code>), add the &lt;code>client-id-metadata-document&lt;/code> executor and configure it:&lt;/p>
&lt;ul>
&lt;li>&lt;strong>Allow http scheme&lt;/strong>: allows &lt;code>http&lt;/code> for the URLs (client_id, &lt;code>client_uri&lt;/code>, &lt;code>logo_uri&lt;/code>, &lt;code>jwks_uri&lt;/code>, etc.). Development only; in production &lt;strong>OFF&lt;/strong>.&lt;/li>
&lt;li>&lt;strong>Trusted domains&lt;/strong>: wildcard patterns of accepted domains (e.g. &lt;code>*.example.org&lt;/code>). If empty, all are denied.&lt;/li>
&lt;li>&lt;strong>Restrict same domain&lt;/strong>: if ON, it requires the &lt;code>client_id&lt;/code> URL, the &lt;code>redirect_uri&lt;/code> and the metadata URLs to all be under the same trusted domain.&lt;/li>
&lt;li>&lt;strong>Required properties&lt;/strong>: properties the metadata document must include.&lt;/li>
&lt;li>&lt;strong>Only Allow Confidential Client&lt;/strong>: if ON, only confidential clients are accepted (with &lt;code>jwks&lt;/code>/&lt;code>jwks_uri&lt;/code> and &lt;code>private_key_jwt&lt;/code> or &lt;code>tls_client_auth&lt;/code> authentication).&lt;/li>
&lt;/ul>
&lt;p>&lt;strong>Policy.&lt;/strong> In &lt;em>Realm Settings → Client Policies → Policies&lt;/em>, create a policy (e.g. &lt;code>cimd-policy&lt;/code>), add the &lt;code>client-id-uri&lt;/code> condition and configure it:&lt;/p>
&lt;ul>
&lt;li>&lt;strong>URI scheme&lt;/strong>: schemes to recognise in the &lt;code>client_id&lt;/code> (in production, &lt;code>https&lt;/code> only).&lt;/li>
&lt;li>&lt;strong>Trusted domains&lt;/strong>: domains accepted in the &lt;code>client_id&lt;/code> host. If filled in, the condition is only true when the host matches; if left empty, it is always false.&lt;/li>
&lt;/ul>
&lt;p>Then associate the &lt;code>cimd-profile&lt;/code> with this policy. From then on, when a request arrives with a &lt;code>client_id&lt;/code> that is an &lt;code>https&lt;/code> URL from a trusted domain, Keycloak downloads the metadata document and uses it to process the request.&lt;/p>
&lt;p>&lt;strong>Global executor settings.&lt;/strong> Some parameters are not in the console and are passed as SPI options at startup:&lt;/p>
&lt;ul>
&lt;li>&lt;code>min-cache-time&lt;/code>: minimum document cache time (300 s by default).&lt;/li>
&lt;li>&lt;code>max-cache-time&lt;/code>: maximum cache time (259200 s by default, 3 days).&lt;/li>
&lt;li>&lt;code>upper-limit-metadata-bytes&lt;/code>: maximum document size (5000 bytes by default).&lt;/li>
&lt;/ul>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">bin/kc.sh start &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="se">&lt;/span> --spi-client-policy-executor--client-id-metadata-document--min-cache-time&lt;span class="o">=&lt;/span>&lt;span class="m">600&lt;/span> &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="se">&lt;/span> --spi-client-policy-executor--client-id-metadata-document--max-cache-time&lt;span class="o">=&lt;/span>&lt;span class="m">86400&lt;/span> &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="se">&lt;/span> --spi-client-policy-executor--client-id-metadata-document--upper-limit-metadata-bytes&lt;span class="o">=&lt;/span>&lt;span class="m">10000&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="a-real-case-vs-code-desktop-as-an-mcp-client">A real case: VS Code desktop as an MCP client&lt;/h3>
&lt;p>Visual Studio Code is an MCP client that uses CIMD. When it connects to an MCP server that requires authorisation, it sends a &lt;code>client_id&lt;/code> that is an &lt;code>https&lt;/code> URL hosted on &lt;code>vscode.dev&lt;/code> (e.g. &lt;code>https://vscode.dev/mcp-client&lt;/code>), and Keycloak downloads its metadata from there. The detail to bear in mind is that VS Code is a &lt;strong>public client&lt;/strong> using &lt;strong>PKCE&lt;/strong> (no client secret) and, for the OAuth callback, it starts a local server with a &lt;code>redirect_uri&lt;/code> of the form &lt;code>http://127.0.0.1:&amp;lt;port&amp;gt;/callback&lt;/code>. Since that redirect is not in the &lt;code>vscode.dev&lt;/code> domain, you have to leave &lt;strong>Restrict same domain OFF&lt;/strong>.&lt;/p>
&lt;p>Start with &lt;code>--features=cimd&lt;/code> and configure:&lt;/p>
&lt;p>&lt;strong>Profile &lt;code>vscode-cimd-profile&lt;/code>&lt;/strong> (&lt;code>client-id-metadata-document&lt;/code> executor):&lt;/p>
&lt;ul>
&lt;li>Allow http scheme: &lt;strong>OFF&lt;/strong>&lt;/li>
&lt;li>Trusted domains: &lt;code>vscode.dev&lt;/code>, &lt;code>127.0.0.1&lt;/code>&lt;/li>
&lt;li>Restrict same domain: &lt;strong>OFF&lt;/strong> (the redirect is a localhost, not &lt;code>vscode.dev&lt;/code>)&lt;/li>
&lt;li>Only Allow Confidential Client: &lt;strong>OFF&lt;/strong> (VS Code is public)&lt;/li>
&lt;/ul>
&lt;p>&lt;strong>Policy &lt;code>vscode-cimd-policy&lt;/code>&lt;/strong> (&lt;code>client-id-uri&lt;/code> condition):&lt;/p>
&lt;ul>
&lt;li>URI scheme: &lt;code>https&lt;/code>&lt;/li>
&lt;li>Trusted domains: &lt;code>vscode.dev&lt;/code>&lt;/li>
&lt;li>Associate the &lt;code>vscode-cimd-profile&lt;/code> profile.&lt;/li>
&lt;/ul>
&lt;p>With this, when VS Code launches the request, Keycloak recognises the &lt;code>client_id&lt;/code> as a &lt;code>vscode.dev&lt;/code> URL, downloads the Client ID Metadata Document and completes the OAuth flow with the callback on localhost.&lt;/p>
&lt;h2 id="the-education-continues">The education continues&lt;/h2>
&lt;p>We have taught our MCP to identify itself and to ask permission in a standard way, reusing the identity Keycloak already governs in the rest of the house. That is no small thing: with a token properly tied to its audience, everything else starts to become possible.&lt;/p>
&lt;p>But, as with any child who grows up, this does not end here. The other two subjects we mentioned at the start are still pending, &lt;strong>separating the tenants (multitenancy)&lt;/strong> and &lt;strong>controlling what it spends (costs)&lt;/strong>, and Keycloak still has some maturing to do in places, such as native Resource Indicators support. We will cover them in future posts. For now, our MCP already knows how to say who it is. And in security, that is the first day of school.&lt;/p>
&lt;h2 id="see-also">See also&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://blog.lo0.es/en/posts/litellm-mcp-gateway-second-front-door/">LiteLLM&amp;rsquo;s MCP gateway&lt;/a> — the layer that sits in front of these servers when there are several: permissions, cost, audit and the state of the specification in September 2026.&lt;/li>
&lt;li>&lt;a href="https://blog.lo0.es/en/posts/keycloak-identity-plane-ai-platform/">Keycloak in an AI platform&lt;/a> — the piece from the inside and its position in the architecture, with the conformance status against MCP revision 2026-07-28.&lt;/li>
&lt;li>&lt;a href="https://blog.lo0.es/en/posts/completing-keycloak-for-mcp-protected-resource/">Completing Keycloak for MCP&lt;/a> — what this post&amp;rsquo;s setup is missing under revision 2026-07-28: protected resource metadata, audience validation and the end of dynamic registration.&lt;/li>
&lt;/ul>
&lt;h2 id="sources">Sources&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://www.keycloak.org/securing-apps/mcp-authz-server">Keycloak — Integrating with Model Context Protocol (MCP)&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://modelcontextprotocol.io/specification/2025-06-18/basic/authorization">Model Context Protocol — Authorization&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://www.rfc-editor.org/rfc/rfc8707">RFC 8707 — Resource Indicators for OAuth 2.0&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://www.rfc-editor.org/rfc/rfc8414">RFC 8414 — OAuth 2.0 Authorization Server Metadata&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://www.rfc-editor.org/rfc/rfc7591">RFC 7591 — OAuth 2.0 Dynamic Client Registration Protocol&lt;/a>&lt;/li>
&lt;/ul></description></item></channel></rss>