MCP端到端安全保护

该服务提供结合API Management、MCP与OAuth的端到端安全保护方案,适用于AI Agent、Claude Code及VS Code等客户端通过APIM安全访问和调用MCP工具。

  • by apc-n-orita
  • HCL
  • 本地部署
  • 1 星标
  • 更新于
在 GitHub 查看 apc-n-orita/apim-mcp-oauth

使用说明

End-to-End MCP Protection with API Management × MCP × OAuth

This repository provides a hands-on learning experience for implementing end-to-end protection using API Management × MCP × OAuth.

Hands-On Overview

This hands-on lab demonstrates the end-to-end flow for securely executing MCP in two patterns:

  • AI Agent / Claude Code → APIM → MCP
  • VS Code (GitHub Copilot Chat / CLI) → APIM → MCP

AI Agent / Claude Code → APIM → MCP

The overall flow when executing MCP from an AI Agent (e.g., MS Foundry Agent) or Claude Code.
Both clients acquire an access token in advance (via managed identity, service principal or az login) and attach it to requests — the APIM flow is identical.

sequenceDiagram
    participant Agent as AI Agent / Claude Code
    participant Entra as Entra ID
    participant APIM as API Management
    participant MCP as MCP Backend (Logic Apps)

    Agent->>Entra: 1) Request access token
    Entra-->>Agent: 2) Return access token

    Agent->>APIM: 3) Access /mcp with access token
    Note over APIM: 4) Validate access token
    APIM->>Entra: 5a) Acquire token for MCP Backend using APIM Managed Identity
    APIM->>MCP: 5b) Access MCP backend with APIM MSI token
    MCP-->>APIM: 6a) Return tool list
    APIM-->>Agent: 6b) Return tool list

    Agent->>APIM: 7) Request to execute MCP tool
    Note over APIM: 8) Validate MCP role from access token

    alt 9a) Role validation succeeds
        APIM->>MCP: 9a-1) Execute MCP tool with APIM MSI token
        MCP-->>APIM: 9a-2) Tool execution result
        APIM-->>Agent: 9a-3) Return MCP tool execution result
    else 9b) Role validation fails
        APIM-->>Agent: 403 Forbidden
    end

VS Code (GitHub Copilot Chat / CLI) → APIM → MCP

The overall flow when executing MCP from VS Code (GitHub Copilot Chat or CLI).
Both Chat and CLI in VS Code share the same OAuth authorization flow — they discover the OAuth endpoint from APIM and authenticate interactively.

sequenceDiagram
    participant VS as VS Code (GitHub Copilot Chat / CLI)
    participant APIM as API Management
    participant ID as Entra ID
    participant MCP as MCP Backend (Logic Apps)

    VS->>APIM: 1) Request access to /mcp
    APIM-->>VS: 2) 401 Unauthorized

    VS->>APIM: 3) GET /.well-known/oauth-protected-resource
    APIM-->>VS: 4) Return OAuth metadata

    VS->>ID: 5) Get an access token through the login/authorization code flow

    VS->>APIM: 6) Re-access /mcp with access token
    Note over APIM: 7) Validate access token
    APIM->>ID: 8a) Acquire token for MCP Backend using APIM Managed Identity
    APIM->>MCP: 8b) Access MCP backend with APIM MSI token
    MCP-->>APIM: 9a) Return tool list
    APIM-->>VS: 9b) Return tool list

    VS->>APIM: 10) Request to execute MCP tool
    Note over APIM: 11) Validate MCP role from access token

    alt 12a) Role validation succeeds
        APIM->>MCP: 12a-1) Execute MCP tool with APIM MSI token
        MCP-->>APIM: 12a-2) Tool execution result
        APIM-->>VS: 12a-3) Return MCP tool execution result
    else 12b) Role validation fails
        APIM-->>VS: 403 Forbidden
    end

Architecture Features

This hands-on lab implements robust end-to-end security through the following five elements:

1. OAuth2 Authorization

User authentication and access token issuance via Entra ID.

2. Token Validation

APIM validates OAuth access tokens (issuer / audience / signature / expiration) and blocks unauthorized requests.

3. Role-Based Authorization

Evaluates MCP roles (claims) within the access token for fine-grained tool execution control.

4. Secretless Connection

APIM's Managed Identity (MSI) enables secure access to MCP Backend, eliminating credential leakage risks.

5. Backend Protection

MCP Backend enforces authorization at two layers:

  • Easy Auth (Entra ID): Platform-level token validation — rejects any request without a valid Entra ID bearer token.
  • App Role check (Mcp.Invoke): Application-level check — rejects callers whose token does not contain the Mcp.Invoke App Role, even if the token itself is valid.

This means only two types of callers can reach the backend directly:

  1. APIM Managed Identity — holds a permanent Mcp.Invoke role assignment, used for all production traffic.
  2. Ops Entra ID group members — assigned Mcp.Invoke via the ops group, for direct-access troubleshooting and testing.

Deploy Hands-On Environment

Deploy the environment using Azure Developer CLI (azd) with the following steps.

Prerequisites

Ensure the following are installed locally:

  • Terraform (recommended: v1.5 or later)
  • Azure Developer CLI (azd, recommended: v1.9 or later)

Deployment Steps

1. Deploy

azd up

Delete Resources

azd down

Hands-On

AI Agent / Claude Code → APIM → MCP

Reproduce the sequence of operations where an AI Agent (e.g., MS Foundry Agent) or Claude Code invokes MCP.
Both clients authenticate by acquiring an Azure access token upfront — AI Agent uses Python scripts, Claude Code uses .mcp.json with headersHelper.

sequenceDiagram
    participant Agent as AI Agent / Claude Code
    participant Entra as Entra ID
    participant APIM as API Management
    participant MCP as MCP Backend (Logic Apps)

    Agent->>Entra: 1) Request access token
    Entra-->>Agent: 2) Return access token

    Agent->>APIM: 3) Access /mcp with access token
    Note over APIM: 4) Validate access token
    APIM->>Entra: 5a) Acquire token for MCP Backend using APIM Managed Identity
    APIM->>MCP: 5b) Access MCP backend with APIM MSI token
    MCP-->>APIM: 6a) Return tool list
    APIM-->>Agent: 6b) Return tool list

    Agent->>APIM: 7) Request to execute MCP tool
    Note over APIM: 8) Validate MCP role from access token

    alt 9a) Role validation succeeds
        APIM->>MCP: 9a-1) Execute MCP tool with APIM MSI token
        MCP-->>APIM: 9a-2) Tool execution result
        APIM-->>Agent: 9a-3) Return MCP tool execution result
    else 9b) Role validation fails
        APIM-->>Agent: 403 Forbidden
    end

Steps

AI Agent (Python)

1. Set Up Python Environment

Navigate to the samplecodes/ directory, create and activate a Python virtual environment, and install the required Python packages.

cd samplecodes
python -m venv oauthmcp
source oauthmcp/bin/activate
pip install azure.identity

2. Configure Environment Variables

Set the required values dynamically from the azd environment.

export OAUTH_APP_ID="$(azd env get-value OAUTH_APP_ID)"
export MCP_URL="$(azd env get-value LOGICAPP_MCP_ENDPOINTS)"

3. Verify Access Token

Run check.entraid_token.py and verify that hello_project1 is included in the roles claim.

python check.entraid_token.py | grep -v '^===' \
  | jq 'if .roles then (if (.roles | any(. == "hello_project1")) then "✅ OK: hello_project1 is included in roles" else "❌ NG: hello_project1 not found in roles — run az logout && az login" end) else "⚠️ roles claim is missing — run az logout && az login" end'

4. Retrieve MCP Tool List

Run mcp_tool_list.py to verify that the tool list can be retrieved.

python mcp_tool_list.py

5. Execute MCP Tool (Success Case)

Execute the hello_project1 tool and confirm it succeeds.

export MCP_TOOL_NAME="hello_project1"
python mcp_tool_call.py

6. Execute MCP Tool (Rejection Case)

Execute the hello_project2 tool and confirm it is rejected due to lack of permissions.

export MCP_TOOL_NAME="hello_project2"
python mcp_tool_call.py
Claude Code

Claude Code connects to APIM-protected MCP servers using the same token-based flow.
The headersHelper field in .mcp.json calls az account get-access-token, so no interactive browser login is required.

1. Configure .mcp.json

Generate .mcp.json dynamically using azd env get-value.

cd $(git rev-parse --show-toplevel)
LA_URL="$(azd env get-value LOGICAPP_MCP_ENDPOINTS)"
OAUTH_APP_ID="$(azd env get-value OAUTH_APP_ID)"
HELPER="TOKEN=\$(az account get-access-token --scope 'api://${OAUTH_APP_ID}/.default' --query accessToken -o tsv) && echo \"{\\\"Authorization\\\": \\\"Bearer \$TOKEN\\\"}\""
jq -n --arg la_url "$LA_URL" --arg helper "$HELPER" \
  '{mcpServers: {
    "logic-hello-mcp": {type: "http", url: $la_url, headersHelper: $helper}
  }}' \
  > .mcp.json

2. Execute MCP Tool (Success Case)

Prompt the following in Claude Code and confirm that the MCP tool executes successfully.

Use logic-hello-mcp to say hello to project1

3. Execute MCP Tool (Rejection Case)

Prompt the following and confirm that execution is rejected due to lack of permissions.

Use logic-hello-mcp to say hello to project2

Tips

Why Azure CLI token instead of OAuth client credentials?
Claude Code supports OAuth via Dynamic Client Registration (DCR), but Entra ID does not. Static client registration would require distributing a client secret to each user, so headersHelper acquires a token via Azure CLI (az login) instead.

Access token expiry and reconnection
Entra ID access tokens are valid for 1 hour. headersHelper runs at startup and on reconnect, and Claude Code also re-runs it and retries once when a tool call returns 401 or 403 (e.g., an expired token).

Why pre-authorizing Azure CLI is safe
The token is scoped to this hands-on's Entra ID app (api://<OAUTH_APP_ID>), which is single-tenant, so only users who can sign in to this tenant can obtain one. The Terraform resource that enables this is:

resource "azuread_application_pre_authorized" "oauth_app" {
  application_id       = azuread_application.oauth_app.id
  authorized_client_id = "04b07795-8ddb-461a-bbee-02f9e1bf7b46" # Azure CLI

  permission_ids = [
    azuread_application_permission_scope.user_impersonation.scope_id,
  ]
}

VS Code (GitHub Copilot Chat / CLI) → APIM → MCP

Verify the sequence of operations where VS Code (GitHub Copilot Chat or CLI) invokes MCP.

sequenceDiagram
    participant VS as VS Code (GitHub Copilot Chat / CLI)
    participant APIM as API Management
    participant ID as Entra ID
    participant MCP as MCP Backend (Logic Apps)

    VS->>APIM: 1) Request access to /mcp
    APIM-->>VS: 2) 401 Unauthorized

    VS->>APIM: 3) GET /.well-known/oauth-protected-resource
    APIM-->>VS: 4) Return OAuth metadata

    VS->>ID: 5) Get an access token through the login/authorization code flow

    VS->>APIM: 6) Re-access /mcp with access token
    Note over APIM: 7) Validate access token
    APIM->>ID: 8a) Acquire token for MCP Backend using APIM Managed Identity
    APIM->>MCP: 8b) Access MCP backend with APIM MSI token
    MCP-->>APIM: 9a) Return tool list
    APIM-->>VS: 9b) Return tool list

    VS->>APIM: 10) Request to execute MCP tool
    Note over APIM: 11) Validate MCP role from access token

    alt 12a) Role validation succeeds
        APIM->>MCP: 12a-1) Execute MCP tool with APIM MSI token
        MCP-->>APIM: 12a-2) Tool execution result
        APIM-->>VS: 12a-3) Return MCP tool execution result
    else 12b) Role validation fails
        APIM-->>VS: 403 Forbidden
    end

Steps

GitHub Copilot Chat (agent mode)

1. Start MCP

Start the MCP server. When prompted for Entra ID authentication, authenticate through the browser and verify that the tool list is correctly retrieved.

2. Execute MCP Tool (Success Case)

Prompt the following and confirm that the MCP tool executes successfully.

Use logic-hello-mcp to say hello to project1

3. Execute MCP Tool (Rejection Case)

Prompt the following and confirm that execution is rejected due to lack of permissions.

Use logic-hello-mcp to say hello to project2
GitHub Copilot CLI

1. Start Copilot CLI

Launch the interactive session, specifying the .mcp.json generated in "Configure .mcp.json" under Claude Code above with --additional-mcp-config.

copilot --additional-mcp-config @.mcp.json

Note: If the browser does not open for MCP authorization when running Copilot CLI on WSL, refer to this article.

2. Execute MCP Tool (Success Case)

In the interactive session, send the following message and confirm that the MCP tool executes successfully.

Use logic-hello-mcp to say hello to project1

3. Execute MCP Tool (Rejection Case)

Send the following message and confirm that execution is rejected due to lack of permissions.

Use logic-hello-mcp to say hello to project2

Technical Details and Use Cases

Technical Details and Use Cases

Conclusion

This repository provides a hands-on experience with end-to-end security implementation combining API Management × MCP × OAuth.

A Note on Where This Sits

If you continue on to apim-mcp-a2a-oauth and then APICenter, you'll be walking a three-repository arc that also traces a shape from the tarot's Major Arcana.

This repository already holds both the Hierophant and the Hermit — not one after the other, but side by side. OAuth2, token validation, and role-based authorization rest on official documentation and established patterns: that's the Hierophant, learning what's already agreed upon. But forcing every caller through APIM as a token broker — a managed-identity token swap paired with Easy Auth on the backend, so direct access is closed off — and using PIM for Groups to grant an ops team JIT access, are original combinations of existing building blocks, not copied whole from a single guide. And the finding that APIM and the backend can silently disagree on which of two duplicate JSON keys wins — a possible authorization bypass — was only found by testing the actual deployed backend, not by reading about it anywhere. That's the Hermit, already at work right here.

The next repository carries the same mixture forward, just with the balance shifted further inward. And the one after that gathers all three back together, without mixing them, into a single undivided circle — the World. This repository's part in that is simply to be a solid first outline, so the later stages have something to stand on.

References