Skip to main content
Glama
sinanpl

mcp-demo-aad-viz

by sinanpl

mcp-demo-aad-viz

Ein ausgearbeitetes Beispiel für zwei MCP-Fähigkeiten, die üblicherweise isoliert gezeigt werden und zusammen deutlich interessanter sind:

  • Microsoft Entra ID (Azure AD)-Autorisierung – der Server ist ein OAuth-2.1-Ressourcenserver. Ihre Entra-Gruppenmitgliedschaft entscheidet, welche Datasets für Sie existieren. Nicht „aufgelistet, dann verweigert“ – sondern schlicht nicht vorhanden.

  • Inline-Apps / Erweiterungen (io.modelcontextprotocol/ui) – Diagramme treffen als interaktives Widget ein, das innerhalb der Konversation gerendert wird, und ihre Anpassung kostet null Tokens.

Beides zusammen ergibt das Sehenswürdige: einen Altair-Diagramm-Builder, dessen Dropdown genau die Datasets enthält, die Ihre Entra-Gruppen erlauben – serverseitig durchgesetzt bei jeder einzelnen Interaktion mit dem Widget.

Erstellt mit MCP 2026-07-28 und dem Python-SDK mcp 2.0. Läuft auf Azure Container Apps. MIT-lizenziert.

Hinweis: Dies ist eine Demonstration, kein Produkt. Sie bringt zehn öffentliche Beispieldatensätze und ein bewusst simples Stufenmodell mit, damit die Autorisierungs-Logik gut nachvollziehbar ist.

Das interaktive Altair-Diagramm-Builder-Widget, das Dataset-/Achsen-/Mark-Steuerungen neben einem gerenderten Streudiagramm der Palmer-Pinguine zeigt


Ohne Azure ausprobieren

Kein Mandant, keine Authentifizierung, kein Deployment – genug, um das Widget in Bewegung zu sehen:

uv sync && uv run python scripts/fetch_datasets.py
MCP_DATAVIZ_AUTH_ENABLED=false MCP_DATAVIZ_PORT=3001 uv run python -m mcp_dataviz

Jeder Aufrufer wird dann so behandelt, als hätte er Zugriff auf alle drei Dataset-Stufen. Richten Sie einen beliebigen MCP-Apps-Host auf http://localhost:3001/mcp aus – für einen browserbasierten Host, der das komplette ui/-Protokoll live anzeigt, siehe Lokale Entwicklung.

Mit Azure ausprobieren

# 1. Directory objects (app registration, scopes, app roles, 3 groups)
./scripts/entra-setup.sh
# 2. Put yourself in a group to pick a persona
source entra.env
az ad group member add --group "$MCP_DATAVIZ_GROUP_ANALYSTS_ID" \
                       --member-id "$(az ad signed-in-user show --query id -o tsv)"
# 3. Deploy (builds the image in Azure; no local Docker needed)
./scripts/deploy.sh --tag v1

Das Skript gibt Ihren MCP-Endpunkt aus. Fügen Sie ihn exakt so in Ihren Client ein – der /mcp-Pfad ist Teil der OAuth-Ressourcenangabe → docs/CONNECT.md.

Verwenden Sie bei jedem Deployment einen eindeutigen --tag. Bei einem wiederholten Tag ist das Bicep-Template byte-identisch mit dem bereits laufenden, es wird keine neue Revision erstellt, und das Deployment meldet Erfolg, während nichts ausgeliefert wird.


Was es zeigt

MCP-Funktion

Wo

Was Sie sehen

Autorisierung (OAuth-2.1-Ressourceserver)

auth.py

Gruppenmitgliedschaft ändert die Größe des Katalogs

MCP-Apps (io.modelcontextprotocol/ui)

chart_builder.html

Dropdowns rendern das Diagramm direkt im Diagramm neu

Nur-App-Tools (visibility: ["app"])

render_chart

Das Neu-Rendern des Widgets kostet null Tokens

input_required

plot_dataset

Hosts ohne Widgets erhalten stattdessen ein Formular

Scope-Hochstufung (403 insufficient_scope)

export_chart

Der erste Export verant eine erneute Zustimmung

Ressourcen + Vorlagen

data://catalog

Nach Berechtigungen gefiltert

Vervollständingungen

Dataset-Argumente

Die Autovervollhält nennt nie ein Dataset, das Sie nicht öffnen dürfen

Prompts

explore_dataset

Geführte erste Erkundung

Zwei Dinge, die die Spezifikation in dieser Revision abgelehnt hat und die dieser Server deshalb vermeidet: sampling und die logging-Fähigkeit (SEP-2577). suggest_chart wählt einen Marker anhand der Spaltentypen statt nach einem Zeichnen.


Das Autorisierungsmodell

Zwei unabhängige Achsen. Das übliche Mincheingang.

WHO YOU ARE                                 WHAT YOU'RE DOING
Entra group ──► app role ──► dataset tier   OAuth scope ──► operation
                (roles claim)                              (scp claim)

analysts   → Open                      ( 4)  Datasets.Read   → everything
engineers  → Open + Operations         ( 7)  Datasets.Export → export_chart
scientists → Open + Confidential       ( 7)     ↑ withheld at first, so the
             ...a *different* 7            first export triggers a step-up
(no group) → nothing                   ( 0)

Stufe

Rolle

Datasets

open

Datasets.Open

iris, penguins, cars, barley

operations

Datasets.Operations

seattle-weather, us-employment, gapminder

confidential

Datasets.Confidential

diamonds, movies, titanic

Ingenieure und Wissenschaftler halten jeweils die gleiche Anzahl an Datasets, aber nicht dieselben; zwei Kollegen, die dieselbe Frage stellen, erhalten also unterschiedliche Antworten.

./scripts/assign-persona.sh engineer colleague@example.com --now

--now weist die Rollen zusätzlich direkt dem Benutzer zu: Eine Gruppenänderung kann bei Entra einige Minuten brauchung, bis sie ein neues Token beeinflusst, eine direkte Zuweisung hingegen etwa zwanzig Sekunden.

Rollen sind eine harte Verweigung. Sie können sich nicht in eine Gruppe hineinfragen. Datasets außerhalb Ihrer Stufe fehlen in den Ergebnissen von tools/list, resources, Vervollständigen und im Dropdown des Widgets – nicht aufgelistet und dann verweigert.

Scopes sind eine weiche Verweigerung. Fehlbare Datasets.Export ergibt 403 mit einer WWW-Authenticate: Bearer error="nefik"insufficient_scope"-Herausforderung, und der Client autorisiert sich erneut, um den Scope abzufragen.

Zwei Entra-spezifische Stolpersteine werden hier umgegangen, die beide verwirrende Fehler erzeugen, wenn die Konfiguration von Hand: Entra hat keine dynamische Client-Registrierung und keinen Metadaten-Endpunkt nach RFC 8414, und die MCP-URLs müssen als Application-ID-URI (Application-ID-URI) registriert sein, sonst schlägt die RFC-8707 resource=-Angabe mit AADSTS9010010 fehl.

Ausführliche Details: docs/AUTHZ.md · docs/CONNECT.md.


Warum das Widget interessant ist

Eine Vegar-Lite-Spezifikation mit Inline-Daten ist 30–300 KB groß. Wenn Sie sie naiv aus einem Tool zurück geben, landet sie bei jedem Diagramm im Kontext des Modells.

Stattdessen:

  1. plot_dataset gibt einen ~900-Byte-Handle zurück – Codierung, Zeilenanzahlen, Warnungen. Kein Spezifikation.

  2. Der Host rendert die ui://-App und übergibt ihm diesen Handle.

  3. Das Widget ruft render_chart (ein Nur-App-Tool) für das eigentliche Spezifikation ab.

Weil Schritt 3 der Client-Typstered aus der App stammt und nicht vom Model, gelangt die Spezifikation nie in die Konversation. Das Ändern eines Dropdowns ist eine kleiner Zserver-Roundtrip und null Voltes.

Auch die Autorisierungsstory bleibt erhalten: render_chart und app_catalogue leiten die Stufen des Aufrufers bei jedem Aufruf neu ab, sodass das Widget kein Dataset erreichen kann, das das Token nicht erlaubt – selbst wenn sich das Modell nicht mehr in feedback-Schleife befindet.

Design-Notizen: docs/DESIGN.md.


Lokale Entwicklung

Zwei Testumgebungen für zwei unterschiedliche Fragen.

„Ist das HTML/JS meines Widget korrekt?“ – ein Miniatur-Host, der das reale ui/-postMessage-Protokoll spricht und jede Nachricht protokolliert, und ein MCP-client vorhanden:

uv run python scripts/preview_widget.py     # http://127.0.0.1:8765

Er setzt dieselbe strikte CSP ein, die auch ein echter Host anwendet, sodass sich CSP-Fehler hier reproduzieren lassen und nicht nur in Produktion auftreten. --strip-structured-content simuliert den einen Hostefekt, die in docs/HOST-COMPATIBILITY.md beschrieben wird.

„Ist meine MCP-Oberfläche korrekt?“ – der Referenz-Host im Modell-Apps-Repository, der den echten Server über HTTP ansteuert:

git clone https://github.com/modelcontextprotocol/ext-apps.git
cd ext-apps && npm install && cd examples/basic-host
SERVERS='["http://localhost:3001/mcp"]' npm start   # http://localhost:8080

Das ist die Testungen die mit zuerst wählt, wenn ein Widget leer bleibt: Sie meldet Protokollverletzungen, die echte Hosts still. Wenn Sie den Server sein, erlauben sie das Origin-Check des SDKs und ergänzt die CORS-Header, die ein browserbasierter Host braucht, und die ausgeschaltet sind, sobald die Autorisierung aktiv ist.

Beachten Sie: Die HTML des Widgets wird **einmal „beim Serverstart“ gelesen – wird Programmieren Sie das Widget, ist ein Neustart des Servers erforderlich.


Host-Kompatibilität

Die Support zwischen verschiedenen Hosts variiert und erzeugt zuverlässig identisch aussehende Symptome – in der Regel ein leeres oder eingeklapptes Widget ohne Hinweis auf einem Fehler. docs/HOST-COMPATIBILITY.md dokumentiert die – CTX, wie die einzelnen Ursachen isoliert wurden und welche serverseitig behebbar sind (eine von drei) und welche nicht.


Datasets

Vier öffentliche, drei operations, drei vertrauliche – alles öffentliche Beispieldatensätze aus der Vega-Datasets-Sammlung. Sie werden zur Build-Zeit in das Image eingebunden, sodass der laufende Container keinen Netzwerkzugriff auf Datensenger braucht. Die Stufennamen sind nur illustrativ und so gewählt, dass das Zugriffsmodell greifbar ist.

open

minimal

vertraulich (illustrativer Grund)

iris

seattle-weather

diamonds – Stückpreise

penguins

us-employment

movies – Umsatzlust

cars

gapminder

titanic – personenbezogene Datensätze

barley


Layout

src/mcp_dataviz/
  server.py       tools, resources, prompts, completions
  auth.py         Entra token verification, roles→tiers, scope challenge
  catalog.py      the 10 datasets and the tier gate
  charts.py       Altair → Vega-Lite, with aggregation pushed into pandas
  config.py       environment settings (nothing hardcoded)
  widgets/        the MCP App
infra/            Bicep: ACR, Container Apps, Log Analytics
scripts/          entra-setup.sh, deploy.sh, assign-persona.sh, preview_widget.py
tests/            168 tests, incl. HTTP-level auth and step-up
docs/             AUTHZ, CONNECT, DESIGN, HOST-COMPATIBILITY

Das Python-Paket behält den Namen mcp_dataviz (und das Präfix DevExperimente Umgebungsvariable) bei, auch wenn das Repository allow heißt- Demo` heißt; eine Umbenennung würde jeden Azure-Ressourcennamen und jede Umgebungsvariable anlaufen, ohne einen Vorteil zu bringen.

Tests

uv run pytest          # 168 tests, no Azure needed
uv run ruff check src tests scripts

tests/test_http.py startet einen echten uvicorn Server und prüft den 401-Challenge, den PRM-Dokument, das 403 unsufficient_scope-Hochstufung und den input_required-Rundtrip.

Kosten

Container Apps skaliert auf null (minReplicas: 0), sodass eine ruhende Demo praktisch nichts kostet; ACR Basic und Log Analytics sind die einzigen stehenden Gebühren (einige Euro pro Monat).

az group delete --name rg-mcp-dataviz --yes && ./scripts/entra-teardown.sh

Lizenz

MIT.

-
license - not tested
Not graded
quality - not tested
C
maintenance

Maintenance

Maintainers
Response time
Release cycle
Releases (12mo)
Commit activity

Resources

Unclaimed servers have limited discoverability.

Looking for Admin?

If you are the server author, to access and configure the admin panel.

Related MCP Connectors

  • MCP Hub: AI service discovery, per-user OAuth, and multi-service workflow orchestration

  • Official Microsoft MCP Server to query Microsoft Entra data using natural language

  • Manage SRG+ hubs, channels, content, assets, users, and workspaces from any MCP-aware AI agent.

View all MCP Connectors

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/sinanpl/mcp-demo-aad-viz'

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