Skip to main content
Glama

Secure Remote MCP Server

main.bicep8.96 kB
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' ]

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/Azure-Samples/remote-mcp-apim-functions-python'

If you have feedback or need assistance with the MCP directory API, please join our Discord server