<?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>DEV Community: Vlastimil Elias</title>
    <description>The latest articles on DEV Community by Vlastimil Elias (@velias).</description>
    <link>https://dev.to/velias</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.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3933314%2F2e21561c-bfcf-4ed5-a823-8b5c60c1e630.png</url>
      <title>DEV Community: Vlastimil Elias</title>
      <link>https://dev.to/velias</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/velias"/>
    <language>en</language>
    <item>
      <title>Authenticated Machine-to-machine MCP without the OAuth puzzle</title>
      <dc:creator>Vlastimil Elias</dc:creator>
      <pubDate>Fri, 24 Jul 2026 13:12:52 +0000</pubDate>
      <link>https://dev.to/velias/authenticated-machine-to-machine-mcp-without-the-oauth-puzzle-31mn</link>
      <guid>https://dev.to/velias/authenticated-machine-to-machine-mcp-without-the-oauth-puzzle-31mn</guid>
      <description>&lt;p&gt;&lt;a href="https://modelcontextprotocol.io/specification/2025-11-25/basic/authorization" rel="noopener noreferrer"&gt;MCP Authorization spec&lt;/a&gt; and most MCP clients assume a human is present: open a browser, consent, get a token, keep going until the session expires, then repeat. That model breaks as soon as you want an autonomous agent, a CI job, a daemon, or any other long-running process that must talk to an OAuth-protected MCP server with nobody at the keyboard.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://github.com/velias/mcp-client-credentials-auth" rel="noopener noreferrer"&gt;mcp-client-credentials-auth&lt;/a&gt;&lt;/strong&gt; is a local MCP authentication proxy for that gap. It sits between your MCP client and a remote MCP server, obtains tokens with the OAuth &lt;code&gt;client_credentials&lt;/code&gt; grant (&lt;a href="https://modelcontextprotocol.io/extensions/auth/oauth-client-credentials" rel="noopener noreferrer"&gt;MCP OAuth Client Credentials extension&lt;/a&gt; Draft), and forwards MCP traffic with a Bearer token. Your MCP client keeps talking plain unauthenticated MCP, and the proxy handles authentication.&lt;/p&gt;

&lt;p&gt;This post is for two audiences: people who need machine access to a remote MCP server today, and MCP server providers who want a ready client path to recommend for that access.&lt;/p&gt;

&lt;p&gt;To use it, the MCP server provider must support machine auth via OAuth &lt;a href="https://datatracker.ietf.org/doc/html/rfc6749#section-4.4" rel="noopener noreferrer"&gt;&lt;code&gt;client_credentials&lt;/code&gt;&lt;/a&gt;. In practice that means a service account (sometimes labeled "API key", "machine-to-machine application", or similar) with a &lt;code&gt;client_id&lt;/code&gt; and &lt;code&gt;client_secret&lt;/code&gt; that their OAuth setup accepts for this grant. Creating those credentials is often self-service in the provider's account or developer portal, or they are handed out on request. Interactive-only servers (browser login / user consent only) are out of scope for this proxy.&lt;/p&gt;

&lt;h2&gt;
  
  
  The use case
&lt;/h2&gt;

&lt;p&gt;Use it whenever the caller is a machine, not a person, &lt;strong&gt;and&lt;/strong&gt; the remote MCP provider offers that service-account style access:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Autonomous agents and background workers that need tools, resources, or prompts from a protected remote MCP&lt;/li&gt;
&lt;li&gt;CI/CD pipelines and ops automations&lt;/li&gt;
&lt;li&gt;Server-to-server integrations and long-running processes where human may not be at the keyboard&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You can also use it when you are tired of unoptimal interactive authentication UX in MCP clients, when you can easily miss that MCP server is not authenticated, and you do not have access to the tools anymore.&lt;/p&gt;

&lt;p&gt;Once you have credentials from the provider, point the proxy at the remote MCP URL and drop it into your MCP client config. The proxy acquires acsess tokens, refreshes them proactively, reconnects when the remote MCP server flaps, and stays transparent to the protocol.&lt;/p&gt;

&lt;h2&gt;
  
  
  Installation that stays simple (because discovery works)
&lt;/h2&gt;

&lt;p&gt;Well-behaved MCP servers publish &lt;a href="https://modelcontextprotocol.io/specification/2025-11-05/basic/authorization" rel="noopener noreferrer"&gt;MCP Authorization&lt;/a&gt; metadata (RFC 9728 / RFC 8414). The proxy follows that discovery path, so you normally do &lt;strong&gt;not&lt;/strong&gt; hand-configure a token endpoint or invent scopes.&lt;/p&gt;

&lt;p&gt;Typical setup in an MCP client such as Cursor or Claude Code is three environment variables:&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;"mcpServers"&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;"my-remote-server"&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;"command"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"npx"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"args"&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="s2"&gt;"-y"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"mcp-client-credentials-auth"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"env"&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;"MCP_CC_PROXY_REMOTE_MCP_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;"https://mcp.example.com/mcp"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"MCP_CC_PROXY_CLIENT_ID"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"my-service"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"MCP_CC_PROXY_CLIENT_SECRET"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"s3cr3t"&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="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;That is the whole install for the common path: remote MCP server URL + &lt;code&gt;client_id&lt;/code&gt; + &lt;code&gt;client_secret&lt;/code&gt;. Discovery finds the IdP token endpoint and the baseline scopes. The proxy fails closed at startup if auth or the remote cannot be made ready, so you do not get a green local session that dies on the first real call.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;npx&lt;/code&gt; downloads and runs the published package on your machine, so use trusted packages only (same caution as any other &lt;code&gt;npx&lt;/code&gt; MCP server).&lt;/p&gt;

&lt;p&gt;If a server accepts Bearer tokens but does not publish discovery, you can still run with a manual token endpoint and scopes (&lt;code&gt;MCP_CC_PROXY_TOKEN_ENDPOINT&lt;/code&gt; and usually &lt;code&gt;MCP_CC_PROXY_SCOPES&lt;/code&gt;). Those values must come from the MCP server provider's documentation, do not try to invent them. Prefer auto-discovery when the MCP server supports it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Two deployment options
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Local stdio (default): one client, one proxy process
&lt;/h3&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F3h5lgluscucbjeay4t77.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F3h5lgluscucbjeay4t77.png" alt="Diagram of local stdio deployment" width="800" height="354"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This is the install path above. Your MCP client spawns the proxy over stdio (proxy acts as local MCP Server) on the same machine, and the proxy talks outbound HTTPS to the remote server with Bearer auth. Each user (or agent host) should use their own service-account credentials, not a shared org-wide secret copied into every laptop config.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Viable when:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A single MCP client (IDE, agent host, CLI) can spawn a local MCP Server process&lt;/li&gt;
&lt;li&gt;Each user can obtain (or be issued) their own &lt;code&gt;client_credentials&lt;/code&gt; client&lt;/li&gt;
&lt;li&gt;You want the smallest attack surface: no local network listener&lt;/li&gt;
&lt;li&gt;Credentials stay on that machine / in that client's secret store&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Prefer stdio whenever one client can own the proxy lifecycle. It is the right default for Cursor, Claude Desktop, Claude Code, and similar local setups.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. On-premises HTTP: one shared proxy and identity for several clients
&lt;/h3&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fg7sg89n15u0i9bzw538n.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fg7sg89n15u0i9bzw538n.png" alt="Diagram of on-premises HTTP deployment" width="799" height="322"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Set &lt;code&gt;MCP_CC_PROXY_TRANSPORT=http&lt;/code&gt;. The process listens for Streamable HTTP so multiple MCP clients on a private network can share one outbound &lt;code&gt;client_credentials&lt;/code&gt; identity. A container image is also available with HTTP as the default transport and k8s style health checks for easy deployment.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Viable when:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Several MCP clients on a trusted private network should share one machine identity&lt;/li&gt;
&lt;li&gt;You do not want to install or spawn the proxy on every workstation&lt;/li&gt;
&lt;li&gt;A single MCP client (IDE, agent host, CLI) can't spawn a local MCP Server process but has to use remote one&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;HTTP mode has no TLS or inbound authentication on the proxy itself, see Security considerations.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Situation&lt;/th&gt;
&lt;th&gt;Choose&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;One client can spawn a local MCP server process, and each user has their own OAuth client&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Local stdio&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Many clients or no local MCP Server support, one shared M2M identity on a private network&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;On-premises HTTP&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Public internet or untrusted callers&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;Not recommended&lt;/strong&gt; for either mode&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  What you get beyond "it connects"
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Transparent bidirectional MCP forwarding&lt;/li&gt;
&lt;li&gt;Identity and capability forwarding (your client sees the remote server's real name and capabilities, including server-to-client features such as sampling and elicitation)&lt;/li&gt;
&lt;li&gt;Proactive access token refresh and 401 retry behavior&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://modelcontextprotocol.io/specification/2025-11-25/basic/authorization#step-up-authorization-flow" rel="noopener noreferrer"&gt;Scope step-up&lt;/a&gt; when the remote challenges with 403 &lt;code&gt;insufficient_scope&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Streamable HTTP with SSE fallback for connection-class failures&lt;/li&gt;
&lt;li&gt;Automatic reconnection and stale remote session recovery&lt;/li&gt;
&lt;li&gt;Optional call audit log (on by default in HTTP mode)&lt;/li&gt;
&lt;li&gt;Categorized errors (&lt;code&gt;authentication&lt;/code&gt; / &lt;code&gt;connection&lt;/code&gt; / &lt;code&gt;remote&lt;/code&gt;) with detailed messages for easier troubleshooting&lt;/li&gt;
&lt;li&gt;Fail-closed startup: the local transport binds only after auth is ready and the remote MCP server is reachable, so your MCP client shows a correct status&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Security considerations
&lt;/h2&gt;

&lt;p&gt;A few rules of thumb matter more than the feature list:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Treat the service account as powerful.&lt;/strong&gt; Whoever holds &lt;code&gt;client_id&lt;/code&gt; / &lt;code&gt;client_secret&lt;/code&gt; (or can reach an HTTP-mode proxy that already has them) gets that remote privilege. Prefer per-user clients for stdio, and rotate on compromise.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Prefer vaults over plain-text MCP config.&lt;/strong&gt; The install snippet above shows secrets in environment variables for brevity. In production, inject &lt;code&gt;client_id&lt;/code&gt; / &lt;code&gt;client_secret&lt;/code&gt; at launch from 1Password, Bitwarden, a cloud secret store, or similar, instead of leaving them in an MCP config file on disk.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Keep the proxy private.&lt;/strong&gt; Stdio has no network listener. HTTP mode is for trusted private networks only: recommended to terminate TLS and inbound auth at a reverse proxy, ingress, or MCP gateway. Never publish the listener to the public internet.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;HTTP mode is one shared machine identity.&lt;/strong&gt; All MCP clients reuse the same outbound Bearer token and scopes. It is not a multi-tenant edge, so use separate deployments per trust boundary.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The proxy owns the outbound Authorization header.&lt;/strong&gt; Access tokens stay in memory and are never logged, and the local MCP client cannot supply or override the Bearer token used toward the remote server.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;More detail is in the repo &lt;a href="https://github.com/velias/mcp-client-credentials-auth#security" rel="noopener noreferrer"&gt;Security&lt;/a&gt; section.&lt;/p&gt;

&lt;h2&gt;
  
  
  For MCP server providers
&lt;/h2&gt;

&lt;p&gt;If you already protect your MCP server with OAuth for interactive clients, your users will still ask how agents, CI, and other headless callers should authenticate. You do not need to ship a custom M2M client for every IDE and agent host. Document this proxy as the supported way to get service-account access: users create (or receive) a &lt;code&gt;client_id&lt;/code&gt; / &lt;code&gt;client_secret&lt;/code&gt;, point the proxy at your MCP URL, and keep using their existing plain MCP client.&lt;/p&gt;

&lt;p&gt;That works best when your product already exposes machine credentials and standard MCP Authorization discovery:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Publish protected resource / authorization server metadata so the proxy can find your token endpoint and baseline scopes&lt;/li&gt;
&lt;li&gt;Offer a service account (or equivalent) granted those baseline scopes for &lt;code&gt;client_credentials&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Prefer a short setup guide: remote URL + credentials (+ this proxy), not a DIY OAuth client walkthrough&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Users then get secure, long-term, non-interactive service-account access without building token acquisition themselves. You keep one OAuth-protected MCP surface for both human consent flows and machine callers, and you point the machine path at a known, discovery-friendly client instead of fragmented one-off scripts.&lt;/p&gt;

&lt;p&gt;If discovery is not available yet, you can still recommend the proxy, but document the necessary settings yourself: the IdP token endpoint URL and the scopes to request (&lt;code&gt;MCP_CC_PROXY_TOKEN_ENDPOINT&lt;/code&gt; and &lt;code&gt;MCP_CC_PROXY_SCOPES&lt;/code&gt;). Users cannot discover those without your guidance. Auto-discovery remains the smoother story when you can publish it.&lt;/p&gt;

&lt;p&gt;For more detail see &lt;a href="https://github.com/velias/mcp-client-credentials-auth#notes-for-mcp-server-developers" rel="noopener noreferrer"&gt;notes for MCP Server Developers&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Links
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;GitHub: &lt;a href="https://github.com/velias/mcp-client-credentials-auth" rel="noopener noreferrer"&gt;velias/mcp-client-credentials-auth&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;npm: &lt;a href="https://www.npmjs.com/package/mcp-client-credentials-auth" rel="noopener noreferrer"&gt;mcp-client-credentials-auth&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Container: &lt;code&gt;ghcr.io/velias/mcp-client-credentials-auth&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Spec: &lt;a href="https://modelcontextprotocol.io/specification/2025-11-25/basic/authorization" rel="noopener noreferrer"&gt;MCP Authorization&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Spec: &lt;a href="https://modelcontextprotocol.io/extensions/auth/oauth-client-credentials" rel="noopener noreferrer"&gt;MCP OAuth Client Credentials&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

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

&lt;p&gt;If you run OAuth-protected MCP for agents or pipelines, or you operate an MCP server and want an easy M2M client path for your users, start with discovery plus service-account credentials and let the proxy handle the rest on the client side. Feedback and issues are welcome on the repo.&lt;/p&gt;

</description>
      <category>mcp</category>
      <category>ai</category>
      <category>security</category>
      <category>agents</category>
    </item>
    <item>
      <title>Bridge the gap between your IdP and the MCP World</title>
      <dc:creator>Vlastimil Elias</dc:creator>
      <pubDate>Fri, 15 May 2026 14:23:40 +0000</pubDate>
      <link>https://dev.to/velias/bridge-the-gap-between-your-idp-and-the-mcp-world-3gbn</link>
      <guid>https://dev.to/velias/bridge-the-gap-between-your-idp-and-the-mcp-world-3gbn</guid>
      <description>&lt;p&gt;So you've got your corporate IdP (Keycloak, Auth0, Okta, Azure AD, whatever) and now you want your MCP servers to use it for auth. You point Claude Code or Cursor at it, aaand... things break. Scope explosions on the consent screen, missing PKCE defaults, clients demanding Dynamic Client Registration your IdP doesn't serve the way MCP expects. Sound familiar?&lt;/p&gt;

&lt;h2&gt;
  
  
  The problem
&lt;/h2&gt;

&lt;p&gt;The &lt;a href="https://modelcontextprotocol.io/specification/2025-11-25/basic/authorization" rel="noopener noreferrer"&gt;MCP Authorization spec&lt;/a&gt; expects certain OAuth behaviors that most enterprise IdPs don't provide out of the box:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;MCP clients expect open Dynamic Client Registration.&lt;/strong&gt; Most IdPs either don't expose it or lock it behind admin credentials.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;MCP clients tend to request all announced scopes.&lt;/strong&gt; This isn't required by the spec - it's just how most clients (Claude Code, Cursor, others) behave in practice. They read &lt;code&gt;scopes_supported&lt;/code&gt; from discovery and request &lt;em&gt;all of them&lt;/em&gt;. Your Keycloak announces 15 internal scopes? Congrats, users now see a consent screen from hell - or the request gets rejected outright if client is not pre-approved for some announced scope.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Many clients add &lt;code&gt;offline_access&lt;/code&gt; unconditionally.&lt;/strong&gt; Again, not a spec requirement - just a common client behavior to ensure they get refresh tokens. This becomes a problem when your IdP restricts long-lived refresh tokens or requires client pre-approval for that scope.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Discovery metadata needs filtering.&lt;/strong&gt; IdPs expose dozens of OIDC fields (CIBA, device flow, logout endpoints...) that are irrelevant noise for MCP and can confuse clients.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You could customize your IdP, but that's a maintenance rabbit hole - especially when you need the same IdP for non-MCP and legacy apps.&lt;/p&gt;

&lt;h2&gt;
  
  
  Enter mcp-auth-adapter
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/velias/mcp-auth-adapter" rel="noopener noreferrer"&gt;&lt;strong&gt;mcp-auth-adapter&lt;/strong&gt;&lt;/a&gt; is a thin, stateless Node.js proxy that sits between your MCP clients and your existing IdP. It doesn't issue tokens or handle authentication - all the real work stays on your IdP. It just makes the OAuth dance MCP-compatible.&lt;/p&gt;

&lt;h3&gt;
  
  
  What it does
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Filtered discovery&lt;/strong&gt; - Serves &lt;code&gt;/.well-known/&lt;/code&gt; IdP metadata with only the fields MCP clients actually need. Injects safe defaults (PKCE S256, authorization_code grant) when your IdP's metadata is incomplete, and own functionality where needed.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Open DCR endpoint&lt;/strong&gt; - &lt;code&gt;POST /register&lt;/code&gt; hands out a fixed, pre-configured &lt;code&gt;client_id&lt;/code&gt; so MCP clients can self-register. No IdP-side DCR needed.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Scope filtering&lt;/strong&gt; - Control what scopes reach your IdP. Strip &lt;code&gt;offline_access&lt;/code&gt;, remove internal scopes, or use an allowlist:&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="c"&gt;# Only these scopes will ever reach the upstream IdP&lt;/span&gt;
&lt;span class="nv"&gt;MCP_PROXY_AUTH_SCOPES_PRESERVED&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;openid,api.read,api.write
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;CIMD support (experimental)&lt;/strong&gt; - The MCP spec defaults to &lt;a href="https://datatracker.ietf.org/doc/draft-ietf-oauth-client-id-metadata-document/" rel="noopener noreferrer"&gt;Client ID Metadata Documents&lt;/a&gt; for client identification, but it's an emerging IETF draft (not yet RFC) and no major IdP supports it natively today. This adapter bridges that gap - it accepts CIMD-style &lt;code&gt;client_id&lt;/code&gt; URLs from MCP clients, validates the metadata documents, and maps them to real upstream client_ids your IdP understands. So you get CIMD compatibility without waiting for your IdP vendor to implement it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Observability built in&lt;/strong&gt; - Prometheus metrics at &lt;code&gt;/metrics&lt;/code&gt;, structured logging, health probes for k8s.&lt;/p&gt;

&lt;h2&gt;
  
  
  Deploy it in 30 seconds
&lt;/h2&gt;

&lt;p&gt;Grab the container image and go:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker run &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="nt"&gt;--name&lt;/span&gt; mcp-auth-adapter &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-p&lt;/span&gt; 3000:3000 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="nv"&gt;MCP_BASE_URL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;https://mcp-auth.example.com &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="nv"&gt;MCP_UPSTREAM_SSO_URL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;https://sso.example.com/auth/realms/external &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="nv"&gt;MCP_PROXY_DCR_CLIENT_ID&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;mcp-client &lt;span class="se"&gt;\&lt;/span&gt;
  ghcr.io/velias/mcp-auth-adapter:latest
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's it. Three env vars for a basic setup:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;MCP_BASE_URL&lt;/code&gt; - the public URL where this adapter lives&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;MCP_UPSTREAM_SSO_URL&lt;/code&gt; - your IdP's issuer URL&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;MCP_PROXY_DCR_CLIENT_ID&lt;/code&gt; - a public client pre-registered at your IdP&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Then point your MCP server's &lt;code&gt;authorization_servers&lt;/code&gt; to &lt;code&gt;MCP_BASE_URL&lt;/code&gt; and clients will discover everything via &lt;code&gt;.well-known&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Typical production config
&lt;/h3&gt;

&lt;p&gt;For a real deployment you'll probably want scope control too:&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="nv"&gt;MCP_BASE_URL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;https://mcp-auth.example.com
&lt;span class="nv"&gt;MCP_UPSTREAM_SSO_URL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;https://sso.example.com/auth/realms/external
&lt;span class="nv"&gt;MCP_PROXY_DCR_CLIENT_ID&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;mcp-client
&lt;span class="nv"&gt;MCP_WELL_KNOWN_SCOPES_SUPPORTED&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;openid,api.read,api.write
&lt;span class="nv"&gt;MCP_PROXY_AUTH_SCOPES_REMOVED&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;offline_access
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This controls both sides: what clients &lt;em&gt;see&lt;/em&gt; in discovery and what actually &lt;em&gt;reaches&lt;/em&gt; your IdP.&lt;/p&gt;

&lt;h2&gt;
  
  
  What it doesn't do
&lt;/h2&gt;

&lt;p&gt;This adapter is intentionally minimal:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;No token issuing&lt;/strong&gt; - tokens come from your IdP, always&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No user database&lt;/strong&gt; - stateless, nothing to back up&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No rate limiting&lt;/strong&gt; - put it behind your existing reverse proxy / WAF&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No CORS&lt;/strong&gt; - designed for redirect-based flows, not browser fetch calls&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Who is this for?
&lt;/h2&gt;

&lt;p&gt;If you're running MCP servers (or planning to) and have an existing OAuth/OIDC provider, this saves you from bending your IdP config to accommodate MCP client quirks. Works with Keycloak, Auth0, Okta, Azure AD/Entra, Google Identity - anything that serves standard OIDC discovery.&lt;/p&gt;

&lt;p&gt;Tested with Claude Code, Claude Desktop, Cursor IDE, Cursor Agent, Gemini CLI, VS Code, and more.&lt;/p&gt;

&lt;h2&gt;
  
  
  Wrapping up
&lt;/h2&gt;

&lt;p&gt;MCP is moving fast, and auth is one of those things that &lt;em&gt;should&lt;/em&gt; just work but often doesn't when you try to connect real-world IdPs to real-world MCP clients. Instead of fighting your IdP config or waiting for vendors to catch up with emerging standards like CIMD, drop a lightweight adapter in between and move on to building the actual MCP tools your users care about.&lt;/p&gt;

&lt;p&gt;If you hit rough edges or have ideas, open an issue - the project is young and feedback shapes the roadmap.&lt;/p&gt;




&lt;p&gt;Apache 2.0 licensed. PRs welcome.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;GitHub:&lt;/strong&gt; &lt;a href="https://github.com/velias/mcp-auth-adapter" rel="noopener noreferrer"&gt;github.com/velias/mcp-auth-adapter&lt;/a&gt;&lt;/p&gt;

</description>
      <category>opensource</category>
      <category>security</category>
      <category>ai</category>
      <category>mcp</category>
    </item>
  </channel>
</rss>
