Azure Diagram MCP Server
A remotely-hosted Model Context Protocol (MCP) server that turns agent-written architecture descriptions into Azure diagrams. Agents describe topology (services, groups, connections); the server owns layout and styling so output stays consistent across models and prompts.
Built for Streamable HTTP so any MCP client (Cursor, VS Code, Copilot, custom agents) can connect over the network. Diagrams are returned inline as PNG and can be persisted to Azure Blob Storage with a shareable SAS URL.
Features
- MCP tools for diagram generation, Azure node discovery, and worked examples
- Server-side style profiles — Graphviz layout, fonts, cluster boxes, and connector colors are injected at render time; agents do not hard-code
graph_attror colors - Safe execution of agent-supplied Python via AST validation, process isolation, restricted builtins, and a render timeout
- CAF-aligned visual language — default
caf-defaultprofile matches Microsoft reference-architecture styling (grey cluster boxes, dashed private paths, dotted observability links) - Optional Blob persistence — inline PNG always returned; shareable URL when storage is configured
- Docker-ready — single image with Graphviz, Cairo, and Node.js (for optional Azure best-practice validation)
- Azure Container Apps deployment — Bicep + Azure Developer CLI (
azd) template included
How it works
flowchart LR
Agent["MCP client / LLM"] -->|"POST /mcp"| Server["FastMCP server"]
Server --> Validate["AST safety check"]
Validate --> Style["Inject style profile"]
Style --> Render["Graphviz → PNG"]
Render --> Inline["Inline PNG response"]
Render --> Blob["Azure Blob (optional)"]
- The agent calls
list_azure_componentsandget_diagram_exampleto learn validdiagrams.azure.*imports. - The agent writes topology-only Python using the
diagramsDSL and callsgenerate_diagram. - The server validates the code, injects the active style profile, renders in an isolated subprocess, and returns the PNG.
Note: A second pipeline for declarative YAML/JSON reference architectures (
generate_reference_diagram) exists in the codebase but is currently disabled in the MCP server. All diagram generation today goes throughgenerate_diagram.
MCP tools
| Tool | Description | | --- | --- | | generate_diagram(code, filename?, title?, style_profile?) | Render diagrams DSL to PNG. Style/layout come from style_profile (default caf-default). | | list_azure_components(category?) | List importable diagrams.azure.* node classes (compute, network, database, …). | | get_diagram_example() | Return a minimal worked DSL example (topology only, no styling). | | list_style_profiles() | List bundled layout/style presets and the default profile name. |
Authoring diagrams (for agents)
Do: describe nodes, Cluster groupings, and connections.
Do not: set graph_attr, direction, show, filename, or arbitrary edge colors — the server applies those from the style profile.
from diagrams import Diagram, Cluster, Edge
from diagrams.azure.network import ApplicationGateway
from diagrams.azure.web import AppServices
from diagrams.azure.security import KeyVaults
from diagrams.azure.database import SQLDatabases
with Diagram("Azure Web App"):
agw = ApplicationGateway("App Gateway")
with Cluster("Application"):
app = AppServices("App Service")
kv = KeyVaults("Key Vault")
db = SQLDatabases("SQL Database")
agw >> Edge(label="HTTPS") >> app
app >> kv
app >> Edge(style="dashed") >> db # dashed = indirect / private path
Connector semantics (colors come from the profile):
| Edge(...) | Meaning | | --- | --- | | plain >> | Solid data-flow connector | | Edge(style="dashed") | Indirect, egress, or private path | | Edge(style="dotted") | Observability, identity, or governance | | Edge(label="HTTPS") | Label only — no custom colors needed |
Style profiles
Profiles live under src/azure_diagram_mcp/assets/style/. The default caf-default profile defines:
- Top-to-bottom layout (
direction: TB), spline routing, spacing - Light grey dashed cluster boxes (VNet / subscription groupings)
- CAF connector palette (solid / dashed / dotted)
To tune diagram appearance globally, edit caf-default.yaml under the diagrams_dsl section — no agent prompt changes required.
# excerpt from assets/style/caf-default.yaml
diagrams_dsl:
direction: TB
graph_attr:
fontsize: "22"
bgcolor: white
splines: spline
connectors:
solid: { style: solid, color: "#323130" }
dashed: { style: dashed, color: "#605E5C" }
dotted: { style: dotted, color: "#A19F9D" }
Pass a different profile to generate_diagram(..., style_profile="caf-default").
Security
Executing agent-supplied Python is the primary risk. Mitigations:
| Layer | What it does | | --- | --- | | AST allowlist (validation.py) | Only diagrams imports permitted; dangerous builtins and dunder escapes rejected | | Process isolation (renderer.py) | Code runs in a separate process with open / eval / exec stripped from builtins | | Timeout | Wall-clock limit on each render (default 30 s) | | Style injection | Server overrides layout kwargs — agents cannot control filenames or open arbitrary files via Diagram |
For production, also restrict Container Apps ingress (authentication, IP allow lists).
Quick start
Prerequisites
- Python 3.12+
- Graphviz (
dotonPATH) - Windows:
winget install graphviz - macOS:
brew install graphviz - Linux:
apt-get install graphviz
Run locally (Python)
python -m venv .venv
# Windows: .venv\Scripts\activate
# macOS/Linux: source .venv/bin/activate
pip install -e .
python -m azure_diagram_mcp.server
Server endpoints:
| URL | Purpose | | --- | --- | | http://localhost:8000/mcp | MCP Streamable HTTP transport | | http://localhost:8000/health | Liveness probe ({"status":"ok","version":"0.1.0"}) |
Run locally (Docker)
docker build -t azure-diagram-mcp:local .
docker run -d --name azure-diagram-mcp-local -p 8000:8000 --restart unless-stopped azure-diagram-mcp:local
Rebuild and redeploy after code changes:
docker build -t azure-diagram-mcp:local .
docker rm -f azure-diagram-mcp-local
docker run -d --name azure-diagram-mcp-local -p 8000:8000 --restart unless-stopped azure-diagram-mcp:local
Connect an MCP client
Point your client at the /mcp URL. Example for Cursor / VS Code (.cursor/mcp.json or .vscode/mcp.json):
{
"mcpServers": {
"azure-diagram": {
"url": "http://localhost:8000/mcp"
}
}
}
For a deployed Container App, replace the host with your app FQDN: https://<fqdn>/mcp.
Optional: Blob persistence
Without storage, diagrams are returned inline only. For shareable URLs locally:
# Windows PowerShell
$env:AZURE_STORAGE_CONNECTION_STRING = "<connection-string>"
$env:BLOB_CONTAINER = "diagrams"
In Azure (Container Apps deployment), the app uses managed identity and user-delegation SAS — no connection string required.
Deploy to Azure Container Apps
Prerequisites: Azure Developer CLI, Docker, Azure subscription.
azd auth login
azd up # first-time: provision infra + deploy
azd deploy # subsequent code/image updates
azd down # tear down
azd up provisions (via infra/):
- Log Analytics + Container Apps environment
- Azure Container Registry
- User-assigned managed identity (ACR pull + Storage Blob Data Contributor)
- Storage account +
diagramscontainer - Container App with external ingress on port 8000
When complete, azd prints SERVICE_MCP_ENDPOINT_URL. Your MCP endpoint is that URL + /mcp.
| Variable | Purpose | | --- | --- | | STORAGE_ACCOUNT_URL | Blob endpoint (managed identity) | | BLOB_CONTAINER | Container name (diagrams) | | AZURE_CLIENT_ID | User-assigned identity client ID | | PORT | Listen port (8000) |
Project layout
src/azure_diagram_mcp/
server.py # FastMCP app, MCP tools, /health
renderer.py # Isolated subprocess render + style injection
dsl_style.py # Style profile loader for diagrams DSL
validation.py # AST safety allowlist
catalog.py # diagrams.azure.* node discovery
storage.py # Blob upload + SAS URLs
assets/
style/ # Style profiles (caf-default.yaml, …)
icon_map.yaml # Service metadata (reference pipeline)
renderers/ # CAF layout engine (reference pipeline, currently disabled)
examples/ # Sample reference-architecture YAML
infra/ # Bicep (Container Apps, ACR, Storage, identity)
Dockerfile
azure.yaml # azd service definition
Environment variables
| Variable | Default | Description | | --- | --- | --- | | PORT | 8000 | HTTP listen port | | HOST | 0.0.0.0 | Bind address | | LOG_LEVEL | INFO | Python log level | | AZURE_STORAGE_CONNECTION_STRING | — | Local/dev Blob storage | | STORAGE_ACCOUNT_URL | — | Blob endpoint (Azure managed identity) | | BLOB_CONTAINER | diagrams | Blob container name | | AZURE_CLIENT_ID | — | Managed identity client ID | | DISABLE_AZURE_MCP | — | Set to 1 to skip external best-practice calls |
License
MIT — see LICENSE.
Acknowledgements
Inspired by David Minkovski's Stop Drawing, Start Prompting and his stdio azure-diagram-mcp. This project adapts the idea for remote Streamable HTTP hosting, server-side style profiles, and durable Blob storage.






