Skip to main content
Glama
H1er0

Azure Files MCP

by H1er0

Azure Files MCP (solo lectura)

Un servidor MCP remoto que le da a Claude acceso de solo lectura a una carpeta en un recurso compartido SMB de Azure Files, donde cada usuario que se conecta solo ve lo que sus propios permisos NTFS ya permiten.

Dos herramientas, nada más:

  • list_directory(path) - lista archivos/carpetas bajo la carpeta raíz configurada.

  • read_file(path) - lee el contenido de un archivo bajo la carpeta raíz configurada. Los archivos de texto plano se devuelven tal cual; PDF, Word (.docx) y Excel (.xlsx) se convierten a texto automáticamente (ver "Lectura de archivos PDF/Word/Excel" más abajo).

No hay ninguna herramienta de escritura, borrado o renombrado en todo este código base: no está simulada, no está deshabilitada por configuración, simplemente no existe.

Para instrucciones paso a paso de configuración de Azure/Entra, consulta SETUP.md. Este archivo cubre la arquitectura y las decisiones de diseño; SETUP.md cubre el recorrido clic a clic por el Portal.

Por qué esto no es exactamente lo que sugiere una primera lectura del ticket

El diseño original asumía: asignar el rol RBAC no privilegiado Storage File Data SMB Share Reader, luego llamar a la API FileREST de Azure Files con el token OAuth de cada usuario, y Azure aplicaría las ACL NTFS por usuario automáticamente.

Eso no funciona. Verificado directamente contra la documentación de la API REST de Microsoft (Authorize with Microsoft Entra ID (REST API)): cada operación de lectura de FileREST (List Directories and Files, Get File, Get File Properties, ...) requiere tanto .../files/read como .../readFileBackupSemantics/action. readFileBackupSemantics/action - el propio término de Microsoft para un modo que omite explícitamente la evaluación de ACL NTFS - solo lo otorga Storage File Data Privileged Reader/Contributor. Storage File Data SMB Share Reader no aparece en absoluto en la tabla de permisos REST - solo se aplica a conexiones genuinas del protocolo SMB (puerto 445, Kerberos), que un backend HTTPS de Node que llama a FileREST no puede usar.

Así que sobre REST, el rol es o privilegiado (omite ACL) o irrelevante. No hay forma de que Azure mismo aplique las ACL NTFS por solicitud REST/OAuth.

Lo que este servidor hace en su lugar: usa Storage File Data Privileged Reader (sigue siendo de solo lectura, sigue autenticado por usuario - ver más abajo), y aplica los permisos NTFS él mismo, en código, usando el descriptor de seguridad real que Azure Files expone para cada archivo/carpeta:

  1. Obtiene el permiso NTFS del archivo/carpeta como una cadena SDDL (llamada REST getPermission).

  2. Analiza la DACL en ACE individuales (src/acl/sddl.ts - hecho a mano; no existe una biblioteca Node/TS mantenida para esto).

  3. Resuelve el SID de AD local del usuario que llama y cada SID de grupo al que pertenece transitivamente, a través de Microsoft Graph (src/graph/sidResolver.ts).

  4. Evalúa la DACL contra ese conjunto de SID usando semántica real de AccessCheck de Windows: la denegación explícita supera a la concesión explícita, los bits no mencionados se deniegan por defecto (src/acl/evaluate.ts).

Esto es una aplicación genuinamente por usuario, solo que implementada aquí en lugar de delegada a la capa RBAC de Azure, porque Azure no tiene un mecanismo invocable por REST que lo haga por ti. El límite de seguridad real es este código base, no el RBAC de Azure - tenlo en cuenta al razonar sobre cualquier cosa a continuación.

Arquitectura

Este servidor es su propio servidor de autorización OAuth 2.1 - Claude nunca habla directamente con Entra. Es una elección de diseño deliberada, no la obvia, por lo que vale la pena explicar por qué: la especificación MCP requiere que los clientes envíen un parámetro resource RFC 8707 igual a la URL del propio servidor MCP, y Entra solo acepta un valor de resource que coincida con un URI de identificador verificado en el registro de la aplicación. Entra se niega rotundamente a registrar cualquier URL *.azurewebsites.net como una (dominio no verificado) - confirmado en vivo como AADSTS9010010, e imposible de arreglar con cualquier configuración del Portal. Así que, en su lugar, Claude se autoriza contra este servidor (cuya propia URL satisface trivialmente la comprobación de resource), y el servidor retransmite el inicio de sesión real a Entra entre bastidores (src/auth/mcpOAuthProvider.ts), devolviendo a Claude el token de acceso real e inalterado de Entra. Después de esa entrega, todo funciona con un token de portador normal emitido por Entra exactamente como lo habría hecho de otra manera.

Cada solicitud de lectura:

  1. Claude se autoriza a través de los endpoints /authorize y /token de este servidor (src/auth/mcpOAuthProvider.ts), que retransmiten el inicio de sesión real a Entra a través de una ruta /oauth/callback y devuelven el token de acceso real de Entra (audiencia = el registro de la aplicación de Entra de esta aplicación). Este servidor solo valida tokens en las solicitudes entrantes (src/auth/tokenVerifier.ts - firma a través de JWKS de Entra, emisor, audiencia, expiración) - nunca emite ni firma uno él mismo.

  2. path se normaliza y se comprueba contra la carpeta raíz configurada (src/files/pathScope.ts) antes de cualquier llamada a Azure - una ruta que se resuelve fuera de la raíz se rechaza independientemente de lo que el token permitiría de otro modo.

  3. El token de usuario entrante se intercambia, a través del flujo on-behalf-of de OAuth2 (src/auth/obo.ts, OnBehalfOfCredential de @azure/identity), por un nuevo token con ámbito https://storage.azure.com/.default. Cada llamada a Azure Files se hace con este token por usuario (src/files/shareClient.ts) - nunca con una entidad de servicio compartida o clave estática.

  4. El permiso NTFS del archivo/directorio se obtiene y se evalúa contra los SID que posee el usuario (src/graph/sidResolver.ts + src/acl/). Si no se concede el acceso, la herramienta devuelve un error de permiso denegado y nada más.

  5. Solo si se concede el acceso, la herramienta devuelve el listado del directorio o el contenido del archivo ya obtenido en el paso 3 - convertido a texto plano primero para PDF/Word/Excel (ver más abajo).

  6. Cada invocación - concedida, denegada o con error - emite una línea de registro de auditoría estructurada (src/audit/log.ts) que registra el usuario, la ruta solicitada y el resultado. Ver "Registro de auditoría" más abajo.

Resolver el SID propio del usuario / SID de grupo usa una llamada de credenciales de cliente de Graph solo de aplicación (src/graph/sidResolver.ts), no OBO. Es deliberado: son metadatos de identidad (a qué grupos pertenece este usuario), no datos de archivo, por lo que una identidad de aplicación compartida allí no viola "nunca usar una credencial compartida para leer datos de archivo" - las lecturas reales de Azure Files siguen siendo estrictamente por usuario en todo momento.

Debido a que resolveHeldSids se ejecuta en cada llamada a list_directory/read_file, su resultado (el SID propio del usuario más cada SID de grupo transitivo) se almacena en caché en memoria por usuario durante SID_CACHE_TTL_MS (por defecto 5 minutos, ver .env.example) - un acierto de caché omite Microsoft Graph por completo. Los cambios de pertenencia a grupos ocurren con poca frecuencia, por lo que esto reduce materialmente la latencia por solicitud y la carga de Graph sin ampliar significativamente la ventana de obsolescencia en un cambio de permisos. Establece SID_CACHE_TTL_MS=0 para deshabilitar el almacenamiento en caché (por ejemplo, mientras depuras un cambio de permisos que no aparece).

El relé OAuth en el paso 1 rastrea los inicios de sesión en curso en dos mapas en memoria de un solo uso y corta duración (pendingAuthorizations, issuedCodes en mcpOAuthProvider.ts). Eso está bien para una sola instancia de App Service, pero significa que este servidor no debe escalarse más allá de una instancia sin antes mover ese estado a un almacén compartido (por ejemplo, Redis) - una segunda instancia fallaría aleatoriamente los inicios de sesión que comenzaron en una instancia diferente de la que terminaron.

Lectura de archivos PDF/Word/Excel

read_file convierte algunos formatos de documentos binarios comunes a texto plano en el servidor (src/files/textExtract.ts), ya que el cliente MCP que renderiza los resultados de este conector no puede analizar por sí mismo un "recurso" binario para que Claude razone sobre él - solo el contenido de texto es realmente legible en el chat. Manejados: .pdf, .docx, .xlsx. No manejados: .doc/.xls binarios heredados (formatos de Office anteriores a 2007), que se degradan a devolver un blob ilegible, y PDFs escaneados/solo imagen, que devuelven un mensaje claro de "sin texto extraíble" en lugar de basura (sin OCR). La salida de extracción está limitada independientemente del límite de tamaño de archivo bruto (MAX_READ_FILE_BYTES), ya que la forma de texto de una hoja de cálculo densa puede exceder su tamaño binario.

Registro de auditoría

El acceso de lectura se aplica enteramente en el propio código de este servidor (ver más arriba), no por RBAC de Azure, por lo que no hay rastro de auditoría en ningún otro lugar - el registro de auditoría de este servidor (src/audit/log.ts) es ese rastro. Cada llamada a list_directory/read_file, independientemente del resultado, emite exactamente una línea JSON a stdout: marca de tiempo, nombre de la herramienta, ruta solicitada, oid y UPN del usuario que llama, la decisión (granted / denied / error), una razón para cualquier cosa que no sea granted, y cuánto tiempo tomó la llamada. Se escribe como una línea JSON de console.log simple en lugar de a través de una biblioteca de registro, para que fluya hacia cualquier pipeline de registro que el destino de despliegue ya recoja de stdout (por ejemplo, el flujo de registros de Azure App Service / Log Analytics) sin cableado adicional.

Configuración requerida de Azure/Entra (no automatizada por este repositorio)

Los pasos completos clic a clic están en SETUP.md. Resumen de lo que realmente se necesita:

  • RBAC: asigna Storage File Data Privileged Reader (solo lectura; no uses Contributor) al grupo de Entra cuyos miembros deberían poder usar este conector, con ámbito en la propia cuenta de almacenamiento. Esto reemplaza el rol SMB Share Reader del ticket original - ver la justificación más arriba. Esta es una puerta gruesa de "¿puede esta persona siquiera preguntar?", no la comprobación de permisos real - los permisos NTFS reales (aplicados en código, ver más arriba) siguen gobernando lo que cada usuario realmente ve.

  • Registro de aplicación: un registro de aplicación hace tres trabajos - cliente OAuth para Claude, identidad para el intercambio On-Behalf-Of a Azure Storage, y (normalmente) la identidad solo de aplicación para Graph. Necesita:

    • Exponer una API: URI de ID de aplicación api://<client-id> (el predeterminado), con un ámbito llamado access_as_user.

    • Autenticación: exactamente un URI de redirección web, <PUBLIC_BASE_URL>/oauth/callback - el propio callback de este servidor, no el de Claude. El callback a nivel de plataforma de Claude (https://claude.ai/api/mcp/auth_callback) nunca se registra en Entra en absoluto; ver "Arquitectura" más arriba para saber por qué.

    • Un secreto de cliente.

    • Este servidor no admite el Registro Dinámico de Clientes - solo reconoce un cliente (el ID de cliente/secreto del propio registro de aplicación), que es también lo que configuras como ID de Cliente OAuth/Secreto al agregar esto como conector personalizado en Claude.

  • Permisos de Graph API (aplicación, con consentimiento de administrador) en el registro de aplicación al que apunte GRAPH_CLIENT_ID: User.Read.All y GroupMember.Read.All (o el más amplio Directory.Read.All) - necesarios para leer onPremisesSecurityIdentifier para usuarios y sus pertenencias a grupos transitivas.

Configuración

Todos los ajustes son variables de entorno - ver .env.example y la tabla de referencia completa en SETUP.md. Lo importante para la reutilización: ROOT_PATH (más STORAGE_ACCOUNT_NAME/SHARE_NAME) es lo único que necesita cambiar para reorientar este servidor a una carpeta, recurso compartido o cliente diferente más adelante. Se lee una vez al inicio del proceso y nunca se acepta como parámetro de herramienta, por lo que no hay forma de que un llamador amplíe el ámbito en tiempo de ejecución.

Para reorientar a una carpeta/recurso compartido diferente:

  1. Actualiza STORAGE_ACCOUNT_NAME, SHARE_NAME, ROOT_PATH en la configuración de App Service.

  2. Asegúrate de que el grupo de Entra objetivo tenga Storage File Data Privileged Reader en la nueva cuenta de almacenamiento.

  3. Reinicia la aplicación. No se necesitan cambios de código ni de compilación.

Ejecución local

npm install
cp .env.example .env   # fill in real values
npm run dev

Compilar, verificación de tipos, pruebas

npm run build   # tsc type-check + emit to dist/
npm test        # vitest - sddl parser, ACE evaluator, path-scope, SID cache, audit log unit tests

Despliegue en Azure App Service

Ver SETUP.md para el recorrido completo. Versión corta:

  1. Empaqueta src/, package.json, package-lock.json y tsconfig.json en un zip: nunca un dist/ o node_modules/ precompilado. El compilador Oryx de Azure lo compila desde cero, en el servidor, en cada implementación (requiere el App Setting SCM_DO_BUILD_DURING_DEPLOYMENT=true).

  2. Implementa el zip en un plan de App Service Linux, Node 20+, exactamente una instancia (consulta la nota sobre el estado del relay OAuth en memoria en "Arquitectura" más arriba).

  3. Configura todas las variables de .env.example como Application Settings del App Service (no un archivo .env commiteado).

  4. PUBLIC_BASE_URL debe ser la URL HTTPS real del App Service, sin barra final: una barra final produce dobles barras en las URLs generadas y rompe la coincidencia del redirect URI de Entra.

  5. Verifica antes de conectar Claude: GET /healthz devuelve ok, y GET /.well-known/oauth-protected-resource/mcp devuelve un documento JSON de metadatos (el sufijo /mcp es obligatorio según RFC 9728, ya que la URL del servidor de recursos tiene un componente de ruta /mcp).

  6. El endpoint MCP al que se conecta Claude es POST {PUBLIC_BASE_URL}/mcp.

Este servidor implementa OAuth manualmente (validación de JWT, los endpoints de metadatos /.well-known/oauth-*, y ahora el relay completo del servidor de autorización, mediante el mcpAuthRouter del SDK de MCP) en lugar de depender de la integración MCP "Easy Auth" integrada del App Service. Esa integración es real pero aún está en vista previa, y la documentación de Microsoft advierte explícitamente contra el reenvío de su token validado a un recurso downstream; de todos modos tendrías que escribir el intercambio on-behalf-of para Storage, así que no habría reducido la cantidad de código aquí, solo habría añadido riesgo de etapa de vista previa.

Limitaciones conocidas

  • Los grupos AD locales de dominio pueden no resolverse. Las ACL de NTFS se comparan con los SIDs de AD local, resueltos mediante onPremisesSecurityIdentifier de Microsoft Graph. Los grupos locales de dominio no se sincronizan/escriben de manera confiable en Entra ID, por lo que una ACE que otorga acceso a un grupo local de dominio no sincronizado no se puede comparar. Esto falla de forma cerrada: un SID de grupo no resuelto nunca puede satisfacer una ACE de ALLOW, por lo que el peor caso es que un usuario vea menos de lo que le corresponde, nunca más (src/graph/sidResolver.ts, src/acl/evaluate.ts). Si las ACL de una carpeta de destino usan grupos locales de dominio, verifica durante las pruebas que esto no sub-permita a usuarios reales; si lo hace, la solución es re-ACLar con grupos universales/globales o agregar una búsqueda de respaldo LDAP (no implementada aquí).

  • El recorrido de directorios (FILE_TRAVERSE) en carpetas ancestras no se verifica por separado. Windows otorga "bypass traverse checking" a Usuarios Autenticados por defecto en la mayoría de las implementaciones reales, por lo que esto coincide con el comportamiento típico del mundo real, pero si el entorno de un cliente tiene restricciones de recorrido no predeterminadas en las carpetas entre la raíz del recurso compartido y ROOT_PATH, vuelve a verificar durante las pruebas.

  • Solo una instancia de App Service - consulta la nota sobre el relay OAuth en "Arquitectura" más arriba.

  • Sin escritura/eliminación/renombrado, por diseño: no es una brecha, es una restricción deliberada.

  • Los archivos binarios heredados .doc/.xls y los PDFs escaneados o solo imagen no son legibles - consulta "Lectura de archivos PDF/Word/Excel" más arriba.

  • Requiere que el AD local del tenant de destino esté sincronizado con Entra (Entra Connect / Cloud Sync) con los SIDs locales fluyendo: todo el modelo de enforcement de NTFS por usuario depende de ello. No funcionará para un tenant solo en la nube, nativo de Entra, sin AD local.

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

Maintenance

Maintainers
Response time
Release cycle
1Releases (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

  • Read-only MCP access to sessions, funnels, campaigns, errors, live visitors, and anomalies.

  • Read-only CVE intelligence, remediation playbooks, and agent setup guides. Not a scanner.

  • Read-only access to your VortexIQ store data: audits, KPIs, alerts, Brand DNA, reports, Ask VIQ.

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/H1er0/Azure-Files-MCP'

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