The State of MCP OAuth with Azure Entra ID and APIM — A Reality Check

AzureMCPOAuthTypeScript

I recently set out to expose an existing REST API as MCP tools using Azure API Management's new MCP feature. The idea was simple: let Claude Code, VS Code Copilot, and other AI clients query our internal data platform through the Model Context Protocol — no new services, no custom MCP server, just APIM translating between MCP and REST.

The APIM part worked. The auth part did not.

This is a summary of what I found, which clients work, which don't, and what you can do about it today (March 2026).

What I Was Building

Our API runs on Azure Functions with Hono. It lets users run SQL queries against internal data products — think a natural-language-to-SQL pipeline. We wanted to expose a subset of these endpoints (execute query, list tables, manage saved queries) as MCP tools so developers could use them directly from their AI coding assistants.

Azure APIM has a preview feature for this: you register an API with apiType: 'mcp', point it at your existing REST API's OpenAPI spec, and APIM generates MCP tool definitions automatically. When a client calls tools/call, APIM translates the JSON-RPC request into an HTTP call to your backend. Your backend never sees MCP — it just handles regular REST requests.

This is APIM's "Mode 1" (REST as MCP), as opposed to "Mode 2" where APIM proxies traffic to a backend that speaks MCP natively. Mode 1 is the interesting one — no code changes to your API, just infrastructure config.

The OAuth Problem

MCP supports OAuth for authentication. Our API is protected by Microsoft Entra ID (Azure AD). In theory, an MCP client should be able to:

  1. Discover the authorization server via RFC 9728 (Protected Resource Metadata)
  2. Get a token with the right audience and scopes
  3. Send it with MCP requests

In practice, this chain is broken at multiple points.

Claude Code: scope Is Ignored

The core issue is simple: Claude Code does not send the scope parameter when requesting tokens from Entra ID. You can put "scope": "api://your-app-id/.default" in your .mcp.json config — Claude Code ignores it.

Without a scope, Entra ID doesn't know which API the token is for. It falls back to Microsoft Graph. So you end up with a token whose audience is https://graph.microsoft.com instead of your API. Your APIM policy rejects it, or — if you haven't set up validation yet — your backend gets a token that says nothing about the user's permissions for your app.

I confirmed this through Entra ID sign-in logs: resourceDisplayName: Microsoft Graph, resourceId: 00000003-0000-0000-c000-000000000000. The token never targets our API.

The relevant issues:

| Issue | Description | Status | |-------|-------------|--------| | claude-code#4540 | Missing scope in DCR + authorization requests | Open (since Jul 2025) | | claude-code#7744 | Ignores scopes_supported from Protected Resource Metadata | Open (since Sep 2025) | | claude-code#12077 | OAuth missing scope for HTTP servers | Closed (inactivity, not fixed) |

Dynamic Client Registration: A Non-Starter with Entra ID

MCP's auth spec leans heavily on Dynamic Client Registration (DCR) — the client registers itself with the authorization server on the fly. Entra ID does not support DCR. There is no way to make it work.

Claude Code insists on attempting DCR even when you provide an explicit clientId in the config. This means even if you pre-register an app in Entra ID and hand Claude Code the client ID, it still tries DCR first and fails.

| Issue | Description | Status | |-------|-------------|--------| | claude-code#3273 | Servers without DCR don't work | Open | | claude-code#26675 | clientId in config, still forces DCR | Open | | claude-code#38102 | DCR despite clientId (duplicate, confirming issue is current) | Open |

The MCP TypeScript SDK Has the Same Problem

It's not just Claude Code. The MCP TypeScript SDK itself doesn't pass scopes during token exchange:

| Issue | Description | Status | |-------|-------------|--------| | typescript-sdk#941 | Scope missing in token exchange | Open, P2, ready for work | | typescript-sdk#1669 | Fix PR: add scope to token exchange | Open since March 12, not merged |

There's also a spec-level incompatibility: Entra ID v2 rejects the resource parameter (RFC 8707) with AADSTS901002, but the MCP spec requires it. This is tracked in modelcontextprotocol#1614.

Which Clients Actually Work?

I tested every major MCP client I could get my hands on against our APIM MCP endpoint with Entra ID OAuth:

| Client | Works? | Notes | |--------|--------|-------| | VS Code (Copilot Agent Mode) | Yes | Built-in Entra client ID, implements RFC 9728. Minor bug: vscode#261120/authorize URL sometimes built from resource origin instead of authorization server | | ChatGPT | Yes | OAuth flow works correctly | | Claude Code CLI | No | Scope ignored, DCR forced | | claude.ai Web | Partial | Popup bugs after auth | | GitHub Copilot CLI | Partial | OAuth supported but buggy: auth prompt not triggered (#1967), DCR port issues (#1951), no silent refresh (#1797) | | Copilot Coding Agent (Cloud) | No | Only supports stdio MCP servers, no remote/OAuth |

The irony: VS Code Copilot and ChatGPT — not Claude — are the ones that can actually authenticate against an MCP server protected by Microsoft's own identity platform.

APIM MCP Preview: The Rough Edges

Even setting OAuth aside, the APIM MCP feature has its own issues. It works for the basic case, but there are several gotchas:

  • Tools only — no MCP Resources or Prompts
  • Not available on Consumption tier — you need at least Developer
  • context.Product is null in MCP policies, so product-based access control is broken
  • context.Response.Body in policies hangs the request — it breaks SSE streaming, which MCP uses
  • /tools/list doesn't validate subscription keys by default
  • Backend HTTP errors (500) come back as HTTP 200 with the error in the JSON body — the MCP client sees a "successful" response containing an error message
  • ARM deployment bug with operationId: Re-importing OpenAPI specs with operationId fields via ARM (Bicep) causes InternalServerError on Developer tier. Direct REST API calls work fine. I work around this by stripping operationId from the specs that get imported into APIM, while keeping them in the full spec for Swagger UI.

Source for several of these: Azure APIM MCP: The Good, The Bad, The Ugly by itsrene, which matches my experience.

What Actually Works Today

If you want to use MCP with Azure APIM today, here are your options:

Option 1: Subscription Key (No OAuth)

The simplest path. No user-level identity, but it works with every client.

{
  "mcpServers": {
    "my-api": {
      "type": "http",
      "url": "https://your-apim.azure-api.net/mcp/your-api/mcp",
      "headers": {
        "Ocp-Apim-Subscription-Key": "<key>"
      }
    }
  }
}

Option 2: Manual Bearer Token

You get user identity, but the token expires (typically 1 hour).

TOKEN=$(az account get-access-token \
  --resource api://your-app-id \
  --query accessToken -o tsv)
{
  "mcpServers": {
    "my-api": {
      "type": "http",
      "url": "https://your-apim.azure-api.net/mcp/your-api/mcp",
      "headers": {
        "Authorization": "Bearer ${MY_API_TOKEN}"
      }
    }
  }
}

Option 3: mcp-remote Proxy

Handles OAuth outside of the MCP client. See geelen/mcp-remote.

Option 4: DCR Proxy (mcp-gateway)

Intercepts DCR requests and injects the right scopes. See hyprmcp/mcp-gateway.

Where Things Stand

The MCP ecosystem is moving fast, but OAuth with enterprise identity providers is clearly not a priority yet. The scope bug in Claude Code has been open since July 2025. The SDK fix has been sitting in a PR since March 12. The spec-level resource parameter conflict with Entra ID is unresolved.

If you're building on Azure and want MCP with proper auth, VS Code Copilot is your best bet right now. For everything else, use a subscription key and accept that you won't have user-level identity until the OAuth story matures.

I'm keeping our APIM MCP endpoint deployed without inbound token validation for now. The full policy with validate-azure-ad-token is ready and waiting — it just needs clients that can actually produce the right tokens.


All issues and findings are current as of March 2026. The MCP auth spec is still in draft, and client implementations are actively changing. Things may improve quickly — or they may not. Check the linked issues for the latest status.

All Articles