Azure MCP Platform is a proof of concept for governing Model Context Protocol (MCP) servers with Azure-native services.
It shows how an organization can register approved MCP servers, expose them through a governed gateway, and let VS Code with GitHub Copilot consume an approved MCP server through that gateway.
- About This Repository
- Current Status
- Conceptual Overview
- Architecture
- Authentication And Authorization
- Azure Resources
- Repository Structure
- Configuration
- Implementation Walkthrough
- Deploy
- Test
- Observability And Monitoring
- Security And Governance
- Cost Model
- Architecture Decisions
- Known Limitations
- Related Repositories
- References
This repository contains a public, reusable Azure proof of concept for an MCP registry and gateway architecture.
The documentation is written for readers who understand general cloud and developer platform concepts, but do not yet know this project. It should help a future maintainer, a new colleague, or an external reader understand what was built, why it was built, how it works, and how to reproduce it.
The goal is to prove a minimal enterprise-oriented pattern for centrally governing MCP server usage:
- Register an approved MCP server in a central registry.
- Route MCP traffic through a gateway.
- Enforce basic policy at the gateway.
- Validate that VS Code with GitHub Copilot can use the approved MCP server through that gateway.
This POC includes:
- Azure API Center as the MCP registry.
- Azure API Management as the MCP gateway.
- Microsoft Learn MCP as the first upstream MCP server.
- VS Code with GitHub Copilot as the MCP host.
- Terraform-managed Azure infrastructure.
- GitHub Actions validation and Terraform planning.
- Public-ready documentation and architecture records.
This POC does not include:
- Custom MCP server development.
- Production-grade private networking.
- Production SLA design.
- Multi-region deployment.
- Full Entra ID/OAuth implementation for MCP clients.
- Automated
terraform applyfrom GitHub Actions.
These items are intentionally deferred until the registry and gateway pattern is proven.
The technical POC is working.
The table below summarizes the current implementation status and the evidence behind each item.
| Area | Status | Evidence |
|---|---|---|
| Azure foundation | Complete | Terraform deployed API Center, API Management, and remote state resources |
| MCP registry | Complete | Microsoft Learn MCP is registered in API Center with governance metadata |
| MCP gateway | Complete | API Management exposes Microsoft Learn MCP through an MCP API endpoint |
| Policy enforcement | Complete | APIM validates Ocp-Apim-Subscription-Key through policy and applies rate limiting |
| Direct gateway test | Complete | APIM returned 200, text/event-stream, and mcp-session-id for MCP initialize |
| VS Code / Copilot test | Complete | VS Code discovered the Microsoft Learn MCP tools through APIM |
| Observability validation | Planned | APIM log validation should be added next |
| Target enterprise auth | Planned | Entra ID/OAuth remains the target architecture |
Validated Microsoft Learn MCP tools:
| Tool | Purpose |
|---|---|
microsoft_docs_search |
Search Microsoft Learn documentation |
microsoft_code_sample_search |
Search official code samples |
microsoft_docs_fetch |
Fetch Microsoft Learn documentation content |
MCP allows AI hosts such as VS Code and GitHub Copilot to call external tools. That is useful, but in an enterprise context it creates a governance problem:
How can approved MCP servers be discovered, governed, secured, and operated centrally?
This POC separates that problem into two concerns:
- Registry: Which MCP servers are approved, who owns them, what risk do they have, and where are they deployed?
- Gateway: How does runtime MCP traffic get routed, controlled, and protected before it reaches the upstream server?
The POC demonstrates this end-to-end path:
- A developer opens this repository in VS Code.
- VS Code reads the workspace MCP configuration from
.vscode/mcp.json. - VS Code starts the configured Remote HTTP MCP server.
- VS Code sends MCP traffic to Azure API Management.
- API Management validates the subscription key through an inbound policy.
- API Management applies rate limiting.
- API Management forwards valid traffic to Microsoft Learn MCP.
- GitHub Copilot discovers and can use the Microsoft Learn MCP tools.
- Azure API Center stores registry and governance metadata for the approved MCP server.
The important result is not only that MCP traffic works. The important result is that the traffic passes through an Azure governance point before it reaches the upstream server.
This table only describes the logical MCP platform components. It intentionally does not include Terraform, CI/CD, or repository tooling.
| Component | Implementation | Responsibility |
|---|---|---|
| MCP host | VS Code with GitHub Copilot Agent mode | Starts the MCP connection and makes tools available to the developer |
| MCP server | Microsoft Learn MCP | Provides read-only documentation and code sample tools |
| MCP registry | Azure API Center | Records approved MCP server metadata, ownership, risk, auth model, and deployment URL |
| MCP gateway | Azure API Management | Receives MCP traffic, validates access, applies policy, and forwards valid requests upstream |
This table describes the supporting engineering components that make the POC reproducible and maintainable.
| Component | Implementation | Responsibility |
|---|---|---|
| Infrastructure as code | Terraform | Defines Azure resources reproducibly |
| Terraform state | Azure Storage | Stores shared remote state outside the local machine |
| CI validation | GitHub Actions | Runs Terraform validation and plan checks |
| Agent instructions | AGENTS.md |
Captures project working conventions for Codex and other agents |
| Project documentation | README.md plus supporting docs/ artifacts |
Makes the architecture understandable and reusable |
The POC has two architecture planes.
The table below explains the role of each plane before the diagrams go into detail.
| Plane | Purpose | Azure Service |
|---|---|---|
| Governance plane | Catalog approved MCP servers and describe ownership, risk, auth, exposure, and runtime deployment | Azure API Center |
| Runtime plane | Route MCP traffic, validate access, apply policy, and protect upstream MCP servers | Azure API Management |
The first upstream MCP server is Microsoft Learn MCP at https://learn.microsoft.com/api/mcp.
This diagram shows who interacts with the POC and which external systems are involved.
flowchart LR
user[Developer]
vscode[VS Code<br/>GitHub Copilot Agent Mode]
repo[GitHub Repository<br/>azure-mcp-platform]
apic[Azure API Center<br/>MCP Registry]
apim[Azure API Management<br/>MCP Gateway]
learn[Microsoft Learn MCP Server<br/>https://learn.microsoft.com/api/mcp]
user -->|opens workspace| vscode
user -->|reads and updates docs/code| repo
repo -->|workspace config<br/>.vscode/mcp.json| vscode
repo -->|Terraform-managed metadata| apic
repo -->|Terraform-managed gateway config| apim
vscode -->|MCP over HTTP<br/>Ocp-Apim-Subscription-Key| apim
apim -->|forwards valid MCP traffic| learn
apic -.->|documents approved server,<br/>owner, auth, risk, deployment URL| apim
Alternative rendered diagram variants are available in docs/diagram-variants.
This diagram shows the Azure resources deployed for the POC and how they relate to each other.
flowchart TB
subgraph rg[Resource Group<br/>rg-<project>-<env>]
st[Storage Account<br/><unique-storage-account-name>]
container[Blob Container<br/>tfstate]
apic[API Center<br/>apic-<project>-<env>]
apicApi[API Center API<br/>microsoft-learn-mcp<br/>kind: mcp]
apicEnv[API Center Environment<br/>apim-dev]
apicDeployment[API Center Deployment<br/>apim-dev]
apim[API Management<br/>apim-<project>-<env><br/>Developer tier]
product[APIM Product<br/>mcp-poc]
api[APIM API<br/>microsoft-learn-mcp<br/>apiType: mcp]
subscription[APIM Subscription<br/>MCP POC test subscription]
namedValue[APIM Named Value<br/>mcp-poc-gateway-key<br/>secret]
policy[APIM API Policy<br/>key validation + rate limit]
end
st --> container
apic --> apicApi
apic --> apicEnv
apicApi --> apicDeployment
apicEnv --> apicDeployment
apicDeployment -.->|runtime URL| api
apim --> product
apim --> api
product --> subscription
product --> api
subscription -->|primary key stored as secret| namedValue
api --> policy
namedValue --> policy
Key implementation detail: the APIM MCP API is managed with azapi_resource because the AzureRM provider does not expose every required MCP API shape as a first-class resource yet.
This sequence shows what happens when VS Code starts the MCP server and discovers tools through APIM.
sequenceDiagram
autonumber
actor Developer
participant VSCode as VS Code / GitHub Copilot
participant APIM as Azure API Management
participant Policy as APIM Inbound Policy
participant Learn as Microsoft Learn MCP
Developer->>VSCode: Start microsoftLearnMcpViaApim
VSCode->>Developer: Prompt for APIM subscription key
Developer->>VSCode: Enter key locally
VSCode->>APIM: POST /microsoft-learn-mcp/mcp initialize
APIM->>Policy: Validate Ocp-Apim-Subscription-Key
Policy->>Policy: Apply rate limit
Policy-->>APIM: Allow request
APIM->>Learn: Forward MCP initialize
Learn-->>APIM: text/event-stream + mcp-session-id
APIM-->>VSCode: MCP initialize result
VSCode->>APIM: tools/list
APIM->>Policy: Validate key and rate limit
APIM->>Learn: Forward tools/list
Learn-->>APIM: Microsoft Learn tools
APIM-->>VSCode: Tool list
VSCode-->>Developer: Discovered 3 tools
The APIM subscription key is entered locally in VS Code and is not committed to Git.
This diagram shows how local development, GitHub, Terraform, and Azure operations fit together.
flowchart LR
local[Local Workspace<br/>Codex + VS Code]
git[Local Git Repository]
github[GitHub Repository<br/>main branch]
actions[GitHub Actions<br/>Terraform validate/plan]
tf[Terraform CLI<br/>manual apply]
state[Azure Storage<br/>remote Terraform state]
azure[Azure Resources<br/>API Center + APIM]
docs[Docs / Obsidian Vault<br/>docs/]
local -->|edit code, docs, Terraform| git
local --> docs
git -->|commit + push| github
github --> actions
actions -->|plan only via OIDC| azure
tf -->|read/write state| state
tf -->|manual apply| azure
azure -->|runtime test| local
For this POC, GitHub Actions validates and plans Terraform only. terraform apply remains manual.
The current POC uses an APIM subscription key because it is simple enough to validate the gateway pattern quickly.
The table below describes each authentication-related step in the current flow.
| Step | Actor | What Happens |
|---|---|---|
| 1 | VS Code | Prompts the local user for the APIM subscription key |
| 2 | VS Code | Sends the key in the Ocp-Apim-Subscription-Key header |
| 3 | APIM inbound policy | Reads the header and compares it with a secret APIM named value |
| 4 | APIM inbound policy | Rejects missing or invalid keys with 401 |
| 5 | APIM inbound policy | Allows valid requests and applies rate limiting |
| 6 | APIM | Forwards valid MCP traffic to Microsoft Learn MCP |
APIM has built-in subscription-key support. During the POC, VS Code Remote HTTP MCP interpreted the built-in APIM authentication challenge as an OAuth/Dynamic Client Registration path.
To keep the POC simple, APIM native subscriptionRequired is disabled for this MCP API and the same key is checked by policy instead. This keeps the architecture on direct Remote HTTP MCP while still proving gateway policy enforcement.
This is a POC choice, not the long-term enterprise authentication model.
The target enterprise direction is Entra ID/OAuth with an MCP-compliant authorization pattern.
| Area | POC | Target Direction |
|---|---|---|
| Authentication | APIM subscription key checked by policy | Entra ID/OAuth with MCP-compliant authorization |
| Network exposure | Public APIM endpoint for local testing | Private networking, VPN, Dev Box, or controlled developer environment |
| Gateway tier | APIM Developer tier | Production-grade tier based on SLA, scale, private networking, and observability needs |
| Server scope | One read-only MCP server | Multiple approved MCP servers with registry-driven discovery |
This table lists the Azure resources and logical objects used by the POC. It answers: "What exists in Azure, and what role does it play?"
| Resource Or Object | Example Pattern | Role In The POC |
|---|---|---|
| Resource group | rg-<project>-<env> |
Groups all Azure resources for the development environment |
| Storage account | <unique-storage-account-name> |
Stores Terraform remote state |
| Blob container | tfstate |
Holds the Terraform state blob |
| API Center | apic-<project>-<env> |
Acts as the MCP registry |
| API Center API | microsoft-learn-mcp |
Represents the approved Microsoft Learn MCP server |
| API Center environment | apim-dev |
Represents the APIM development gateway environment |
| API Center deployment | apim-dev |
Points registry metadata to the APIM runtime URL |
| API Management | apim-<project>-<env> |
Acts as the MCP gateway |
| APIM API | microsoft-learn-mcp |
Exposes the MCP endpoint through APIM |
| APIM product | mcp-poc |
Groups gateway access for the POC |
| APIM subscription | mcp-poc subscription |
Provides the subscription key used by VS Code |
| APIM named value | mcp-poc-gateway-key |
Stores the expected key as a secret for policy comparison |
| APIM API policy | Inbound policy | Validates the key and applies rate limiting |
This table lists the URLs a reader needs when testing or reasoning about the runtime path.
| Purpose | Endpoint |
|---|---|
| APIM gateway base URL | https://<apim-name>.azure-api.net |
| Microsoft Learn MCP through APIM | https://<apim-name>.azure-api.net/microsoft-learn-mcp/mcp |
| Upstream Microsoft Learn MCP | https://learn.microsoft.com/api/mcp |
| API Center MCP registry | https://<api-center-name>.data.<region>.azure-apicenter.ms/workspaces/default/v0.1/servers |
.
├── AGENTS.md
├── README.md
├── SECURITY.md
├── LICENSE
├── .github/
│ ├── azure-federated-credential.example.json
│ └── workflows/terraform-validate.yml
├── .vscode/
│ └── mcp.json
├── docs/
│ ├── decisions/
│ ├── diagram-variants/
│ ├── diagrams/
│ ├── mcp-compatibility-assessment.md
│ ├── mcp-governance-model.md
│ ├── runbooks/
│ └── references.md
└── infra/terraform/
The table below explains the repository folders and files that matter most to a new reader.
| Path | Purpose |
|---|---|
| README.md | Main project documentation and first entry point |
| AGENTS.md | Project working conventions for Codex and other coding agents |
| .vscode/mcp.json | VS Code Remote HTTP MCP server configuration |
| infra/terraform | Terraform implementation for Azure resources |
| docs/decisions | Architecture Decision Records |
| docs/mcp-compatibility-assessment.md | Compatibility matrix for future MCP host/server integration candidates |
| docs/mcp-governance-model.md | Work-in-progress governance model for remote, local, API-to-MCP, and Copilot integration patterns |
| docs/runbooks | Operational procedures and manual test instructions |
| docs/diagram-variants | Alternative diagram sources and rendered variants |
| docs/references.md | Consolidated source references |
The root README is the primary documentation surface. Files under docs/ are supporting artifacts, runbooks, or appendices.
Create a local variables file from the example:
cp infra/terraform/environments/dev/terraform.tfvars.example \
infra/terraform/environments/dev/terraform.tfvarsThen replace the placeholder values in terraform.tfvars.
Local *.tfvars files are ignored by Git. Keep them local because they can contain personal, subscription-specific, or environment-specific values.
The Terraform validation workflow expects these repository variables.
| Variable | Purpose |
|---|---|
AZURE_CLIENT_ID |
Entra app registration client ID for GitHub OIDC |
AZURE_TENANT_ID |
Entra tenant ID |
AZURE_SUBSCRIPTION_ID |
Azure subscription ID |
TF_STATE_RESOURCE_GROUP_NAME |
Resource group containing Terraform state storage |
TF_STATE_STORAGE_ACCOUNT_NAME |
Storage account for Terraform state |
TF_STATE_CONTAINER_NAME |
Blob container for Terraform state |
TF_STATE_KEY |
State blob name, for example dev.terraform.tfstate |
API_CENTER_NAME |
API Center instance name |
API_MANAGEMENT_NAME |
API Management instance name |
API_MANAGEMENT_PUBLISHER_EMAIL |
APIM publisher email |
The workspace MCP configuration lives in .vscode/mcp.json.
It defines one Remote HTTP MCP server.
| Setting | Value |
|---|---|
| Server name | microsoftLearnMcpViaApim |
| Type | http |
| URL | https://<apim-name>.azure-api.net/microsoft-learn-mcp/mcp |
| Header | Ocp-Apim-Subscription-Key |
| Secret handling | VS Code prompts locally; the key is not committed |
This section explains the logical setup sequence. It is not a click-by-click deployment runbook; it is meant to help a reader understand how the pieces were assembled.
| Step | What Was Set Up | Why It Matters |
|---|---|---|
| 1 | GitHub repository | Provides versioned source control for Terraform, docs, decisions, and runbooks |
| 2 | Azure foundation | Provides the subscription, resource group, and remote state foundation for repeatable infrastructure |
| 3 | Terraform backend | Moves state out of the local machine and into Azure Storage |
| 4 | GitHub OIDC | Lets GitHub Actions plan against Azure without long-lived cloud credentials |
| 5 | Azure API Center | Creates the registry for approved MCP server metadata |
| 6 | Azure API Management | Creates the runtime gateway for MCP traffic |
| 7 | Microsoft Learn MCP APIM API | Exposes the upstream MCP server through the gateway |
| 8 | APIM policy | Validates the key and applies rate limiting |
| 9 | API Center metadata | Records owner, risk, auth type, exposure, and runtime deployment URL |
| 10 | VS Code MCP config | Lets VS Code/GitHub Copilot connect to the APIM MCP endpoint |
| 11 | End-to-end test | Proves that Copilot can discover Microsoft Learn MCP tools through APIM |
Prerequisites:
| Tool | Purpose |
|---|---|
| Azure CLI | Azure login and subscription context |
| Terraform CLI | Infrastructure deployment |
| GitHub CLI | GitHub repository and workflow setup |
| VS Code | MCP host validation |
| GitHub Copilot | MCP tool consumption from Copilot Chat |
Initialize Terraform:
terraform -chdir=infra/terraform initValidate Terraform:
terraform -chdir=infra/terraform validatePlan Terraform:
SUB=$(az account show --query id -o tsv)
TENANT=$(az account show --query tenantId -o tsv)
terraform -chdir=infra/terraform plan \
-var-file="environments/dev/terraform.tfvars" \
-var="subscription_id=$SUB" \
-var="tenant_id=$TENANT"Apply manually:
terraform -chdir=infra/terraform apply \
-var-file="environments/dev/terraform.tfvars" \
-var="subscription_id=$SUB" \
-var="tenant_id=$TENANT"The direct gateway test verifies APIM before VS Code is involved.
| Test | Expected Result |
|---|---|
| Valid APIM key | 200, text/event-stream, mcp-session-id |
| Missing APIM key | 401 from APIM policy |
| Invalid APIM key | 401 from APIM policy |
| Repeated calls beyond limit | 429 throttling response |
Get the APIM subscription key:
terraform -chdir=infra/terraform output -raw mcp_poc_subscription_primary_keyThen:
- Open this repository in VS Code.
- Open the Command Palette with
Shift+Command+P. - Run
MCP: List Servers. - Select
microsoftLearnMcpViaApim. - Choose
Start Server. - Enter the APIM subscription key when prompted.
- Open GitHub Copilot Chat in Agent mode.
- Confirm the Microsoft Learn MCP tools are available.
Expected VS Code output:
Starting server microsoftLearnMcpViaApim
Connection state: Running
Discovered 3 tools
Prompt example:
Use the Microsoft Learn MCP server to find official guidance for exposing an existing MCP server through Azure API Management.
Detailed runbook: docs/runbooks/vscode-copilot-mcp-test.md
Observability is planned but not fully documented yet.
The POC should be able to show that gateway operation is visible, not only that connectivity works. The table below defines the observability checks that should be added next.
| Check | Purpose | Status |
|---|---|---|
| APIM request logs | Show requests to /microsoft-learn-mcp/mcp |
Planned |
| Status code visibility | Confirm successful, unauthorized, and throttled requests are visible | Planned |
| Latency visibility | Understand gateway and upstream response behavior | Planned |
| Failed key attempts | Prove that denied requests can be audited | Planned |
| Rate-limit events | Prove that throttling is visible operationally | Planned |
This table summarizes the current controls. It answers: "How is the POC governed today?"
| Control | Implementation |
|---|---|
| Gateway access | APIM requires Ocp-Apim-Subscription-Key |
| Key validation | APIM inbound policy compares the header against a secret named value |
| Rate limiting | APIM policy limits calls per time window |
| Registry metadata | API Center records owner, status, risk, auth type, network exposure, tool access, and purpose |
| Terraform state | Remote state stored in Azure Storage |
| CI authentication | GitHub Actions uses OIDC for Azure-backed Terraform plans |
| Secret hygiene | Local *.tfvars, state files, plans, and credentials are ignored by Git |
API Center records the Microsoft Learn MCP server with governance metadata.
| Field | Example Value |
|---|---|
owner |
platform-team |
environment |
dev |
status |
approved |
riskLevel |
low |
dataClassification |
public |
authType |
subscription-key |
networkExposure |
public-poc |
toolAccess |
read-only |
businessPurpose |
Microsoft Learn documentation lookup through governed MCP access |
upstreamServer |
https://learn.microsoft.com/api/mcp |
approvedForHosts |
VS Code, GitHub Copilot |
lastReviewed |
Review date for the registry entry |
documentationUrl |
https://learn.microsoft.com/ |
The main cost driver is Azure API Management.
This table separates the cost-relevant components and the expected cost character.
| Component | Cost Character |
|---|---|
| API Management Developer tier | Ongoing monthly cost while provisioned; non-production and no SLA |
| API Center | Depends on Azure pricing and SKU availability |
| Storage account for Terraform state | Very small cost for this POC |
| Microsoft Learn MCP | External Microsoft endpoint; no custom hosting cost in this repo |
To avoid unnecessary spend, deprovision the Azure resources when the POC is not needed.
This table summarizes the durable decisions behind the current POC architecture.
| Decision | Chosen Option | Why |
|---|---|---|
| Registry | Azure API Center | Azure-native catalog for approved MCP server metadata and discovery |
| Gateway | Azure API Management | Azure-native policy enforcement, routing, subscription keys, and rate limiting |
| Initial MCP server | Microsoft Learn MCP | Useful read-only tools without custom server development |
| Initial host | VS Code with GitHub Copilot | Real target developer experience |
| Authentication for POC | Subscription key checked by APIM policy | Fast validation without OAuth setup |
| Enterprise auth direction | Entra ID/OAuth | Better fit for identity-based enterprise governance |
| Network model for POC | Public APIM endpoint | Faster local testing |
| Target network model | Private or controlled access path | Better enterprise security posture |
| Apply model | Manual Terraform apply | Keeps POC changes deliberate while CI validates and plans |
Detailed ADR: ADR-001: Use Azure API Center and API Management for the MCP Registry/Gateway POC
This table lists the known boundaries of the current POC so readers do not mistake it for a production architecture.
| Limitation | Impact | Follow-Up |
|---|---|---|
| APIM Developer tier has no SLA | Not production-ready | Evaluate production tiers later |
| Public APIM endpoint | Easier to test, weaker than private enterprise pattern | Add private networking or controlled developer access path |
| Subscription key auth | Good POC shortcut, weaker than identity-based auth | Design Entra ID/OAuth target architecture |
| One upstream MCP server in this repository | Proves the core Microsoft Learn pattern, not full registry scale | See the linked Firecrawl and ESLint follow-up POCs for third-party remote and local MCP patterns |
| Observability not fully documented | Gateway operations are not yet easy to demonstrate | Add APIM log checks and Azure Monitor validation |
This repository contains only the Azure MCP Platform POC.
| Repository | Purpose | Visibility Intent |
|---|---|---|
azure-mcp-platform |
Concrete Azure MCP registry/gateway POC | Public reference POC |
azure-mcp-firecrawl-poc |
Remote third-party MCP gateway POC with Firecrawl, APIM, and API Center | Public reference POC |
mcp-eslint-local-poc |
Local MCP governance POC with ESLint, VS Code, and GitHub Copilot | Public reference POC |
codex-cloud-workbench |
Personal cloud/Codex enablement and local workbench | Private |
codex-azure-project-template |
Reusable Azure/Codex project template and standards | Private |
Core references used for this POC:
| Area | Reference |
|---|---|
| MCP architecture | Model Context Protocol architecture |
| MCP authorization | MCP authorization specification |
| VS Code MCP servers | VS Code MCP servers |
| VS Code MCP configuration | VS Code MCP configuration reference |
| APIM MCP gateway | Expose and govern an existing MCP server with Azure API Management |
| API Center MCP registry | Register and discover MCP servers in Azure API Center |
| API Center metadata | Set metadata properties in Azure API Center |
| Terraform Azure backend | Terraform AzureRM backend |
| GitHub OIDC for Azure | GitHub Actions OIDC in Azure |
| API Management pricing | Azure API Management pricing |
Additional references are collected in docs/references.md.