An Azure service that provides a hybrid, multi-cloud management platform for APIs.
@Sutton, Will skip to the second code block, that's what worked for me. Rest of this is explanation.
I was having the same issue. But I have found a solution that appears to be not documented. I'll show what didn't work, then what did work. I'll also include the az rest output of both.
In short, what worked is creating a backend resource and linking the api to that backend resource. I don't see that documented anywhere that was linked to. I figured it out by looking at the bicep export and the az rest results.
My use case was creating the Azure DevOps MCP as a pass thru.
This deploys but produces an 'API' rather than 'MCP' in the portal. Also note the different syntax of the endpoints. The documented syntax is rejected by the deployment.
resource apimService 'Microsoft.ApiManagement/service@2025-09-01-preview' existing = {
name: apimServiceName
}
resource azureDevOpsMcpApi 'Microsoft.ApiManagement/service/apis@2025-09-01-preview' = {
parent: apimService
name: 'azdo-mcp'
properties: {
type: 'mcp'
displayName: 'Azure DevOps MCP Server'
description: 'Azure DevOps Remote MCP server exposed through API Management.'
path: 'azdo-mcp'
protocols: [
'https'
]
serviceUrl: 'https://mcp.dev.azure.com/[REDACTED]'
subscriptionRequired: false
mcpProperties: {
transportType: 'streamable'
endpoints: {
'message': {
uriTemplate: '/mcp'
}
}
}
}
}
Note that using the documented endpoint syntax produces this error when deploying.
Status Message: One or more fields contain incorrect values: (Code: ValidationError)
- Parsing error(s): An error occured while parsing the input. Message: Cannot deserialize the current JSON array (e.g. [1,2,3]) into type 'System.Collections.Generic.Dictionary`2[System.String,Microsoft.Azure.ApiManagement.Management.Contracts.McpEndpointContract]' because the type requires a JSON object (e.g. {"name":"value"}) to deserialize correctly.
The below bicep deploys and produces a functioning pass thru 'MCP' MCP server in the APIM.
resource apimService 'Microsoft.ApiManagement/service@2025-09-01-preview' existing = {
name: apimServiceName
}
resource azureDevOpsMcpApi 'Microsoft.ApiManagement/service/apis@2025-09-01-preview' = {
parent: apimService
name: 'azdo-mcp'
properties: {
type: 'mcp'
displayName: 'Azure DevOps MCP Server'
description: 'Azure DevOps Remote MCP server exposed through API Management.'
path: 'azdo-mcp'
protocols: [
'https'
]
subscriptionRequired: false
mcpProperties: {
transportType: 'streamable'
endpoints: {
'mcp': {
uriTemplate: '/[REDACTED]'
}
}
}
backendId: azureDevOpsMcpBackend.name
}
}
resource azureDevOpsMcpBackend 'Microsoft.ApiManagement/service/backends@2025-09-01-preview' = {
parent: apimService
name: 'azdo-mcp-backend'
properties: {
url: 'https://mcp.dev.azure.com'
protocol: 'http'
}
}
Note two items that made this function as would be expected:
- The creation of the '/service/backends' resource and the 'backendId' property in the associated api.
- Using the hashtable syntax in the 'endpoints' property (not the array) and specifying a value for the 'mcp' endpoint. Not a 'message' endpoint.
The redacted part in the mcp.uriTemplate is the specific Azure DevOps organization name used in the MCP endpoint. That might be '/' if there's no additional pathing needed in other pass thru MCP servers. I haven't tested that yet.
Here is the result of the 'az rest' call for the first deployment that deploys but produces an 'API' MCP server.
{
"id": "/subscriptions/[REDACTED]/resourceGroups/[REDACTED]-rg/providers/Microsoft.ApiManagement/service/[REDACTED]-apim/apis/azdo-mcp",
"name": "azdo-mcp",
"properties": {
"a2aProperties": null,
"agent": null,
"apiRevision": "1",
"authenticationSettings": {
"oAuth2": null,
"oAuth2AuthenticationSettings": [],
"openid": null,
"openidAuthenticationSettings": []
},
"backendId": null,
"description": "Azure DevOps Remote MCP server exposed through API Management.",
"displayName": "Azure DevOps MCP Server",
"effectivePath": null,
"isAgent": false,
"isCurrent": true,
"jsonRpcProperties": null,
"mcpProperties": {
"endpoints": {
"message": {
"uriTemplate": "/mcp"
}
}
},
"path": "azdo-mcp",
"protocols": [
"https"
],
"provisioningState": "Succeeded",
"serviceUrl": "https://mcp.dev.azure.com/[REDACTED]",
"subscriptionKeyParameterNames": {
"bearer": null,
"header": "Ocp-Apim-Subscription-Key",
"query": "subscription-key"
},
"subscriptionRequired": false,
"type": "mcp"
},
"type": "Microsoft.ApiManagement/service/apis"
}
And here's the 'az rest' output for the second one, the deployment that deploys a functioning pass thru 'MCP' MCP server.
{
"id": "/subscriptions/[REDACTED]/resourceGroups/[REDACTED]-rg/providers/Microsoft.ApiManagement/service/[REDACTED]-apim/apis/azdo-mcp",
"name": "azdo-mcp",
"properties": {
"a2aProperties": null,
"agent": null,
"apiRevision": "1",
"authenticationSettings": {
"oAuth2": null,
"oAuth2AuthenticationSettings": [],
"openid": null,
"openidAuthenticationSettings": []
},
"backendId": "azdo-mcp-backend",
"description": "Azure DevOps Remote MCP server exposed through API Management.",
"displayName": "Azure DevOps MCP Server",
"effectivePath": null,
"isAgent": false,
"isCurrent": true,
"jsonRpcProperties": null,
"mcpProperties": {
"endpoints": {
"mcp": {
"uriTemplate": "/[REDACTED]"
}
}
},
"path": "azdo-mcp",
"protocols": [
"https"
],
"provisioningState": "Succeeded",
"serviceUrl": null,
"subscriptionKeyParameterNames": {
"bearer": null,
"header": "Ocp-Apim-Subscription-Key",
"query": "subscription-key"
},
"subscriptionRequired": false,
"type": "mcp"
},
"type": "Microsoft.ApiManagement/service/apis"
}