Note
Access to this page requires authorization. You can try signing in or changing directories.
Access to this page requires authorization. You can try changing directories.
In this article, you learn how to create and manage MCP servers in Azure API Management by using the REST API, ARM templates, Bicep, the Azure CLI, and Terraform.
Important
The MCP server management features described in this article require API Management REST API version 2025-09-01-preview or later. Pin this version in every request.
For background about MCP server capabilities, see About MCP servers in Azure API Management.
Prerequisites
Your identity needs permissions to read the API Management service and create or update APIs, API tools, API policies, and product API bindings. For Terraform, the identity also needs read access to the existing API Management service used by the
azurerm_api_managementdata source.For Azure CLI:
Use the Bash environment in Azure Cloud Shell. For more information, see Get started with Azure Cloud Shell.
If you prefer to run CLI reference commands locally, install the Azure CLI. If you're running on Windows or macOS, consider running Azure CLI in a Docker container. For more information, see How to run the Azure CLI in a Docker container.
If you're using a local installation, sign in to the Azure CLI by using the az login command. To finish the authentication process, follow the steps displayed in your terminal. For other sign-in options, see Authenticate to Azure using Azure CLI.
When you're prompted, install the Azure CLI extension on first use. For more information about extensions, see Use and manage extensions with the Azure CLI.
Run az version to find the version and dependent libraries that are installed. To upgrade to the latest version, run az upgrade.
For Azure PowerShell:
- If you choose to use Azure PowerShell locally:
- Install the latest version of the Az PowerShell module.
- Connect to your Azure account using the Connect-AzAccount cmdlet.
- If you choose to use Azure Cloud Shell:
- See Overview of Azure Cloud Shell for more information.
- If you choose to use Azure PowerShell locally:
For Terraform: Install and configure Terraform
Resource model
Azure Resource Manager represents MCP servers as follows:
Passthrough server: Points at an existing external MCP backend. The MCP server resource declares the backend URL and transport type (streamable HTTP or SSE).
Tool: An API tool sub-resource of an MCP server. You can safely manage tool resources from CI/CD. You can add, rename, or remove tools without recreating the MCP server.
Policies: As with regular APIs, attach API policy or policy sub-resources to an MCP server.
Products: Product binding is a separate child relationship (
products/{productId}/apis/{mcpServerId}), enabling independent deployment and multi-product binding.
REST examples
For clarity, the following examples show abbreviated response bodies. For full response schemas, see the API Management REST API reference.
Adding the If-Match: * header in the PUT and DELETE call examples makes the requests idempotent. This header applies whether or not the resource already exists, which is the recommended pattern for CI/CD pipelines.
Before you begin
Set the following variables before running any example. All examples in this section reference these variables.
SUBSCRIPTION_ID="<your-subscription-id>"
RESOURCE_GROUP="<your-resource-group>"
APIM_NAME="<your-api-management-service-name>"
API_VERSION="2025-09-01-preview"
BASE_URL="https://management.azure.com/subscriptions/${SUBSCRIPTION_ID}/resourceGroups/${RESOURCE_GROUP}/providers/Microsoft.ApiManagement/service/${APIM_NAME}"
TOKEN=$(az account get-access-token --resource https://management.azure.com --query accessToken -o tsv)
List MCP servers
Returns all APIs in the instance filtered to type mcp. Use the $top and $skip query parameters to page through large result sets.
Reference: Api - List By Service
curl -sG "${BASE_URL}/apis" \
--data-urlencode "api-version=${API_VERSION}" \
--data-urlencode "\$filter=type eq 'mcp'" \
-H "Authorization: Bearer ${TOKEN}"
Response (200 OK)
{
"count": 1,
"value": [
{
"id": "/subscriptions/.../apis/my-mcp-server",
"name": "my-mcp-server",
"type": "Microsoft.ApiManagement/service/apis",
"properties": {
"type": "mcp",
"displayName": "My MCP Server",
"path": "my-mcp",
"protocols": [ "https" ]
}
}
]
}
Common error: 401 Unauthorized. The bearer token is expired. Re-run the token acquisition command.
Get a single MCP server
Reference: Api - Get
MCP_SERVER_ID="my-mcp-server"
curl -s "${BASE_URL}/apis/${MCP_SERVER_ID}?api-version=${API_VERSION}" \
-H "Authorization: Bearer ${TOKEN}"
Response (200 OK)
{
"id": "/subscriptions/.../apis/my-mcp-server",
"name": "my-mcp-server",
"type": "Microsoft.ApiManagement/service/apis",
"properties": {
"type": "mcp",
"displayName": "My MCP Server",
"path": "my-mcp",
"protocols": [ "https" ],
"serviceUrl": "https://api.contoso.com"
}
}
Common error: 404 Not Found. Confirm that mcpServerId matches the name field returned by the List operation.
Create a REST API-backed MCP server
Creates the MCP server resource. After creation, add tools to it individually by using the Add or update a tool operation. Each tool references a specific operation in a backing REST API resource.
Reference: Api - Create Or Update
MCP_SERVER_ID="my-mcp-server"
curl -s -X PUT \
"${BASE_URL}/apis/${MCP_SERVER_ID}?api-version=${API_VERSION}" \
-H "Authorization: Bearer ${TOKEN}" \
-H "Content-Type: application/json" \
-H "If-Match: *" \
-d '{
"properties": {
"type": "mcp",
"path": "my-mcp",
"displayName": "My MCP Server",
"description": "MCP server backed by a REST API",
"protocols": ["https"]
}
}'
Response (201 Created)
{
"id": "/subscriptions/.../apis/my-mcp-server",
"name": "my-mcp-server",
"type": "Microsoft.ApiManagement/service/apis",
"properties": {
"type": "mcp",
"displayName": "My MCP Server",
"path": "my-mcp",
"protocols": ["https"],
"provisioningState": "InProgress"
}
}
Note
provisioningState: InProgress is expected for async PUT operations. Poll the URL returned in the Azure-AsyncOperation response header to confirm completion.
Common error: 400 Bad Request. Ensure type is "mcp" and path is unique within the service instance.
Create a passthrough MCP server
A passthrough server forwards all MCP requests directly to an external MCP backend. Set mcpProperties.transportType to match the transport that your backend implements.
Before creating a passthrough server, confirm that the backend is reachable from the API Management gateway and implements the selected MCP transport at the endpoint paths you configure. If the backend requires authentication, configure the required credentials or headers by using API Management policies or backend configuration.
Reference: Api - Create Or Update
Streamable HTTP transport
Use streamable for backends that implement the current MCP streamable HTTP transport specification. A single endpoint definition is required.
MCP_SERVER_ID="my-mcp-passthrough"
curl -s -X PUT \
"${BASE_URL}/apis/${MCP_SERVER_ID}?api-version=${API_VERSION}" \
-H "Authorization: Bearer ${TOKEN}" \
-H "Content-Type: application/json" \
-H "If-Match: *" \
-d '{
"properties": {
"type": "mcp",
"path": "my-mcp-passthrough",
"displayName": "My Passthrough MCP Server",
"description": "Passthrough MCP server using streamable HTTP transport",
"protocols": ["https"],
"serviceUrl": "https://mcp-backend.contoso.com",
"mcpProperties": {
"transportType": "streamable",
"endpoints": [
{ "name": "message", "uriTemplate": "/mcp" }
]
}
}
}'
Response (201 Created)
{
"id": "/subscriptions/.../apis/my-mcp-passthrough",
"name": "my-mcp-passthrough",
"type": "Microsoft.ApiManagement/service/apis",
"properties": {
"type": "mcp",
"displayName": "My Passthrough MCP Server",
"path": "my-mcp-passthrough",
"protocols": ["https"],
"serviceUrl": "https://mcp-backend.contoso.com",
"provisioningState": "InProgress",
"mcpProperties": {
"transportType": "streamable",
"endpoints": [ { "name": "message", "uriTemplate": "/mcp" } ]
}
}
}
SSE transport
Use sse for backends that implement the HTTP+SSE (Server-Sent Events) transport. Define two endpoints: one for the SSE event stream and one for the message channel.
MCP_SERVER_ID="my-mcp-sse"
curl -s -X PUT \
"${BASE_URL}/apis/${MCP_SERVER_ID}?api-version=${API_VERSION}" \
-H "Authorization: Bearer ${TOKEN}" \
-H "Content-Type: application/json" \
-H "If-Match: *" \
-d '{
"properties": {
"type": "mcp",
"path": "my-mcp-sse",
"displayName": "My SSE MCP Server",
"description": "Passthrough MCP server using SSE transport",
"protocols": ["https"],
"serviceUrl": "https://mcp-backend.contoso.com",
"mcpProperties": {
"transportType": "sse",
"endpoints": [
{ "name": "sse", "uriTemplate": "/sse" },
{ "name": "message", "uriTemplate": "/messages" }
]
}
}
}'
Common errors:
400 Bad Request. InvalidmcpProperties. VerifytransportTypeisstreamableorsse, and everyuriTemplatestarts with/.400 Bad Request. SSE transport requires exactly two endpoints (sseandmessage). Streamable transport requires one (message).
Add or update a tool
Adds a new tool to a REST API-backed MCP server, or updates an existing one. The operationId field links the tool to a specific operation in a backing REST API resource. You can add, update, or remove tools independently without recreating the parent server.
Reference: Api Tool - Create Or Update
MCP_SERVER_ID="my-mcp-server"
TOOL_ID="listOrders"
BACKING_API_ID="orders-api"
BACKING_OP_ID="list-orders"
OP_ID="/subscriptions/${SUBSCRIPTION_ID}/resourceGroups/${RESOURCE_GROUP}/providers/Microsoft.ApiManagement/service/${APIM_NAME}/apis/${BACKING_API_ID}/operations/${BACKING_OP_ID}"
curl -s -X PUT \
"${BASE_URL}/apis/${MCP_SERVER_ID}/tools/${TOOL_ID}?api-version=${API_VERSION}" \
-H "Authorization: Bearer ${TOKEN}" \
-H "Content-Type: application/json" \
-H "If-Match: *" \
--data-raw "{
\"properties\": {
\"displayName\": \"listOrders\",
\"description\": \"List all orders for a customer\",
\"operationId\": \"${OP_ID}\"
}
}"
Response (201 Created)
{
"id": "/subscriptions/.../apis/my-mcp-server/tools/listOrders",
"name": "listOrders",
"type": "Microsoft.ApiManagement/service/apis/tools",
"properties": {
"displayName": "listOrders",
"description": "List all orders for a customer",
"operationId": "/subscriptions/.../apis/orders-api/operations/list-orders"
}
}
Common errors:
400 Bad Request. TheoperationIdpath is malformed or the referenced operation doesn't exist.404 Not Found. The parent MCP server doesn't exist. Create the server before adding tools.
Delete a tool
Removes a tool from an MCP server. Delete tools before deleting the backing REST API operations they reference; otherwise, the delete fails with a dependency error.
Reference: Api Tool - Delete
MCP_SERVER_ID="my-mcp-server"
TOOL_ID="listOrders"
curl -s -X DELETE \
"${BASE_URL}/apis/${MCP_SERVER_ID}/tools/${TOOL_ID}?api-version=${API_VERSION}" \
-H "Authorization: Bearer ${TOKEN}" \
-H "If-Match: *"
Response: 200 OK on success.
Common error: 412 Precondition Failed — If-Match is required for deletions. Use If-Match: * to match any ETag.
Apply a policy at MCP scope
Creates or replaces the policy document attached to an MCP server. The server evaluates policies at this scope for every tool invocation. The rawxml format accepts unencoded policy XML.
Reference: Api Policy - Create Or Update
MCP_SERVER_ID="my-mcp-server"
POLICY='<policies><inbound><base /><rate-limit calls="100" renewal-period="60" /></inbound><backend><forward-request /></backend><outbound><base /></outbound></policies>'
curl -s -X PUT \
"${BASE_URL}/apis/${MCP_SERVER_ID}/policies/policy?api-version=${API_VERSION}" \
-H "Authorization: Bearer ${TOKEN}" \
-H "Content-Type: application/json" \
-H "If-Match: *" \
--data-raw "{\"properties\":{\"format\":\"rawxml\",\"value\":\"${POLICY}\"}}"
Response (200 OK)
{
"id": "/subscriptions/.../apis/my-mcp-server/policies/policy",
"name": "policy",
"type": "Microsoft.ApiManagement/service/apis/policies",
"properties": {
"value": "<policies>...</policies>"
}
}
Common error: 400 Bad Request. Malformed policy XML. Validate the document before sending.
Bind an MCP server to a product
Associates the MCP server with a product, so subscribers of that product can call the server's tools. The request has no body.
When you bind the MCP server to a product, you make it available through that product, but clients still need access according to the product's configuration. If the product requires subscriptions, the client must use a valid subscription key for that product.
Reference: Product Api - Create Or Update
MCP_SERVER_ID="my-mcp-server"
PRODUCT_ID="my-product"
curl -s -X PUT \
"${BASE_URL}/products/${PRODUCT_ID}/apis/${MCP_SERVER_ID}?api-version=${API_VERSION}" \
-H "Authorization: Bearer ${TOKEN}" \
-H "Content-Length: 0"
Response: 201 Created with the MCP server's API contract in the body.
Common error: 404 Not Found. Verify that both productId and mcpServerId exist before creating the binding.
Delete an MCP server
Deletes an MCP server and all its tool and policy sub-resources. Before deleting the server, remove any tools that reference operations in backing APIs; otherwise, you can't delete those operations while the tool reference exists.
Reference: Api - Delete
MCP_SERVER_ID="my-mcp-server"
curl -s -X DELETE \
"${BASE_URL}/apis/${MCP_SERVER_ID}?api-version=${API_VERSION}" \
-H "Authorization: Bearer ${TOKEN}" \
-H "If-Match: *"
Response: 200 OK on success.
Common error: 412 Precondition Failed. If-Match is required for deletions. Use If-Match: * to bypass ETag checking.
ARM and Bicep templates
The following templates deploy a complete MCP server configuration in a single deployment. Each template assumes an existing API Management service instance and uses a parameter table so you can reuse the same file across environments by changing only the parameter values.
REST API-backed MCP server
These templates create an MCP server, define one tool that maps to an operation in an existing backing REST API, attach a rate-limit policy at the server scope, and bind the server to an existing product. You can do all these tasks in a single deployment.
Prerequisites: an existing API Management service, a REST API (backingApiId) with at least one operation (backingOperationId), and an existing product (productId).
| Parameter | Required | Default | Description |
|---|---|---|---|
serviceName |
Yes | — | Name of the existing API Management service instance. |
mcpServerId |
No | orders-mcp |
Resource name for the new MCP server. Must be unique within the service. |
backingApiId |
Yes | — | Resource name of the existing REST API that backs this MCP server. |
backingOperationId |
Yes | — | Resource name of the operation to expose as a tool. |
toolId |
No | sampleTool |
Resource name and display name of the MCP tool to create. |
productId |
No | starter |
Resource name of the existing product to bind the server to. |
@description('Name of the existing API Management service instance.')
param serviceName string
@description('Resource name for the new MCP server.')
param mcpServerId string = 'orders-mcp'
@description('Resource name of the existing REST API that backs this MCP server.')
param backingApiId string
@description('Resource name of the operation in the backing REST API to expose as a tool.')
param backingOperationId string
@description('Resource name and display name of the MCP tool to create.')
param toolId string = 'sampleTool'
@description('Resource name of the existing product to bind the MCP server to.')
param productId string = 'starter'
resource apimService 'Microsoft.ApiManagement/service@2025-09-01-preview' existing = {
name: serviceName
}
resource mcpServer 'Microsoft.ApiManagement/service/apis@2025-09-01-preview' = {
parent: apimService
name: mcpServerId
properties: {
type: 'mcp'
displayName: 'Orders MCP Server'
description: 'MCP server backed by the Orders REST API'
path: mcpServerId
protocols: [ 'https' ]
subscriptionRequired: true
}
}
resource mcpTool 'Microsoft.ApiManagement/service/apis/tools@2025-09-01-preview' = {
parent: mcpServer
name: toolId
properties: {
displayName: toolId
description: 'MCP tool backed by an API operation'
operationId: resourceId(
'Microsoft.ApiManagement/service/apis/operations',
serviceName, backingApiId, backingOperationId
)
}
}
resource mcpPolicy 'Microsoft.ApiManagement/service/apis/policies@2025-09-01-preview' = {
parent: mcpServer
name: 'policy'
properties: {
format: 'rawxml'
value: '''<policies>
<inbound>
<base />
<rate-limit calls="100" renewal-period="60" />
</inbound>
<backend>
<forward-request />
</backend>
<outbound>
<base />
</outbound>
</policies>'''
}
}
resource product 'Microsoft.ApiManagement/service/products@2025-09-01-preview' existing = {
parent: apimService
name: productId
}
resource productBinding 'Microsoft.ApiManagement/service/products/apis@2025-09-01-preview' = {
parent: product
name: mcpServerId
dependsOn: [ mcpServer ]
}
To deploy:
# Use orders-mcp.json if you're deploying the ARM template.
az deployment group create \
--resource-group <resource-group> \
--template-file orders-mcp.bicep \
--parameters serviceName=<api-management-name> \
backingApiId=orders-api \
backingOperationId=get-orders \
toolId=getOrders
Passthrough MCP server
These templates create a passthrough MCP server that uses the streamable HTTP transport. The server scope has a rate-limit policy, and the server binds to an existing product. The templates don't define any tool sub-resources. The external backend determines the tool surface.
Note
The following templates use transportType: streamable, which implements the current MCP streamable HTTP specification. To use SSE transport instead, set transportType to sse and replace the endpoints array with two entries: { "name": "sse", "uriTemplate": "/sse" } and { "name": "message", "uriTemplate": "/messages" }. In Bicep, use the same values with single-quoted strings.
Prerequisites: an existing API Management service, a reachable MCP backend URL (backendUrl) that implements the selected transport and endpoint paths, and an existing product (productId).
| Parameter | Required | Default | Description |
|---|---|---|---|
serviceName |
Yes | — | Name of the existing API Management service instance. |
mcpServerId |
No | external-mcp |
Resource name for the new MCP server. Must be unique within the service. |
backendUrl |
Yes | — | Absolute URL of the external MCP backend. |
productId |
No | starter |
Resource name of the existing product to bind the server to. |
@description('Name of the existing API Management service instance.')
param serviceName string
@description('Resource name for the new MCP server.')
param mcpServerId string = 'external-mcp'
@description('Absolute URL of the external MCP backend.')
param backendUrl string
@description('Resource name of the existing product to bind the MCP server to.')
param productId string = 'starter'
resource apimService 'Microsoft.ApiManagement/service@2025-09-01-preview' existing = {
name: serviceName
}
resource mcpServer 'Microsoft.ApiManagement/service/apis@2025-09-01-preview' = {
parent: apimService
name: mcpServerId
properties: {
type: 'mcp'
displayName: 'External MCP Server'
description: 'Passthrough MCP server using streamable HTTP transport'
path: mcpServerId
protocols: [ 'https' ]
serviceUrl: backendUrl
subscriptionRequired: true
mcpProperties: {
transportType: 'streamable'
endpoints: [
{
name: 'message'
uriTemplate: '/mcp'
}
]
}
}
}
resource mcpPolicy 'Microsoft.ApiManagement/service/apis/policies@2025-09-01-preview' = {
parent: mcpServer
name: 'policy'
properties: {
format: 'rawxml'
value: '''<policies>
<inbound>
<base />
<rate-limit calls="100" renewal-period="60" />
</inbound>
<backend>
<forward-request />
</backend>
<outbound>
<base />
</outbound>
</policies>'''
}
}
resource product 'Microsoft.ApiManagement/service/products@2025-09-01-preview' existing = {
parent: apimService
name: productId
}
resource productBinding 'Microsoft.ApiManagement/service/products/apis@2025-09-01-preview' = {
parent: product
name: mcpServerId
dependsOn: [ mcpServer ]
}
To deploy:
# Use external-mcp.json if you're deploying the ARM template.
az deployment group create \
--resource-group <resource-group> \
--template-file external-mcp.bicep \
--parameters serviceName=<api-management-name> \
backendUrl=https://mcp-backend.contoso.com
Azure CLI
Currently, you can use az rest to call the REST API directly. The following script creates a passthrough MCP server, attaches a rate-limit policy, and binds it to a product. This process covers the same scenario as the Bicep template in the previous section.
Set variables, and then run the four az rest calls in order.
Note
az rest uses the credential from your current az login session. You don't need a separate authentication step.
# Variables. Edit these for your environment
SUBSCRIPTION_ID=$(az account show --query id -o tsv)
RESOURCE_GROUP="<your-resource-group>"
APIM_NAME="<your-apim-service-name>"
MCP_SERVER_ID="external-mcp"
BACKEND_URL="https://mcp-backend.contoso.com"
PRODUCT_ID="starter"
API_VERSION="2025-09-01-preview"
BASE="https://management.azure.com/subscriptions/${SUBSCRIPTION_ID}/resourceGroups/${RESOURCE_GROUP}/providers/Microsoft.ApiManagement/service/${APIM_NAME}"
# 1. Create the passthrough MCP server
az rest --method PUT \
--uri "${BASE}/apis/${MCP_SERVER_ID}?api-version=${API_VERSION}" \
--headers "If-Match=*" \
--body '{
"properties": {
"type": "mcp",
"displayName": "External MCP Server",
"description": "Passthrough MCP server using streamable HTTP transport",
"path": "external-mcp",
"protocols": ["https"],
"serviceUrl": "'"${BACKEND_URL}"'",
"subscriptionRequired": true,
"mcpProperties": {
"transportType": "streamable",
"endpoints": [
{ "name": "message", "uriTemplate": "/mcp" }
]
}
}
}'
# 2. Attach a rate-limit policy at the server scope
az rest --method PUT \
--uri "${BASE}/apis/${MCP_SERVER_ID}/policies/policy?api-version=${API_VERSION}" \
--headers "If-Match=*" \
--body '{
"properties": {
"format": "rawxml",
"value": "<policies><inbound><base /><rate-limit calls=\"100\" renewal-period=\"60\" /></inbound><backend><forward-request /></backend><outbound><base /></outbound></policies>"
}
}'
# 3. Bind the server to a product
az rest --method PUT \
--uri "${BASE}/products/${PRODUCT_ID}/apis/${MCP_SERVER_ID}?api-version=${API_VERSION}"
Each step is idempotent. Re-running the script updates the resource in place. To verify the server was created, run the following command:
az rest --method GET \
--uri "${BASE}/apis/${MCP_SERVER_ID}?api-version=${API_VERSION}"
Terraform
The AzureRM Terraform provider doesn't yet have native resources for MCP servers. Currently, you can use the azapi_resource resource type from the AzAPI provider, which lets you manage any Azure resource type against any API version. The following example mirrors the passthrough MCP server Bicep template.
Prerequisites: an existing API Management service, a reachable MCP backend URL, and an existing product. Add the AzAPI provider to your terraform block if it isn't already present.
terraform {
required_providers {
azurerm = {
source = "hashicorp/azurerm"
version = ">= 3.0"
}
azapi = {
source = "Azure/azapi"
version = ">= 1.13"
}
}
}
provider "azurerm" {
features {}
}
provider "azapi" {}
Variables
variable "resource_group_name" {
description = "Name of the resource group containing the API Management service."
type = string
}
variable "service_name" {
description = "Name of the existing API Management service instance."
type = string
}
variable "mcp_server_id" {
description = "Resource name for the new MCP server."
type = string
default = "external-mcp"
}
variable "backend_url" {
description = "Absolute URL of the external MCP backend."
type = string
}
variable "product_id" {
description = "Resource name of the existing product to bind the server to."
type = string
default = "starter"
}
Resources
# Reference the existing API Management service
data "azurerm_api_management" "apim" {
name = var.service_name
resource_group_name = var.resource_group_name
}
# 1. Create the passthrough MCP server
resource "azapi_resource" "mcp_server" {
type = "Microsoft.ApiManagement/service/apis@2025-09-01-preview"
name = var.mcp_server_id
parent_id = data.azurerm_api_management.apim.id
body = {
properties = {
type = "mcp"
displayName = "External MCP Server"
description = "Passthrough MCP server using streamable HTTP transport"
path = var.mcp_server_id
protocols = ["https"]
serviceUrl = var.backend_url
subscriptionRequired = true
mcpProperties = {
transportType = "streamable"
endpoints = [
{
name = "message"
uriTemplate = "/mcp"
}
]
}
}
}
}
# 2. Attach a rate-limit policy at the server scope
resource "azapi_resource" "mcp_policy" {
type = "Microsoft.ApiManagement/service/apis/policies@2025-09-01-preview"
name = "policy"
parent_id = azapi_resource.mcp_server.id
body = {
properties = {
format = "rawxml"
value = "<policies><inbound><base /><rate-limit calls=\"100\" renewal-period=\"60\" /></inbound><backend><forward-request /></backend><outbound><base /></outbound></policies>"
}
}
depends_on = [azapi_resource.mcp_server]
}
# 3. Bind the server to a product
resource "azapi_resource" "product_binding" {
type = "Microsoft.ApiManagement/service/products/apis@2025-09-01-preview"
name = var.mcp_server_id
parent_id = "${data.azurerm_api_management.apim.id}/products/${var.product_id}"
body = {}
depends_on = [azapi_resource.mcp_server]
}
To deploy:
Note
azapi_resource uses the AzAPI provider's authentication, which reads from the same az login credential as the Azure CLI. You don't need to configure separate authentication when running locally.
terraform init
terraform apply \
-var="resource_group_name=<resource-group>" \
-var="service_name=<api-management-name>" \
-var="backend_url=https://mcp-backend.contoso.com"
CI/CD patterns
Idempotent upserts: Send PUT requests with
If-Match: "*"so the same template applies whether or not the resource already exists.Promote configurations across environments: Treat MCP server definitions and tool lists as source-controlled artifacts. Parameterize only environment-specific values, such as instance name and backend URL.
Generate the tool list from your API spec: Drive the tools sub-resource from your source OpenAPI file so the tool surface stays in sync with the backing API as it evolves.
Delete in the right order: Remove MCP tool references before deleting the backing APIs or operations they point to. Otherwise, the delete fails on a foreign-key check.