mcp-demo-aad-viz
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.

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.pyMCP_DATAVIZ_AUTH_ENABLED=false MCP_DATAVIZ_PORT=3001 uv run python -m mcp_datavizA 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 v1El 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) | La pertenencia a un grupo cambia el tamaño del catálogo | |
MCP Apps ( | Los desplegables redirigen el gráfico en su lugar | |
Herramientas solo de la app ( |
| El renderzo repetido del widget cuesta cero tokens |
|
| Los hosts sin widgets reciben en su lugar un formulario |
Ampliación de ámbito ( |
| La primera exportación desencadena un nuevo consentimiento |
Recursos y plantillas |
| Filtrar por permisos |
Completions | argumentos de conjunto de datos | El autocompletado nunca sugiere un conjunto de datos que no puedes abrir |
Prompts |
| 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:
plot_datasetdevuelve un manejado de ~900 bytes — codificación, recuentos de filas, avisos. No Hay spec.El host renderiza la app
ui://y le entrega ese cueposuelo.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:8765Inyecta 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:8080Este 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) |
|
|
|
|
|
|
|
|
|
|
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-COMPATIBILITYEl 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 neededuv run ruff check src tests scriptstests/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.shLicencia
MIT.
This server cannot be installed
Maintenance
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.
Latest Blog Posts
- Who's Calling? MCP Hosts Are an Identity Blind Spot (And the Spec Knows It)By Om-Shree-0709 on .mcpAgent IdentityOAuth 2.1
- Your AI Chatbot Just Exposed Your CEO's Salary to an InternBy Om-Shree-0709 on .Agent IdentityMCP SecurityOAuth Delegation
- Why MCP Servers Need Execution Sandboxing (And Why Your Current Stack Isn't Enough)By Om-Shree-0709 on .Agentic AiPrompt InjectionWebAssembly
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