Skip to content

CATDAB/matplotlib-azurefunction

 
 

Repository files navigation

matplotlib-azurefunction

Azure Functions (Python) app that renders matplotlib charts and returns a public URL to the PNG. Exposes the same renderer through two surfaces:

  • HTTP: POST /api/chart — usable from Copilot Studio, Power Automate, curl, anything.
  • MCP: a single tool generate_chart on an MCP server named MatplotlibChartGenerator, hosted at /runtime/webhooks/mcp.

The MCP tool is designed to be reasoning-driven: it requires the caller to articulate intent, takeaway, and audience before picking chart type and styling, so the resulting chart's annotations actually support the conclusion the viewer should reach.

Features

  • Chart types: bar, line, pie, scatter, histogram, area, box, violin, heatmap
  • Multi-series support (all types except pie and heatmap)
  • Dual y-axes for line/scatter/bar when series have different units
  • Error bars (bar/line), stacked variants (bar/area), horizontal bars
  • Log scales, explicit axis limits
  • Six annotation types: point, hline, vline, hspan, vspan, text
  • Styling: matplotlib themes (ggplot, seaborn-v0_8, dark_background, fivethirtyeight, bmh, grayscale, default), custom fonts, figsize, dpi, grid

Rendered PNGs are uploaded to Azure Blob Storage and the public URL is returned.

Architecture

client ──► /api/chart           ─┐
                                 ├─► render with matplotlib ─► upload PNG to Blob ─► return public URL
MCP client ──► generate_chart  ─┘

Both entry points share the same renderer in function_app.py.

Project layout

function_app.py                Single-file Functions app: HTTP route + MCP tool + renderer
host.json                      Functions host config (extension bundle, MCP server config)
requirements.txt               Python dependencies
openapi.json                   OpenAPI 2.0 spec for the /api/chart HTTP endpoint
local.settings.json.example    Template for local env config — copy to local.settings.json
copilot-studio-instructions.md Setup guide for using this as a Copilot Studio action
topics/                        Copilot Studio topic YAMLs (prepare-chart-data, render-chart)

Configuration

The app reads these environment variables (locally via local.settings.json, in Azure via App Settings):

Variable Required Default Purpose
AzureWebJobsStorage yes Functions runtime storage (can be UseDevelopmentStorage=true locally)
FUNCTIONS_WORKER_RUNTIME yes python Functions worker language
CHARTS_BLOB_CONNECTION_STRING yes Connection string for the storage account where rendered chart PNGs are uploaded
CHARTS_BLOB_CONTAINER no matplotlib-charts Blob container name (must be publicly readable for the returned URL to work)

⚠️ The blob container needs public-read access for the returned URL to render in browsers/Copilot. Either set the container's anonymous access level to "Blob", or replace _upload_to_blob with a SAS-based variant if you want to keep it private.

Local development

Requires Python 3.10+ and the Azure Functions Core Tools v4.

# 1. Clone & set up venv
git clone https://github.com/<YOUR-GH-USER>/matplotlib-azurefunction.git
cd matplotlib-azurefunction
python -m venv .venv
.venv\Scripts\activate            # Windows
# source .venv/bin/activate       # macOS/Linux
pip install -r requirements.txt

# 2. Copy the example settings and fill in your storage connection string
copy local.settings.json.example local.settings.json
# edit local.settings.json — set CHARTS_BLOB_CONNECTION_STRING

# 3. Run
func start

The HTTP endpoint will be at http://localhost:7071/api/chart. The MCP server is reachable at http://localhost:7071/runtime/webhooks/mcp.

Smoke test

curl -X POST http://localhost:7071/api/chart \
  -H "Content-Type: application/json" \
  -d '{
    "chart_type": "bar",
    "data": {"labels": ["A","B","C"], "values": [10, 20, 15]},
    "params": {"title": "Smoke test", "style": "ggplot"}
  }'

Deployment to Azure

Provision once:

# Resource group, storage account, function app (Linux, Python 3.11, Flex Consumption or Consumption)
az group create --name <rg> --location <region>
az storage account create --name <storage> --resource-group <rg> --sku Standard_LRS
az functionapp create \
  --resource-group <rg> \
  --name <app> \
  --storage-account <storage> \
  --runtime python --runtime-version 3.11 \
  --functions-version 4 \
  --os-type Linux --consumption-plan-location <region>

# Configure app settings — the storage connection string the renderer uploads to
az functionapp config appsettings set \
  --resource-group <rg> --name <app> \
  --settings \
    CHARTS_BLOB_CONNECTION_STRING="<connection-string>" \
    CHARTS_BLOB_CONTAINER="matplotlib-charts"

Deploy via zip (recommended for this repo — func azure functionapp publish --build remote has been observed to silently fail here):

# From the project root, with the venv activated
python -m pip install --target .python_packages\lib\site-packages -r requirements.txt
Compress-Archive -Path .\* -DestinationPath deploy.zip -Force
az functionapp deployment source config-zip \
  --resource-group <rg> --name <app> --src deploy.zip

HTTP endpoint

POST /api/chart?code=<function-key> — request body matches the MCP tool's chart_type / data / params shape. See openapi.json for the full schema.

Function-level auth is enforced (AuthLevel.FUNCTION) — each caller should get its own named key:

az functionapp keys set \
  --resource-group <rg> --name <app> \
  --key-name <caller-name> --key-type functionKeys

MCP tool

Tool name: generate_chart. Required arguments: intent, takeaway, audience, chart_type, data, params. The first three are reasoning notes — the server doesn't act on them, they exist to force the calling model to think before parameterizing the chart. See the tool description in function_app.py for full property docs.

Copilot Studio integration

See copilot-studio-instructions.md and the topic YAMLs in topics/.

License

MIT

About

No description, website, or topics provided.

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages

  • Python 100.0%