<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>OAuth on lo0 — Blog Técnico</title><link>https://blog.lo0.es/tags/oauth/</link><description>Recent content in OAuth on lo0 — Blog Técnico</description><generator>Hugo -- gohugo.io</generator><language>es</language><lastBuildDate>Thu, 18 Jun 2026 08:30:00 +0200</lastBuildDate><atom:link href="https://blog.lo0.es/tags/oauth/index.xml" rel="self" type="application/rss+xml"/><item><title>Cuando el MCP crece: ponerle autenticación con Keycloak</title><link>https://blog.lo0.es/posts/mcp-crece-autenticacion-keycloak/</link><pubDate>Thu, 18 Jun 2026 08:30:00 +0200</pubDate><guid>https://blog.lo0.es/posts/mcp-crece-autenticacion-keycloak/</guid><description>&lt;p>Cuando un hijo es pequeño, basta con vigilar que no se haga daño. Le pones una valla en la escalera, tapas los enchufes y poco más. Pero el niño crece. Empieza a salir solo, a manejar dinero, a tomar decisiones que tienen consecuencias. Y entonces la educación deja de ser «que no se caiga» para convertirse en algo mucho más exigente: enseñarle a identificarse, a pedir permiso, a saber hasta dónde puede llegar y a rendir cuentas de lo que hace.&lt;/p>
&lt;p>Con nuestros servidores MCP nos ha pasado exactamente eso.&lt;/p>
&lt;h2 id="el-mcp-ya-no-es-un-juguete">El MCP ya no es un juguete&lt;/h2>
&lt;p>Hace nada, un servidor &lt;a href="https://modelcontextprotocol.io/">Model Context Protocol&lt;/a> era un experimento que corría en el portátil de alguien para que un modelo pudiera leer un par de ficheros o llamar a una API interna. Daba igual quién lo usara: éramos nosotros, en local, sin nada en juego.&lt;/p>
&lt;p>Eso se acabó. Los MCP se han vuelto piezas de infraestructura que la IA usa para tocar sistemas de verdad. Y, como cualquier hijo que crece, han desarrollado necesidades nuevas que antes no tenían:&lt;/p>
&lt;ul>
&lt;li>&lt;strong>Multitenancy&lt;/strong>: ya no hay un único usuario, sino muchos, y lo de uno no puede ser visible para otro.&lt;/li>
&lt;li>&lt;strong>Control de costes&lt;/strong>: cada llamada del modelo consume tokens, cómputo y, a veces, APIs de pago. Hay que medir, atribuir y poner límites.&lt;/li>
&lt;li>&lt;strong>Autenticación y autorización&lt;/strong>: saber quién está al otro lado, qué permisos tiene y para qué servidor concreto vale su credencial.&lt;/li>
&lt;/ul>
&lt;p>Son tres frentes distintos. Hoy nos centramos en el tercero, que es el que abre la puerta a los otros dos: &lt;strong>la autenticación&lt;/strong>. Sin saber quién llama, no hay tenant que separar ni coste que atribuir.&lt;/p>
&lt;h2 id="por-qué-la-autenticación-del-mcp-es-un-problema-con-nombre-propio">Por qué la autenticación del MCP es un problema con nombre propio&lt;/h2>
&lt;p>La buena noticia es que aquí no hay que inventar nada. La especificación de MCP no se sacó de la manga un mecanismo de seguridad propio: se apoya en &lt;strong>OAuth&lt;/strong>, el mismo estándar que lleva años detrás del «Iniciar sesión con…» de medio internet. El MCP define cómo un servidor MCP actúa de &lt;em>recurso protegido&lt;/em> y delega la emisión de credenciales en un &lt;em>servidor de autorización&lt;/em>.&lt;/p>
&lt;p>Y ese servidor de autorización puede ser &lt;a href="https://www.keycloak.org/">Keycloak&lt;/a>, el gestor de identidades open source de la Cloud Native Computing Foundation que probablemente ya tengas montado para el resto de tus aplicaciones. La idea es no añadir un silo de identidad más solo porque ahora hablamos con modelos: reutilizar el que ya gobierna a tus usuarios.&lt;/p>
&lt;p>El matiz importante es que &lt;strong>MCP no es un único estándar congelado&lt;/strong>, sino una especificación viva con varias versiones, y cada una pide cosas distintas. A día de hoy conviven cuatro:&lt;/p>
&lt;ul>
&lt;li>&lt;code>2025-11-25&lt;/code> (la última)&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> (la inicial, que ni siquiera contemplaba autorización)&lt;/li>
&lt;/ul>
&lt;p>Igual que con la educación de un hijo no es lo mismo lo que necesita a los 6 años que a los 16, lo que Keycloak tiene que cumplir depende de la versión de MCP con la que trabajes. Veámoslo.&lt;/p>
&lt;h2 id="qué-le-exige-mcp-a-un-servidor-de-autorización-y-qué-cumple-keycloak">Qué le exige MCP a un servidor de autorización (y qué cumple Keycloak)&lt;/h2>
&lt;p>La especificación enumera una serie de estándares OAuth que el servidor de autorización debe soportar, con distintos niveles de exigencia (&lt;code>MUST&lt;/code>, &lt;code>SHOULD&lt;/code>, &lt;code>MAY&lt;/code>). Esta es la foto, cruzada con lo que Keycloak soporta:&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Estándar&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 (borrador)&lt;/td>
&lt;td>MUST&lt;/td>
&lt;td>MUST&lt;/td>
&lt;td>MUST&lt;/td>
&lt;td>Soportado&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>Soportado&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>No soportado&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>Soportado&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>OAuth Client ID Metadata Document (borrador)&lt;/td>
&lt;td>SHOULD&lt;/td>
&lt;td>—&lt;/td>
&lt;td>—&lt;/td>
&lt;td>Soportado (experimental)&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>Una aclaración: MCP también adopta &lt;em>OAuth 2.0 Protected Resource Metadata&lt;/em> (RFC 9728), pero ese estándar es cosa del &lt;strong>servidor MCP&lt;/strong>, no del servidor de autorización, así que no entra en la tabla de Keycloak.&lt;/p>
&lt;p>Si tomamos como criterio de conformidad que Keycloak «soporta» una versión cuando cumple todos sus &lt;code>MUST&lt;/code> y &lt;code>SHOULD&lt;/code>, el resultado es este:&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Versión MCP&lt;/th>
&lt;th>Conformidad&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>Soportada&lt;/strong>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>2025-06-18&lt;/code>&lt;/td>
&lt;td>Parcial, sin Resource Indicators (RFC 8707)&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>2025-11-25&lt;/code>&lt;/td>
&lt;td>Parcial, sin Resource Indicators (RFC 8707)&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>La única pieza que hoy le falta a Keycloak es &lt;strong>Resource Indicators for OAuth 2.0 (RFC 8707)&lt;/strong>, el parámetro &lt;code>resource&lt;/code> que ata un token a un servidor concreto. La comunidad de Keycloak ya tiene en hoja de ruta soportarlo; mientras tanto, hay un rodeo perfectamente válido que vemos más abajo. Es, siguiendo la analogía, esa asignatura que al niño aún se le resiste pero que se puede aprobar con un truco de estudio hasta que madure del todo.&lt;/p>
&lt;h2 id="manos-a-la-obra-montar-keycloak-según-tu-versión-de-mcp">Manos a la obra: montar Keycloak según tu versión de MCP&lt;/h2>
&lt;h3 id="para-mcp-2025-03-26">Para MCP 2025-03-26&lt;/h3>
&lt;p>No hay que configurar nada especial. Esta versión no exige el parámetro &lt;code>resource&lt;/code>, así que Keycloak cumple de serie. El hijo todavía es pequeño y la valla en la escalera basta.&lt;/p>
&lt;h3 id="para-mcp-2025-06-18-y-2025-11-25-atar-el-token-a-su-audiencia">Para MCP 2025-06-18 y 2025-11-25: atar el token a su audiencia&lt;/h3>
&lt;p>Aquí empieza lo interesante. Por seguridad, estas versiones exigen que un token de acceso esté &lt;strong>ligado a la audiencia&lt;/strong> para la que se emitió. En cristiano:&lt;/p>
&lt;ul>
&lt;li>El cliente MCP &lt;strong>debe&lt;/strong> incluir el parámetro &lt;code>resource&lt;/code> (de RFC 8707) en la petición de autorización y de token, con el valor que identifica al servidor MCP que va a usar.&lt;/li>
&lt;li>El servidor MCP &lt;strong>debe&lt;/strong> validar que los tokens que recibe se emitieron específicamente para él.&lt;/li>
&lt;/ul>
&lt;p>Esto evita que un token robado o reutilizado sirva para entrar en un servidor distinto del previsto. El problema, como decíamos, es que &lt;strong>Keycloak todavía no entiende el parámetro &lt;code>resource&lt;/code>&lt;/strong>.&lt;/p>
&lt;p>La solución mientras llega el soporte nativo de RFC 8707 es usar el parámetro &lt;strong>&lt;code>scope&lt;/code>&lt;/strong> de OAuth para conseguir el mismo efecto. Imaginemos esta situación:&lt;/p>
&lt;ul>
&lt;li>El servidor MCP está en &lt;code>https://example.com/mcp&lt;/code>.&lt;/li>
&lt;li>Soporta tres scopes: &lt;code>mcp:tools&lt;/code>, &lt;code>mcp:prompts&lt;/code> y &lt;code>mcp:resources&lt;/code>.&lt;/li>
&lt;li>El cliente pide un token con &lt;code>resource = https://example.com/mcp&lt;/code> y una combinación de esos scopes.&lt;/li>
&lt;li>Queremos que Keycloak emita un token cuyo &lt;code>aud&lt;/code> (audiencia) sea precisamente &lt;code>https://example.com/mcp&lt;/code>.&lt;/li>
&lt;/ul>
&lt;p>Para lograrlo, configuramos Keycloak así, repitiendo el patrón para cada scope:&lt;/p>
&lt;ul>
&lt;li>Crea un &lt;strong>client scope&lt;/strong> &lt;code>mcp:tools&lt;/code> de tipo &lt;em>Optional&lt;/em> y añádele un &lt;strong>Audience mapper&lt;/strong> cuyo &lt;em>Included Custom Audience&lt;/em> sea &lt;code>https://example.com/mcp&lt;/code>.&lt;/li>
&lt;li>Crea un &lt;strong>client scope&lt;/strong> &lt;code>mcp:prompts&lt;/code> de tipo &lt;em>Optional&lt;/em> con su &lt;strong>Audience mapper&lt;/strong> apuntando a &lt;code>https://example.com/mcp&lt;/code>.&lt;/li>
&lt;li>Crea un &lt;strong>client scope&lt;/strong> &lt;code>mcp:resources&lt;/code> de tipo &lt;em>Optional&lt;/em> con su &lt;strong>Audience mapper&lt;/strong> apuntando a &lt;code>https://example.com/mcp&lt;/code>.&lt;/li>
&lt;/ul>
&lt;p>La clave es que el &lt;em>Included Custom Audience&lt;/em> de cada client scope sea &lt;strong>idéntico&lt;/strong> al valor del parámetro &lt;code>resource&lt;/code> de la petición y a la URL del servidor MCP. Con esto, si el cliente pide un token con esos tres scopes, Keycloak emite algo como:&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>El servidor MCP ya puede comprobar el &lt;code>aud&lt;/code> y rechazar cualquier token que no fuera para él. Hemos atado la credencial a su destinatario sin esperar a RFC 8707.&lt;/p>
&lt;h3 id="si-usas-mcp-inspector">Si usas MCP Inspector&lt;/h3>
&lt;p>&lt;a href="https://github.com/modelcontextprotocol/inspector">MCP Inspector&lt;/a> es la herramienta oficial para depurar servidores MCP, y registra clientes dinámicamente contra Keycloak ejecutando JavaScript desde su backend. Para que funcione hay que ajustar &lt;strong>CORS&lt;/strong> en las &lt;em>anonymous access policies&lt;/em> del registro de clientes:&lt;/p>
&lt;ul>
&lt;li>&lt;strong>Allowed Client Scopes&lt;/strong>: incluir los scopes que soporta tu servidor MCP.&lt;/li>
&lt;li>&lt;strong>Allowed Registration Web Origins&lt;/strong>: incluir el origen web del backend de MCP Inspector.&lt;/li>
&lt;li>&lt;strong>Trusted Hosts&lt;/strong>: incluir el host o IP de la máquina que envía la petición de registro dinámico, es decir, donde corre tu navegador.&lt;/li>
&lt;/ul>
&lt;h3 id="para-mcp-2025-11-25-registro-de-clientes-con-client-id-metadata-document">Para MCP 2025-11-25: registro de clientes con Client ID Metadata Document&lt;/h3>
&lt;p>La última versión añade un capítulo nuevo: cómo se registran los clientes. Contempla tres enfoques, y eliges según tu escenario:&lt;/p>
&lt;ul>
&lt;li>&lt;strong>Client ID Metadata Documents&lt;/strong>: cuando cliente y servidor no se conocen de antes (el caso más común).&lt;/li>
&lt;li>&lt;strong>Pre-registro&lt;/strong>: cuando ya existe una relación previa.&lt;/li>
&lt;li>&lt;strong>Dynamic Client Registration&lt;/strong>: para compatibilidad hacia atrás o requisitos concretos.&lt;/li>
&lt;/ul>
&lt;p>Keycloak soporta el primero, &lt;strong>OAuth Client ID Metadata Document (CIMD)&lt;/strong>, aunque conviene saber que es una &lt;strong>función experimental&lt;/strong>: puede traer cambios incompatibles en versiones futuras. Para activarla, arranca Keycloak con el 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>La idea de CIMD es que el &lt;code>client_id&lt;/code> deja de ser un identificador opaco y pasa a ser &lt;strong>una URL&lt;/strong> que apunta a un documento con los metadatos del cliente. Keycloak descarga ese documento y procesa la petición con lo que encuentre. Para que lo haga, hay que crear un &lt;em>client policy profile&lt;/em> y una &lt;em>policy&lt;/em> que lo disparen.&lt;/p>
&lt;p>&lt;strong>Perfil (profile).&lt;/strong> En &lt;em>Realm Settings → Client Policies → Profiles&lt;/em>, crea un perfil (p. ej. &lt;code>cimd-profile&lt;/code>), añade el ejecutor &lt;code>client-id-metadata-document&lt;/code> y configúralo:&lt;/p>
&lt;ul>
&lt;li>&lt;strong>Allow http scheme&lt;/strong>: permite &lt;code>http&lt;/code> para las URLs (client_id, &lt;code>client_uri&lt;/code>, &lt;code>logo_uri&lt;/code>, &lt;code>jwks_uri&lt;/code>, etc.). Solo en desarrollo; en producción &lt;strong>OFF&lt;/strong>.&lt;/li>
&lt;li>&lt;strong>Trusted domains&lt;/strong>: patrones con comodín de dominios aceptados (p. ej. &lt;code>*.example.org&lt;/code>). Si está vacío, se deniegan todos.&lt;/li>
&lt;li>&lt;strong>Restrict same domain&lt;/strong>: si está ON, exige que el &lt;code>client_id&lt;/code> URL, el &lt;code>redirect_uri&lt;/code> y las URLs de los metadatos estén todos bajo el mismo dominio de confianza.&lt;/li>
&lt;li>&lt;strong>Required properties&lt;/strong>: propiedades que el documento de metadatos debe incluir obligatoriamente.&lt;/li>
&lt;li>&lt;strong>Only Allow Confidential Client&lt;/strong>: si está ON, solo acepta clientes confidenciales (con &lt;code>jwks&lt;/code>/&lt;code>jwks_uri&lt;/code> y autenticación &lt;code>private_key_jwt&lt;/code> o &lt;code>tls_client_auth&lt;/code>).&lt;/li>
&lt;/ul>
&lt;p>&lt;strong>Política (policy).&lt;/strong> En &lt;em>Realm Settings → Client Policies → Policies&lt;/em>, crea una policy (p. ej. &lt;code>cimd-policy&lt;/code>), añade la condición &lt;code>client-id-uri&lt;/code> y configúrala:&lt;/p>
&lt;ul>
&lt;li>&lt;strong>URI scheme&lt;/strong>: esquemas a reconocer en el &lt;code>client_id&lt;/code> (en producción, solo &lt;code>https&lt;/code>).&lt;/li>
&lt;li>&lt;strong>Trusted domains&lt;/strong>: dominios aceptados en el host del &lt;code>client_id&lt;/code>. Si se rellenan, la condición solo es verdadera cuando el host coincide; si se dejan vacíos, es siempre falsa.&lt;/li>
&lt;/ul>
&lt;p>Luego asocia el &lt;code>cimd-profile&lt;/code> a esta policy. A partir de ahí, cuando llegue una petición con un &lt;code>client_id&lt;/code> que sea una URL &lt;code>https&lt;/code> de un dominio de confianza, Keycloak descarga el documento de metadatos y lo usa para procesar la petición.&lt;/p>
&lt;p>&lt;strong>Ajustes globales del ejecutor.&lt;/strong> Algunos parámetros no están en la consola y se pasan como opciones SPI al arrancar:&lt;/p>
&lt;ul>
&lt;li>&lt;code>min-cache-time&lt;/code>: tiempo mínimo de caché del documento (por defecto 300 s).&lt;/li>
&lt;li>&lt;code>max-cache-time&lt;/code>: tiempo máximo de caché (por defecto 259200 s, 3 días).&lt;/li>
&lt;li>&lt;code>upper-limit-metadata-bytes&lt;/code>: tamaño máximo del documento (por defecto 5000 bytes).&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="caso-real-vs-code-desktop-como-cliente-mcp">Caso real: VS Code desktop como cliente MCP&lt;/h3>
&lt;p>Visual Studio Code es un cliente MCP que usa CIMD. Cuando se conecta a un servidor MCP que requiere autorización, manda un &lt;code>client_id&lt;/code> que es una URL &lt;code>https&lt;/code> alojada en &lt;code>vscode.dev&lt;/code> (p. ej. &lt;code>https://vscode.dev/mcp-client&lt;/code>), y Keycloak descarga de ahí sus metadatos. El detalle a tener en cuenta es que VS Code es un &lt;strong>cliente público&lt;/strong> que usa &lt;strong>PKCE&lt;/strong> (sin secreto de cliente) y, para el callback de OAuth, levanta un servidor local con un &lt;code>redirect_uri&lt;/code> tipo &lt;code>http://127.0.0.1:&amp;lt;puerto&amp;gt;/callback&lt;/code>. Como ese redirect no está en el dominio de &lt;code>vscode.dev&lt;/code>, hay que dejar &lt;strong>Restrict same domain en OFF&lt;/strong>.&lt;/p>
&lt;p>Arranca con &lt;code>--features=cimd&lt;/code> y configura:&lt;/p>
&lt;p>&lt;strong>Perfil &lt;code>vscode-cimd-profile&lt;/code>&lt;/strong> (ejecutor &lt;code>client-id-metadata-document&lt;/code>):&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> (el redirect es un localhost, no &lt;code>vscode.dev&lt;/code>)&lt;/li>
&lt;li>Only Allow Confidential Client: &lt;strong>OFF&lt;/strong> (VS Code es público)&lt;/li>
&lt;/ul>
&lt;p>&lt;strong>Política &lt;code>vscode-cimd-policy&lt;/code>&lt;/strong> (condición &lt;code>client-id-uri&lt;/code>):&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>Asocia el perfil &lt;code>vscode-cimd-profile&lt;/code>.&lt;/li>
&lt;/ul>
&lt;p>Con esto, cuando VS Code lance la petición, Keycloak reconoce el &lt;code>client_id&lt;/code> como una URL de &lt;code>vscode.dev&lt;/code>, descarga el Client ID Metadata Document y completa el flujo OAuth con el callback en localhost.&lt;/p>
&lt;h2 id="la-educación-continúa">La educación continúa&lt;/h2>
&lt;p>Hemos enseñado a nuestro MCP a identificarse y a pedir permiso de forma estándar, reutilizando la identidad que ya gobierna Keycloak en el resto de la casa. No es poca cosa: con un token bien atado a su audiencia, lo demás empieza a ser posible.&lt;/p>
&lt;p>Pero, como con cualquier hijo que crece, esto no termina aquí. Quedan pendientes las otras dos asignaturas que mencionábamos al principio —&lt;strong>separar a los inquilinos (multitenancy)&lt;/strong> y &lt;strong>controlar lo que gasta (costes)&lt;/strong>— y a Keycloak todavía le falta madurar en algún punto, como el soporte nativo de Resource Indicators. Iremos cubriéndolas en próximos artículos. Por ahora, nuestro MCP ya sabe decir quién es. Y eso, en seguridad, es el primer día de colegio.&lt;/p>
&lt;h2 id="fuentes">Fuentes&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>