Skip to main content
Glama
sinanpl

mcp-demo-aad-viz

by sinanpl

mcp-demo-aad-viz

Un ejemplo práctico de dos capacidades de MCP que normalmente se demuestran por separado, pero que juntas resultan considerablemente más interesantes:

  • Autorización con Microsoft Entra ID (Azure AD) — el servidor es un servidor de recursos OAuth 2.1. Tu pertenencia a grupos de Entra decide qué conjuntos de datos existen para ti. No es que "se muestren y luego se rechacen": simplemente no están.

  • Aplicaciones/extensiones integradas (io.modelcontextprotocol/ui) — los gráficos llegan como un widget interactivo renderizado en la conversación, y ajustarlo cuesta cero tokens.

La combinación de ambas es lo que vale la pena ver: un constructor de gráficos Altair cuyo desplegable de conjuntos de datos contiene exactamente los conjuntos que permiten tus grupos de Entra, aplicado en el servidor en cada interacción con el widget.

Desarrollado contra MCP 2026-07-28 con el SDK de Python mcp 2.0. Se despliega en Azure Container Apps. Licencia MIT.

Atención: esto es una demostración, no un producto. Incluye diez conjuntos de datos de ejemplo públicos y un modelo de niveles deliberadamente sencillo para que la historia de la autorización se entienda con claridad.

El widget interactivo del constructor de gráficos Altair, con controles de conjunto de datos, eje y marca junto a un diagrama de dispersión del conjunto de datos Palmer penguins.


Pruébalo sin Azure

No necesitas tenant, ni autenticación, ni despliegue: basta para ver el widget funcionar.

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

A continuación, todo el que invoca recibe el acceso de los tres niveles de conjuntos de datos. Apunta cualquier host de MCP Apps a http://localhost:3001/mcp: consulta Desarrollo local para ver un host basado en navegador que te muestra todo el protocolo ui/ mientras se desarrolla.

Pruébalo con Azure

# 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

El script muestra tu punto de conexión de MCP. Añádelo a tu cliente exactamente como se imprime, porque la ruta /mcp forma parte del identificador de recurso de OAuth → docs/CONNECT.md.

Usa una --tag única en cada despliegue. Con una tag repetida, la plantilla Bicep es byte a byte idéntica a la del despliegue en curso, no se crea ninguna revisión nueva y el despliegue se notifica como correcto, aunque no llegue a entregarse nada.


Qué demuestra

Una destaque de MCP

Dónde

Qué verás

Autorización (OAuth 2.1 RS)

auth.py

La pertenencia a un grupo cambia el tamaño del catálogo

MCP Apps (io.modelcontextprotocol/ui)

chart_builder.html

Los desplegables redirigen el gráfico en su lugar

Herramientas solo de la app (visibility: ["app"])

render_chart

El renderzo repetido del widget cuesta cero tokens

input_required

plot_dataset

Los hosts sin widgets reciben en su lugar un formulario

Ampliación de ámbito (403 insufficient_scope)

export_chart

La primera exportación desencadena un nuevo consentimiento

Recursos y plantillas

data://catalog

Filtrar por permisos

Completions

argumentos de conjunto de datos

El autocompletado nunca sugiere un conjunto de datos que no puedes abrir

Prompts

explore_dataset

Un primer recorrido guiado

Dos elementos que la especificación obsojan en esta revisión y que este servidor, por lo tanto, evita: sampling y los logging (SEP-2577). suggest_chart elige una marca a partir de los tipos de columna en lugar de correr a un modelo en consulta.


El modelo de autorización

Dos ejes independientes. Confundirlos es el error habitual.

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)

| Nivelzone | el rol | Conjuntos de datos | | open | Datasets.Open | iris, penguins, cars, barley | | operations | Datasets.Operations | seattle-weather, us-employment, gapminder | | confidential | Datasets.Confidential | diamonds, movies, titanic |

Los ingenieros y los científicos tienen el mismo número de conjuntos de datos, pero no los mismos. Por eso dos colegas que hacen la misma pregunta obtienen respuestas diferentes.

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

--now también asigna los roles directamente al usuario: un cambio de grupo puede tardar varios minutos en llegar a un nuevo token, mientras que una asignación directa deriva unos veinte segundos.

Los roles son una denegación firme. No puedes solicitar entrar en un grupo. Los conjuntos de datos que están fuera de tu nivel quedan fuera de los resultados de tools/list, de resources/list, de las sectorcompletions y del desplegable del widget; no es que se muestren y luego se rechacen.

Los ámbitos son una denegación del tipo suave. Si tienes Datasets.Export, devuelve 403 con un desafío WWW-Authenticate: Bearer error="insufficient_scope" y el cliente vuelve a autorizarse para pedirlo.

Dos trampas específicas de Entra que este repositorio resuelve, y que producen errores desconcertantes si se configuran a mano: Entra no has registro dinámico de clientes, no tiene punto de punto de terminal de metadata RFC 8414, y la URL MCP debe registrarse como un identificación URI de aplicación; de lo contrario, resource= (RFC 8707) falla con AADSTS4510010.

Detalles completos: docs/AUTHZ.md · docs/CONNECT.md.


Por qué el widget es interesante

Una spec Vega-Lite con datos incrustados ocupa entre 30 y 300 KB. Devolverla directamente desde una herramienta coloca esos datos en el contexto de la conversación en cada gráfico.

En lugar de eso:

  1. plot_dataset devuelve un manejado de ~900 bytes — codificación, recuentos de filas, avisos. No Hay spec.

  2. El host renderiza la app ui:// y le entrega ese cueposuelo.

  3. El widget llama a render_chart (una herramienta solo para la app) para obtener la spec real.

Como el paso 3 se origina en la app y no en el modelo, la spec nunca entra en la conversación. Cambiar un desplegable es solo una llamada promedio al servidor y equival a cero tokens.

La alegación de autorización también se cumple aquí: render_chart y app_catalogue vuelven a derivar los niveles del que la llamada en cada llamada, por lo que el widget no puede llegar a un conjunto de datos que token no permita — incluso aunque el modelo no esté ya en el lazo.

Notas de diseño: docs/DESIGN.md.


Desarrollo local

Dos bancos de pruebas, para dos preguntas diferentes.

"¿Es correcto el HTML/JS de mi widget?" — un host en miniatura que habla el verdadero protocolo ui/ de postMessage y registra cada mensaje, sin que ningún cliente MCP intervenga:

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

Inyecta la misma CSP restrictiva que aplica un host real, de modo que los fallos de CSP se ven aquí en lugar de solo en producción. --strip-structured-content sustetya un defecto descrito en docs/HOST-COMPATIBILITY.md.

"¿Es correcta la superficie de MCP?" — el host de referencia del repositorio MCP Apps, conduciendo el servidor real por HTTP:

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

Este es el banco de pruebas en el que conviene fijarse primero cuando un widget aparece en blanco: informe de las violaciones del protocolo que los hosts de producción se tragan silenciosamente. El uso de MCP_DATAVIZ_AUTH_ENABLED=false al lanzar el servidor también relaja la comprobación Origin del SDK y añade las cabeceras CORS, que un host basado en navegador necesita y que se encuentran desactivadas mientras la autenticación esté activada.

Ten en cuenta que el HTML del widget se lee una vez en la construcción del servidor, por lo que editarlo requiere reiniciar el servidor.


Compatibilidad con hosts

El soporte de MCP Apps varía entre hosts de maneras que producen los mismos síntomas visuales: normalmente un widget vacío o plegado y no hay ningún error. docs/HOST-COMPATIBILITY.md documenta lo que realmente se observó, cómo se realizó el aislamiento de cada causa y cuáles se pueden corregir en el servidor (uno de tres) en lugar de los que no.


Conjuntos de datos

Cuatro abiertos, tres de operaciones, tres confidenciales: todos conjuntos de datos públicos de muestra de la colección Vega datasets. Se insertan en la imagen en tiempo de compilación, por lo que el contenedor en ejecución no necesita acceder a ningún origen de datos. Las etiquetas de nivel son ilustrativas, elegidas para que el modelo de acceso sea concreto.

abierto

Operaciones

confidencial (motivo ilustrativo)

iris

seattle-weather

diamonds — precio por unidad

penguins

us-employment

movies — ingresos comerciales

cars

gapminder

titanic — datos a nivela personal

barley


Estructura

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

El paquete Python conserva el nombre mcp_dataviz (y el prefijo de variable de entorno MCP_DATAVIZ_) aunque el repositorio sea mcp-demo-aad-viz; renombrarlo solo reetiquetría todos los nombres de recursos de Azure y las variables de entorno sin aportar ningún beneficio.

Pruebas

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

tests/test_http.py ejecuta un servidor uvicorn real y reproduce los siguientes que se indican: el desafío 401, el documento PRM, la subida de 403 insufficient_scope y el ciclo de ida y vuelta de input_required.

Coste

Container Apps reduce el número de réplicas a cero (minReplicas: 0), por lo que una demo sin uso no cuesta prácticamente nada; ACR Basic y Log Analytics serán los únicos cargos fijos (pocoos € / mes).

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

Licencia

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