Clinical Terminology MCP

Deploy to Azure Container Apps

Deploy Clinical Terminology MCP servers to Azure Container Apps.

Deploy MCP servers to Azure Container Apps for serverless container hosting with built-in scaling.

Prerequisites

  • Azure CLI installed and logged in
  • Docker installed locally
  • Azure subscription with appropriate permissions

Set Up Resources

# Set variables
export RESOURCE_GROUP=clinical-terminology-mcp
export LOCATION=eastus
export ENVIRONMENT=mcp-env
export ACR_NAME=clinicalterminologymcp

# Create resource group
az group create \
  --name $RESOURCE_GROUP \
  --location $LOCATION

# Create Container Registry
az acr create \
  --resource-group $RESOURCE_GROUP \
  --name $ACR_NAME \
  --sku Basic \
  --admin-enabled true

Build and Push Image

# Login to ACR
az acr login --name $ACR_NAME

# Build and push (using ACR Tasks)
az acr build \
  --registry $ACR_NAME \
  --image snomed-mcp:latest \
  --file snomed-mcp/Dockerfile \
  .

# Or push locally built image
docker build -t snomed-mcp -f snomed-mcp/Dockerfile .
docker tag snomed-mcp $ACR_NAME.azurecr.io/snomed-mcp:latest
docker push $ACR_NAME.azurecr.io/snomed-mcp:latest

Store Secrets in Key Vault

# Create Key Vault
az keyvault create \
  --name mcp-secrets \
  --resource-group $RESOURCE_GROUP \
  --location $LOCATION

# Store ICD-11 credentials
az keyvault secret set \
  --vault-name mcp-secrets \
  --name icd11-client-id \
  --value "your-client-id"

az keyvault secret set \
  --vault-name mcp-secrets \
  --name icd11-client-secret \
  --value "your-client-secret"

# Store UMLS API key
az keyvault secret set \
  --vault-name mcp-secrets \
  --name umls-api-key \
  --value "your-api-key"

# Store LOINC credentials
az keyvault secret set \
  --vault-name mcp-secrets \
  --name loinc-username \
  --value "your-username"

az keyvault secret set \
  --vault-name mcp-secrets \
  --name loinc-password \
  --value "your-password"

Create Container Apps Environment

az containerapp env create \
  --name $ENVIRONMENT \
  --resource-group $RESOURCE_GROUP \
  --location $LOCATION

Create Managed Identity

# Create user-assigned managed identity
az identity create \
  --name mcp-identity \
  --resource-group $RESOURCE_GROUP

# Get identity details
IDENTITY_ID=$(az identity show \
  --name mcp-identity \
  --resource-group $RESOURCE_GROUP \
  --query id -o tsv)

IDENTITY_CLIENT_ID=$(az identity show \
  --name mcp-identity \
  --resource-group $RESOURCE_GROUP \
  --query clientId -o tsv)

IDENTITY_PRINCIPAL_ID=$(az identity show \
  --name mcp-identity \
  --resource-group $RESOURCE_GROUP \
  --query principalId -o tsv)

# Grant ACR pull permission
ACR_ID=$(az acr show --name $ACR_NAME --query id -o tsv)
az role assignment create \
  --assignee $IDENTITY_PRINCIPAL_ID \
  --role AcrPull \
  --scope $ACR_ID

# Grant Key Vault access
az keyvault set-policy \
  --name mcp-secrets \
  --object-id $IDENTITY_PRINCIPAL_ID \
  --secret-permissions get

Deploy Container App

Basic Server (no secrets)

az containerapp create \
  --name snomed-mcp \
  --resource-group $RESOURCE_GROUP \
  --environment $ENVIRONMENT \
  --image $ACR_NAME.azurecr.io/snomed-mcp:latest \
  --registry-server $ACR_NAME.azurecr.io \
  --user-assigned $IDENTITY_ID \
  --registry-identity $IDENTITY_ID \
  --target-port 8080 \
  --ingress external \
  --min-replicas 0 \
  --max-replicas 10 \
  --cpu 0.5 \
  --memory 1Gi \
  --env-vars \
    MCP_TRANSPORT=http \
    MCP_HTTP_ADDR=:8080 \
    MCP_LOG_FORMAT=json \
    MCP_METRICS_ENABLED=true

Server with Secrets (e.g., ICD-11)

# Get Key Vault URI
KEYVAULT_URI=$(az keyvault show --name mcp-secrets --query properties.vaultUri -o tsv)

az containerapp create \
  --name icd11-mcp \
  --resource-group $RESOURCE_GROUP \
  --environment $ENVIRONMENT \
  --image $ACR_NAME.azurecr.io/icd11-mcp:latest \
  --registry-server $ACR_NAME.azurecr.io \
  --user-assigned $IDENTITY_ID \
  --registry-identity $IDENTITY_ID \
  --target-port 8080 \
  --ingress external \
  --min-replicas 0 \
  --max-replicas 10 \
  --cpu 0.5 \
  --memory 1Gi \
  --env-vars \
    MCP_TRANSPORT=http \
    MCP_HTTP_ADDR=:8080 \
    MCP_LOG_FORMAT=json \
  --secrets \
    icd11-client-id=keyvaultref:${KEYVAULT_URI}secrets/icd11-client-id,identityref:$IDENTITY_ID \
    icd11-client-secret=keyvaultref:${KEYVAULT_URI}secrets/icd11-client-secret,identityref:$IDENTITY_ID \
  --env-vars \
    ICD11_CLIENT_ID=secretref:icd11-client-id \
    ICD11_CLIENT_SECRET=secretref:icd11-client-secret

Configure Health Probes

Update the container app with health probes:

az containerapp update \
  --name snomed-mcp \
  --resource-group $RESOURCE_GROUP \
  --set-env-vars "MCP_TRANSPORT=http" \
  --yaml - << 'EOF'
properties:
  template:
    containers:
      - name: snomed-mcp
        probes:
          - type: liveness
            httpGet:
              path: /health/live
              port: 8080
            initialDelaySeconds: 5
            periodSeconds: 10
          - type: readiness
            httpGet:
              path: /health/ready
              port: 8080
            initialDelaySeconds: 5
            periodSeconds: 10
EOF

Configure Custom Domain

# Add custom domain
az containerapp hostname add \
  --name snomed-mcp \
  --resource-group $RESOURCE_GROUP \
  --hostname mcp.yourdomain.com

# Bind certificate (managed)
az containerapp hostname bind \
  --name snomed-mcp \
  --resource-group $RESOURCE_GROUP \
  --hostname mcp.yourdomain.com \
  --environment $ENVIRONMENT \
  --validation-method CNAME

Verify Deployment

# Get app URL
APP_URL=$(az containerapp show \
  --name snomed-mcp \
  --resource-group $RESOURCE_GROUP \
  --query properties.configuration.ingress.fqdn -o tsv)

# Test health
curl https://$APP_URL/health

# Test MCP endpoint
curl -X POST https://$APP_URL/mcp \
  -H "Content-Type: application/json" \
  -d '{"jsonrpc":"2.0","method":"initialize","params":{},"id":1}'

Scaling Rules

Configure auto-scaling based on HTTP requests:

az containerapp update \
  --name snomed-mcp \
  --resource-group $RESOURCE_GROUP \
  --scale-rule-name http-rule \
  --scale-rule-type http \
  --scale-rule-http-concurrency 50

Deploy All Servers

Script to deploy all servers:

#!/bin/bash
SERVERS="snomed-mcp rxnorm-mcp icd10-mcp ucum-mcp"

for server in $SERVERS; do
  az containerapp create \
    --name $server \
    --resource-group $RESOURCE_GROUP \
    --environment $ENVIRONMENT \
    --image $ACR_NAME.azurecr.io/$server:latest \
    --registry-server $ACR_NAME.azurecr.io \
    --user-assigned $IDENTITY_ID \
    --registry-identity $IDENTITY_ID \
    --target-port 8080 \
    --ingress external \
    --min-replicas 0 \
    --max-replicas 10 \
    --env-vars \
      MCP_TRANSPORT=http \
      MCP_HTTP_ADDR=:8080 \
      MCP_LOG_FORMAT=json \
      MCP_METRICS_ENABLED=true
done

Monitoring

Container Apps integrates with Azure Monitor. View metrics in the Azure Portal:

  • Requests and response times
  • Replica count
  • CPU and memory usage

Enable Log Analytics for application logs:

az containerapp logs show \
  --name snomed-mcp \
  --resource-group $RESOURCE_GROUP \
  --follow