Skip to main content
Glama
H1er0

Azure Files MCP

by H1er0

Azure Files MCP (schreibgeschützt)

Ein Remote-MCP-Server, der Claude schreibgeschützten Zugriff auf einen Ordner in einer Azure Files SMB-Freigabe gewährt, wobei jeder verbundene Benutzer nur das sieht, was seine eigenen NTFS-Berechtigungen bereits erlauben.

Zwei Tools, sonst nichts:

  • list_directory(path) – listet Dateien/Ordner unter dem konfigurierten Stammordner auf.

  • read_file(path) – liest den Inhalt einer Datei unter dem konfigurierten Stammordner. Reine Textdateien werden unverändert zurückgegeben; PDF, Word (.docx) und Excel (.xlsx) werden automatisch in Text umgewandelt (siehe „PDF/Word/Excel-Dateien lesen“ unten).

Es gibt kein Schreib-, Lösch- oder Umbenennungs-Tool irgendwo in dieser Codebasis – nicht ausgestubst, nicht per Konfiguration deaktiviert, sondern einfach nicht vorhanden.

Schritt-für-Schritt-Anleitungen zur Azure/Entra-Einrichtung finden Sie in SETUP.md. Diese Datei behandelt die Architektur und Designentscheidungen; SETUP.md enthält die klick-für-Klick-Portal-Anleitung.

Warum dies nicht ganz das ist, was ein erster Blick auf das Ticket vermuten lässt

Das ursprüngliche Design ging davon aus: Weisen Sie die nicht privilegierte RBAC-Rolle Storage File Data SMB Share Reader zu, rufen Sie dann die FileREST-API von Azure Files mit dem eigenen OAuth-Token jedes Benutzers auf, und Azure würde NTFS-ACLs pro Benutzer automatisch durchsetzen.

Das funktioniert nicht. Direkt gegen die REST-API-Dokumentation von Microsoft verifiziert (Autorisieren mit Microsoft Entra ID (REST-API)): jede FileREST-Leseoperation (List Directories and Files, Get File, Get File Properties, ...) erfordert sowohl .../files/read als auch .../readFileBackupSemantics/action. readFileBackupSemantics/action – Microsofts eigener Begriff für einen Modus, der die NTFS-ACL-Auswertung explizit überspringt – wird nur von Storage File Data Privileged Reader/Contributor gewährt. Storage File Data SMB Share Reader erscheint überhaupt nicht in der REST-Berechtigungstabelle – es gilt nur für echte SMB-Protokollverbindungen (Port 445, Kerberos), die ein Node-HTTPS-Backend, das FileREST aufruft, nicht verwenden kann.

Über REST ist die Rolle also entweder privilegiert (umgeht ACLs) oder irrelevant. Es gibt keine Möglichkeit, Azure selbst dazu zu bringen, NTFS-ACLs pro Anfrage auf REST/OAuth-Basis durchzusetzen.

Was dieser Server stattdessen tut: Er verwendet Storage File Data Privileged Reader (weiterhin schreibgeschützt, weiterhin pro Benutzer authentifiziert – siehe unten) und setzt NTFS-Berechtigungen selbst im Code durch, unter Verwendung des echten Sicherheitsdeskriptors, den Azure Files für jede Datei/jeden Ordner bereitstellt:

  1. Rufen Sie die NTFS-Berechtigung der Datei/des Ordners als SDDL-String ab (getPermission-REST-Aufruf).

  2. Parsen Sie die DACL in einzelne ACEs (src/acl/sddl.ts – handgeschrieben; es gibt keine gepflegte Node/TS-Bibliothek dafür).

  3. Lösen Sie die eigene On-Premises-AD-SID des aufrufenden Benutzers und alle Gruppen-SIDs, zu denen er transitiv gehört, über Microsoft Graph auf (src/graph/sidResolver.ts).

  4. Bewerten Sie die DACL gegen diese SID-Menge mit echten Windows-AccessCheck-Semantiken – explizites Verweigern schlägt explizites Erlauben, nicht erwähnte Bits werden standardmäßig verweigert (src/acl/evaluate.ts).

Dies ist eine echte Durchsetzung pro Benutzer – nur hier implementiert, anstatt an die RBAC-Ebene von Azure delegiert, weil Azure keinen über REST aufrufbaren Mechanismus hat, der das für Sie erledigt. Die eigentliche Sicherheitsgrenze ist diese Codebasis, nicht Azure RBAC – behalten Sie das im Hinterkopf, wenn Sie über alles Weitere nachdenken.

Architektur

Dieser Server ist sein eigener OAuth-2.1-Autorisierungsserver – Claude kommuniziert nie direkt mit Entra. Das ist eine bewusste Designentscheidung, nicht die naheliegende, daher lohnt es sich zu erklären, warum: Die MCP-Spezifikation verlangt, dass Clients einen RFC-8707-resource-Parameter senden, der der eigenen URL des MCP-Servers entspricht, und Entra akzeptiert nur einen resource-Wert, der mit einer verifizierten Bezeichner-URI in der App-Registrierung übereinstimmt. Entra weigert sich rundweg, eine *.azurewebsites.net-URL als solche zu registrieren (nicht verifizierte Domäne) – live bestätigt als AADSTS9010010 und durch keine Portal-Einstellung behebbar. Stattdessen autorisiert Claude gegen diesen Server (dessen eigene URL die Ressourcenprüfung trivial erfüllt), und der Server leitet die echte Anmeldung im Hintergrund an Entra weiter (src/auth/mcpOAuthProvider.ts) und gibt Claude Entras echtes, unverändertes Zugriffstoken zurück. Nach dieser Übergabe funktioniert alles mit einem normalen, von Entra ausgestellten Bearer-Token genau wie sonst.

Jede Leseanfrage:

  1. Claude autorisiert über die eigenen /authorize- und /token-Endpunkte dieses Servers (src/auth/mcpOAuthProvider.ts), die die eigentliche Anmeldung über eine /oauth/callback-Route an Entra weiterleiten und Entras echtes Zugriffstoken zurückgeben (Zielgruppe = die Entra-App-Registrierung dieser App). Dieser Server validiert nur Token bei eingehenden Anfragen (src/auth/tokenVerifier.ts – Signatur über Entras JWKS, Aussteller, Zielgruppe, Ablauf) – er gibt selbst nie eines aus oder signiert eines.

  2. path wird normalisiert und gegen den konfigurierten Stammordner geprüft (src/files/pathScope.ts) vor jedem Azure-Aufruf – ein Pfad, der außerhalb des Stammordners aufgelöst wird, wird abgelehnt, unabhängig davon, was das Token sonst erlauben würde.

  3. Das eingehende Benutzertoken wird über den OAuth2-On-Behalf-Of-Flow (src/auth/obo.ts, OnBehalfOfCredential von @azure/identity) gegen ein neues Token mit dem Bereich https://storage.azure.com/.default ausgetauscht. Jeder Azure-Files-Aufruf erfolgt mit diesem Pro-Benutzer-Token (src/files/shareClient.ts) – niemals mit einem gemeinsamen Dienstprinzipal oder statischen Schlüssel.

  4. Die NTFS-Berechtigung der Datei/des Verzeichnisses wird abgerufen und gegen die vom Benutzer gehaltenen SIDs ausgewertet (src/graph/sidResolver.ts + src/acl/). Wenn der Zugriff nicht gewährt wird, gibt das Tool einen Fehler „Zugriff verweigert“ zurück und sonst nichts.

  5. Nur wenn der Zugriff gewährt wird, gibt das Tool die bereits in Schritt 3 abgerufene Verzeichnisliste oder den Dateiinhalt zurück – für PDF/Word/Excel zuerst in reinen Text umgewandelt (siehe unten).

  6. Jeder Aufruf – gewährt, verweigert oder mit Fehler – erzeugt eine strukturierte Audit-Log-Zeile (src/audit/log.ts), die den Benutzer, den angeforderten Pfad und das Ergebnis aufzeichnet. Siehe „Audit-Logging“ unten.

Die Auflösung der eigenen SID/Gruppen-SIDs des Benutzers verwendet einen App-only-Graph-Client-Credentials-Aufruf (src/graph/sidResolver.ts), nicht OBO. Das ist beabsichtigt: Es handelt sich um Identitätsmetadaten (zu welchen Gruppen gehört dieser Benutzer), nicht um Dateidaten, daher verletzt eine gemeinsame App-Identität dort nicht das Prinzip „niemals eine gemeinsame Anmeldeinformation zum Lesen von Dateidaten verwenden“ – die tatsächlichen Azure-Files-Lesevorgänge bleiben durchgehend strikt pro Benutzer.

Da resolveHeldSids bei jedem einzelnen list_directory/read_file-Aufruf ausgeführt wird, wird sein Ergebnis (die eigene SID des Benutzers plus alle transitiven Gruppen-SIDs) pro Benutzer für SID_CACHE_TTL_MS (Standard 5 Minuten, siehe .env.example) im Speicher zwischengespeichert – ein Cache-Treffer überspringt Microsoft Graph vollständig. Gruppenmitgliedschaften ändern sich selten genug, sodass dies die Latenz pro Anfrage und die Graph-Last erheblich reduziert, ohne das Fenster der Veraltung bei einer Berechtigungsänderung wesentlich zu vergrößern. Setzen Sie SID_CACHE_TTL_MS=0, um das Caching zu deaktivieren (z. B. während Sie eine Berechtigungsänderung debuggen, die nicht angezeigt wird).

Das OAuth-Relay in Schritt 1 verfolgt laufende Anmeldungen in zwei kurzlebigen, einmalig verwendbaren In-Memory-Maps (pendingAuthorizations, issuedCodes in mcpOAuthProvider.ts). Das ist für eine einzelne App-Service-Instanz in Ordnung, bedeutet aber, dass dieser Server nicht über eine Instanz hinaus skaliert werden darf, ohne diesen Zustand zuvor in einen gemeinsamen Speicher (z. B. Redis) zu verschieben – eine zweite Instanz würde Anmeldungen, die auf einer anderen Instanz begonnen haben als auf der, auf der sie abgeschlossen werden, zufällig fehlschlagen lassen.

PDF/Word/Excel-Dateien lesen

read_file konvertiert eine Handvoll gängiger binärer Dokumentformate serverseitig in reinen Text (src/files/textExtract.ts), da der MCP-Client, der die Ergebnisse dieses Connectors rendert, selbst keinen binären „resource“-Blob parsen kann, damit Claude darüber nachdenken kann – nur Textinhalte sind im Chat tatsächlich lesbar. Behandelt: .pdf, .docx, .xlsx. Nicht behandelt: ältere binäre .doc/.xls-Formate (Office-Formate vor 2007), die auf einen unlesbaren Blob zurückfallen, und gescannte/nur-Bild-PDFs, die eine klare Meldung „kein extrahierbarer Text“ zurückgeben, statt Müll (keine OCR). Die Extraktionsausgabe ist unabhängig vom Rohdateigrößenlimit (MAX_READ_FILE_BYTES) begrenzt, da die Textform eines dichten Tabellenblatts dessen Binärgröße überschreiten kann.

Audit-Logging

Der Lesezugriff wird vollständig im eigenen Code dieses Servers durchgesetzt (siehe oben), nicht durch Azure RBAC, daher gibt es keine Audit-Spur an anderer Stelle – das Audit-Log dieses Servers (src/audit/log.ts) ist sie. Jeder list_directory/read_file-Aufruf gibt unabhängig vom Ergebnis genau eine JSON-Zeile an stdout aus: Zeitstempel, Tool-Name, angeforderter Pfad, die oid und UPN des aufrufenden Benutzers, die Entscheidung (granted / denied / error), einen Grund für alles andere als granted und die Dauer des Aufrufs. Es wird als einfache console.log-JSON-Zeile geschrieben und nicht über eine Logging-Bibliothek, sodass es in jede Log-Pipeline fließt, in die das Bereitstellungsziel bereits stdout sammelt (z. B. Azure App Service Log Stream / Log Analytics), ohne zusätzliche Verkabelung.

Erforderliche Azure/Entra-Konfiguration (nicht durch dieses Repo automatisiert)

Vollständige Klick-für-Klick-Schritte finden Sie in SETUP.md. Zusammenfassung dessen, was tatsächlich benötigt wird:

  • RBAC: Weisen Sie Storage File Data Privileged Reader (schreibgeschützt; verwenden Sie nicht Contributor) der Entra-Gruppe zu, deren Mitglieder diesen Connector verwenden können sollen, mit Gültigkeitsbereich auf das Speicherkonto selbst. Dies ersetzt die Rolle „SMB Share Reader“ aus dem ursprünglichen Ticket – siehe Begründung oben. Dies ist ein grobes Tor „Kann diese Person überhaupt fragen“, nicht die eigentliche Berechtigungsprüfung – echte NTFS-Berechtigungen (im Code durchgesetzt, siehe oben) bestimmen weiterhin, was jeder Benutzer tatsächlich sieht.

  • App-Registrierung: Eine App-Registrierung erledigt drei Aufgaben – OAuth-Client für Claude, Identität für den On-Behalf-Of-Austausch mit Azure Storage und (normalerweise) die App-only-Identität für Graph. Sie benötigt:

    • API verfügbar machen: Anwendungs-ID-URI api://<client-id> (Standard), mit einem Bereich namens access_as_user.

    • Authentifizierung: genau eine Web-Umleitungs-URI, <PUBLIC_BASE_URL>/oauth/callback – der eigene Callback dieses Servers, nicht der von Claude. Claudes plattformweiter Callback (https://claude.ai/api/mcp/auth_callback) wird in Entra überhaupt nie registriert; siehe „Architektur“ oben für den Grund.

    • Ein Client-Geheimnis.

    • Dieser Server unterstützt keine dynamische Client-Registrierung – er erkennt immer nur einen Client (die eigene Client-ID/das eigene Geheimnis dieser App-Registrierung), was auch das ist, was Sie als OAuth-Client-ID/Geheimnis konfigurieren, wenn Sie dies als benutzerdefinierten Connector in Claude hinzufügen.

  • Graph-API-Berechtigungen (Anwendung, mit Administratorzustimmung) auf der App-Registrierung, auf die GRAPH_CLIENT_ID zeigt: User.Read.All und GroupMember.Read.All (oder das breitere Directory.Read.All) – erforderlich, um onPremisesSecurityIdentifier für Benutzer und ihre transitiven Gruppenmitgliedschaften zu lesen.

Konfiguration

Alle Einstellungen sind Umgebungsvariablen – siehe .env.example und die vollständige Referenztabelle in SETUP.md. Das Wichtige für die Wiederverwendung: ROOT_PATH (plus STORAGE_ACCOUNT_NAME/SHARE_NAME) ist das einzige, was geändert werden muss, um diesen Server später auf einen anderen Ordner, eine andere Freigabe oder einen anderen Client auszurichten. Es wird beim Prozessstart einmal gelesen und nie als Tool-Parameter akzeptiert, sodass ein Aufrufer den Gültigkeitsbereich zur Laufzeit nicht erweitern kann.

Um auf einen anderen Ordner/eine andere Freigabe umzuleiten:

  1. Aktualisieren Sie STORAGE_ACCOUNT_NAME, SHARE_NAME, ROOT_PATH in der App-Service-Konfiguration.

  2. Stellen Sie sicher, dass die Ziel-Entra-Gruppe Storage File Data Privileged Reader auf dem neuen Speicherkonto hat.

  3. Starten Sie die App neu. Keine Code- oder Build-Änderungen erforderlich.

Lokal ausführen

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

Build, Typprüfung, Test

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

Bereitstellen in Azure App Service

Siehe SETUP.md für die vollständige Anleitung. Kurzfassung:

  1. Packen Sie src/, package.json, package-lock.json und tsconfig.json in eine Zip-Datei - niemals ein vorgefertigtes dist/ oder node_modules/. Der Oryx-Builder von Azure kompiliert es bei jedem Deployment frisch, serverseitig (erfordert die App-Einstellung SCM_DO_BUILD_DURING_DEPLOYMENT=true).

  2. Stellen Sie die Zip-Datei in einem Linux-App-Service-Plan bereit, Node 20+, genau eine Instanz (siehe den Hinweis zum In-Memory-OAuth-Relay-Status in "Architecture" oben).

  3. Legen Sie alle Variablen aus .env.example als App-Service-Anwendungseinstellungen fest (nicht als eingecheckte .env-Datei).

  4. PUBLIC_BASE_URL muss die echte HTTPS-URL des App Service sein, ohne abschließenden Schrägstrich - ein solcher erzeugt doppelte Schrägstriche in generierten URLs und bricht den Entra-Redirect-URI-Abgleich.

  5. Verifizieren Sie vor dem Verbinden mit Claude: GET /healthz gibt ok zurück, und GET /.well-known/oauth-protected-resource/mcp gibt ein JSON-Metadaten-Dokument zurück (das /mcp-Suffix ist gemäß RFC 9728 erforderlich, da die Ressourcen-Server-URL selbst eine /mcp-Pfadkomponente hat).

  6. Der MCP-Endpunkt, mit dem Claude verbindet, ist POST {PUBLIC_BASE_URL}/mcp.

Dieser Server implementiert OAuth selbst (JWT-Validierung, die /.well-known/oauth-*-Metadaten-Endpunkte, und jetzt das vollständige Autorisierungs-Server-Relay – über den mcpAuthRouter des MCP-SDK) anstatt sich auf die integrierte "Easy Auth"-MCP-Integration des App Service zu verlassen. Diese Integration ist real, aber noch in der Vorschau, und Microsofts eigene Dokumentation warnt ausdrücklich davor, ihr validiertes Token an eine nachgelagerte Ressource weiterzuleiten – Sie müssten den On-Behalf-Of-Austausch für Storage ohnehin selbst schreiben, also hätte es die Menge an Code hier nicht reduziert, sondern nur Vorschau-Risiko hinzugefügt.

Bekannte Einschränkungen

  • Domänenlokale AD-Gruppen werden möglicherweise nicht aufgelöst. NTFS-ACLs werden mit lokalen AD-SIDs abgeglichen, die über Microsoft Graphs onPremisesSecurityIdentifier aufgelöst werden. Domänenlokale Gruppen werden nicht zuverlässig mit Entra ID synchronisiert/zurückgeschrieben, daher kann eine ACE, die Zugriff auf eine nicht synchronisierte domänenlokale Gruppe gewährt, nicht abgeglichen werden. Dies schlägt geschlossen fehl: Eine unaufgelöste Gruppen-SID kann niemals eine ALLOW-ACE erfüllen, sodass der schlimmste Fall ist, dass ein Benutzer weniger sieht, als ihm zusteht, niemals mehr (src/graph/sidResolver.ts, src/acl/evaluate.ts). Wenn die ACLs eines Zielordners domänenlokale Gruppen verwenden, verifizieren Sie während des Testens, dass dies keine echten Benutzer unterberechtigt; falls doch, besteht die Lösung entweder darin, die ACLs mit universellen/globalen Gruppen neu zu setzen oder einen LDAP-Fallback-Lookup hinzuzufügen (hier nicht implementiert).

  • Verzeichnisdurchlauf (FILE_TRAVERSE) auf übergeordneten Ordnern wird nicht separat geprüft. Windows gewährt "Bypass-Traverse-Checking" standardmäßig für authentifizierte Benutzer in den meisten realen Bereitstellungen, daher entspricht dies typischem realem Verhalten. Wenn die Umgebung eines Kunden jedoch nicht standardmäßige Traverse-Einschränkungen für Ordner zwischen dem Freigabestamm und ROOT_PATH aufweist, verifizieren Sie dies während des Testens erneut.

  • Nur eine einzelne App-Service-Instanz – siehe den OAuth-Relay-Hinweis in "Architecture" oben.

  • Kein Schreiben/Löschen/Umbenennen, absichtlich – keine Lücke, sondern eine bewusste Einschränkung.

  • Legacy-Binärdateien .doc/.xls und gescannte/nur-Bild-PDFs sind nicht lesbar – siehe "PDF/Word/Excel-Dateien lesen" oben.

  • Erfordert, dass das lokale AD des Zielmandanten mit Entra synchronisiert ist (Entra Connect / Cloud Sync) und lokale SIDs durchfließen – das gesamte benutzerbezogene NTFS-Durchsetzungsmodell hängt davon ab. Funktioniert nicht für einen Cloud-only, Entra-nativen Mandanten ohne lokales AD.

-
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