targetScope = 'subscription'
@minLength(1)
@maxLength(64)
@description('Name of the the environment which is used to generate a short unique hash used in all resources.')
param environmentName string
@minLength(1)
@description('Primary location for all resources')
@allowed(['australiaeast', 'eastasia', 'eastus', 'eastus2', 'northeurope', 'southcentralus', 'southeastasia', 'swedencentral', 'uksouth', 'westus2', 'eastus2euap'])
@metadata({
azd: {
type: 'location'
}
})
param location string
param vnetEnabled bool
param apiServiceName string = ''
param apiUserAssignedIdentityName string = ''
param applicationInsightsName string = ''
param appServicePlanName string = ''
param logAnalyticsName string = ''
param resourceGroupName string = ''
param storageAccountName string = ''
param vNetName string = ''
param mcpEntraApplicationDisplayName string = ''
param mcpEntraApplicationUniqueName string = ''
param disableLocalAuth bool = true
// MCP Client APIM gateway specific variables
var oauth_scopes = 'openid https://graph.microsoft.com/.default'
var abbrs = loadJsonContent('./abbreviations.json')
var resourceToken = toLower(uniqueString(subscription().id, environmentName, location))
var tags = { 'azd-env-name': environmentName }
var functionAppName = !empty(apiServiceName) ? apiServiceName : '${abbrs.webSitesFunctions}api-${resourceToken}'
var deploymentStorageContainerName = 'app-package-${take(functionAppName, 32)}-${take(toLower(uniqueString(functionAppName, resourceToken)), 7)}'
// Organize resources in a resource group
resource rg 'Microsoft.Resources/resourceGroups@2021-04-01' = {
name: !empty(resourceGroupName) ? resourceGroupName : '${abbrs.resourcesResourceGroups}${environmentName}'
location: location
tags: tags
}
var apimResourceToken = toLower(uniqueString(subscription().id, resourceGroupName, environmentName, location))
var apiManagementName = '${abbrs.apiManagementService}${apimResourceToken}'
// apim service deployment
module apimService './core/apim/apim.bicep' = {
name: apiManagementName
scope: rg
params:{
apiManagementName: apiManagementName
}
}
var cosmosDbName = '${abbrs.documentDBDatabaseAccounts}${resourceToken}'
// CosmosDB for OAuth client registrations
module cosmosDb './core/database/cosmosdb.bicep' = {
name: 'cosmosdb'
scope: rg
params: {
cosmosDbAccountName: cosmosDbName
location: location
tags: tags
}
}
// Grant APIM system-assigned managed identity access to CosmosDB
module apimCosmosDbRoleAssignment './core/database/cosmosdb-rbac.bicep' = {
name: 'apimCosmosDbRoleAssignment'
scope: rg
params: {
cosmosDbAccountName: cosmosDb.outputs.cosmosDbAccountName
principalId: apimService.outputs.principalId
}
}
// MCP client oauth via APIM gateway
module oauthAPIModule './app/apim-oauth/oauth.bicep' = {
name: 'oauthAPIModule'
scope: rg
params: {
location: location
entraAppUniqueName: !empty(mcpEntraApplicationUniqueName) ? mcpEntraApplicationUniqueName : 'mcp-oauth-${abbrs.applications}${apimResourceToken}'
entraAppDisplayName: !empty(mcpEntraApplicationDisplayName) ? mcpEntraApplicationDisplayName : 'MCP-OAuth-${abbrs.applications}${apimResourceToken}'
apimServiceName: apimService.name
oauthScopes: oauth_scopes
entraAppUserAssignedIdentityPrincipleId: apimService.outputs.entraAppUserAssignedIdentityPrincipleId
entraAppUserAssignedIdentityClientId: apimService.outputs.entraAppUserAssignedIdentityClientId
cosmosDbEndpoint: cosmosDb.outputs.cosmosDbEndpoint
cosmosDbDatabaseName: cosmosDb.outputs.databaseName
cosmosDbContainerName: cosmosDb.outputs.containerName
}
dependsOn: [
apimCosmosDbRoleAssignment
]
}
// MCP server API endpoints
module mcpApiModule './app/apim-mcp/mcp-api.bicep' = {
name: 'mcpApiModule'
scope: rg
params: {
apimServiceName: apimService.name
functionAppName: functionAppName
}
dependsOn: [
api
oauthAPIModule
]
}
// User assigned managed identity to be used by the function app to reach storage and service bus
module apiUserAssignedIdentity './core/identity/userAssignedIdentity.bicep' = {
name: 'apiUserAssignedIdentity'
scope: rg
params: {
location: location
tags: tags
identityName: !empty(apiUserAssignedIdentityName) ? apiUserAssignedIdentityName : '${abbrs.managedIdentityUserAssignedIdentities}api-${resourceToken}'
}
}
// The application backend is a function app
module appServicePlan './core/host/appserviceplan.bicep' = {
name: 'appserviceplan'
scope: rg
params: {
name: !empty(appServicePlanName) ? appServicePlanName : '${abbrs.webServerFarms}${resourceToken}'
location: location
tags: tags
sku: {
name: 'FC1'
tier: 'FlexConsumption'
}
}
}
module api './app/api.bicep' = {
name: 'api'
scope: rg
params: {
name: functionAppName
location: location
tags: tags
applicationInsightsName: monitoring.outputs.applicationInsightsName
appServicePlanId: appServicePlan.outputs.id
runtimeName: 'python'
runtimeVersion: '3.11'
storageAccountName: storage.outputs.name
deploymentStorageContainerName: deploymentStorageContainerName
identityId: apiUserAssignedIdentity.outputs.identityId
identityClientId: apiUserAssignedIdentity.outputs.identityClientId
appSettings: {
}
virtualNetworkSubnetId: !vnetEnabled ? '' : serviceVirtualNetwork.outputs.appSubnetID
}
}
// Backing storage for Azure functions api
module storage './core/storage/storage-account.bicep' = {
name: 'storage'
scope: rg
params: {
name: !empty(storageAccountName) ? storageAccountName : '${abbrs.storageStorageAccounts}${resourceToken}'
location: location
tags: tags
containers: [{name: deploymentStorageContainerName}, {name: 'snippets'}]
publicNetworkAccess: vnetEnabled ? 'Disabled' : 'Enabled'
networkAcls: !vnetEnabled ? {} : {
defaultAction: 'Deny'
}
}
}
var StorageBlobDataOwner = 'b7e6dc6d-f1e8-4753-8033-0f276bb0955b'
var StorageQueueDataContributor = '974c5e8b-45b9-4653-ba55-5f855dd0fb88'
// Allow access from api to blob storage using a managed identity
module blobRoleAssignmentApi 'app/storage-Access.bicep' = {
name: 'blobRoleAssignmentapi'
scope: rg
params: {
storageAccountName: storage.outputs.name
roleDefinitionID: StorageBlobDataOwner
principalID: apiUserAssignedIdentity.outputs.identityPrincipalId
}
}
// Allow access from api to queue storage using a managed identity
module queueRoleAssignmentApi 'app/storage-Access.bicep' = {
name: 'queueRoleAssignmentapi'
scope: rg
params: {
storageAccountName: storage.outputs.name
roleDefinitionID: StorageQueueDataContributor
principalID: apiUserAssignedIdentity.outputs.identityPrincipalId
}
}
// Virtual Network & private endpoint to blob storage
module serviceVirtualNetwork 'app/vnet.bicep' = if (vnetEnabled) {
name: 'serviceVirtualNetwork'
scope: rg
params: {
location: location
tags: tags
vNetName: !empty(vNetName) ? vNetName : '${abbrs.networkVirtualNetworks}${resourceToken}'
}
}
module storagePrivateEndpoint 'app/storage-PrivateEndpoint.bicep' = if (vnetEnabled) {
name: 'servicePrivateEndpoint'
scope: rg
params: {
location: location
tags: tags
virtualNetworkName: !empty(vNetName) ? vNetName : '${abbrs.networkVirtualNetworks}${resourceToken}'
subnetName: !vnetEnabled ? '' : serviceVirtualNetwork.outputs.peSubnetName
resourceName: storage.outputs.name
}
}
// Monitor application with Azure Monitor
module monitoring './core/monitor/monitoring.bicep' = {
name: 'monitoring'
scope: rg
params: {
location: location
tags: tags
logAnalyticsName: !empty(logAnalyticsName) ? logAnalyticsName : '${abbrs.operationalInsightsWorkspaces}${resourceToken}'
applicationInsightsName: !empty(applicationInsightsName) ? applicationInsightsName : '${abbrs.insightsComponents}${resourceToken}'
disableLocalAuth: disableLocalAuth
}
}
var monitoringRoleDefinitionId = '3913510d-42f4-4e42-8a64-420c390055eb' // Monitoring Metrics Publisher role ID
// Allow access from api to application insights using a managed identity
module appInsightsRoleAssignmentApi './core/monitor/appinsights-access.bicep' = {
name: 'appInsightsRoleAssignmentapi'
scope: rg
params: {
appInsightsName: monitoring.outputs.applicationInsightsName
roleDefinitionID: monitoringRoleDefinitionId
principalID: apiUserAssignedIdentity.outputs.identityPrincipalId
}
}
// App outputs
output APPLICATIONINSIGHTS_CONNECTION_STRING string = monitoring.outputs.applicationInsightsConnectionString
output AZURE_LOCATION string = location
output AZURE_TENANT_ID string = tenant().tenantId
output SERVICE_API_NAME string = api.outputs.SERVICE_API_NAME
output AZURE_FUNCTION_NAME string = api.outputs.SERVICE_API_NAME
output SERVICE_API_ENDPOINTS array = [ '${apimService.outputs.gatewayUrl}/mcp/sse' ]