Skip to main content
Glama
H1er0

Azure Files MCP

by H1er0

Azure Files MCP (только чтение)

Удалённый MCP-сервер, который даёт Claude доступ только для чтения к одной папке на SMB-ресурсе Azure Files, где каждый подключающийся пользователь видит только то, что уже разрешают его собственные разрешения NTFS.

Два инструмента, и ничего больше:

  • list_directory(path) — список файлов/папок в настроенной корневой папке.

  • read_file(path) — чтение содержимого файла в настроенной корневой папке. Обычные текстовые файлы возвращаются как есть; PDF, Word (.docx) и Excel (.xlsx) автоматически преобразуются в текст (см. «Чтение PDF/Word/Excel файлов» ниже).

В этом коде нет ни одного инструмента для записи, удаления или переименования — не заглушек, не отключённых конфигурацией, просто отсутствуют.

Пошаговые инструкции по настройке Azure/Entra см. в SETUP.md. Этот файл описывает архитектуру и проектные решения; в SETUP.md — пошаговое прохождение по порталу.

Почему это не совсем то, что можно предположить при первом чтении задачи

Первоначальный дизайн предполагал: назначить непривилегированную роль RBAC Storage File Data SMB Share Reader, затем вызывать FileREST API Azure Files с собственным OAuth-токеном каждого пользователя, и Azure будет автоматически применять NTFS ACL для каждого пользователя.

Это не работает. Проверено напрямую по документации REST API Microsoft (Authorize with Microsoft Entra ID (REST API)): каждая операция чтения FileREST (List Directories and Files, Get File, Get File Properties, ...) требует как .../files/read, так и .../readFileBackupSemantics/action. readFileBackupSemantics/action — собственный термин Microsoft для режима, который явно пропускает оценку NTFS ACL — предоставляется только Storage File Data Privileged Reader/Contributor. Storage File Data SMB Share Reader вообще не появляется в таблице разрешений REST — он применяется только к настоящим подключениям по протоколу SMB (порт 445, Kerberos), которые Node HTTPS-бэкенд, вызывающий FileREST, использовать не может.

Таким образом, через REST роль либо привилегированная (обходит ACL), либо неактуальна. Невозможно заставить сам Azure применять NTFS ACL на основе каждого запроса REST/OAuth.

Что вместо этого делает этот сервер: использует Storage File Data Privileged Reader (по-прежнему только чтение, по-прежнему аутентифицирован для каждого пользователя — см. ниже) и сам применяет разрешения NTFS в коде, используя реальный дескриптор безопасности, который Azure Files предоставляет для каждого файла/папки:

  1. Получает разрешение NTFS файла/папки в виде строки SDDL (вызов REST getPermission).

  2. Разбирает DACL на отдельные ACE (src/acl/sddl.ts — написан вручную; не существует поддерживаемой Node/TS библиотеки для этого).

  3. Разрешает собственный локальный AD SID вызывающего пользователя и все SID групп, в которые он транзитивно входит, через Microsoft Graph (src/graph/sidResolver.ts).

  4. Оценивает DACL против этого набора SID, используя реальную семантику Windows AccessCheck — явный запрет побеждает явное разрешение, неуказанные биты по умолчанию запрещены (src/acl/evaluate.ts).

Это действительно применение прав для каждого пользователя — просто реализовано здесь, а не делегировано уровню RBAC Azure, потому что у Azure нет механизма, вызываемого через REST, который сделал бы это за вас. Реальная граница безопасности — этот код, а не RBAC Azure — имейте это в виду, рассуждая о любом из нижеследующего.

Архитектура

Этот сервер — собственный сервер авторизации OAuth 2.1 — Claude никогда не общается с Entra напрямую. Это осознанное проектное решение, а не очевидное, поэтому стоит объяснить, почему: спецификация MCP требует, чтобы клиенты отправляли параметр resource RFC 8707, равный URL самого MCP-сервера, а Entra принимает только значение resource, соответствующее проверенному URI идентификатора в регистрации приложения. Entra категорически отказывается регистрировать любой URL *.azurewebsites.net как таковой (непроверенный домен) — подтверждено вживую как AADSTS9010010, и это нельзя исправить никакими настройками портала. Поэтому вместо этого Claude авторизуется против этого сервера (чей собственный URL тривиально удовлетворяет проверке resource), а сервер ретранслирует реальный вход в Entra за кулисами (src/auth/mcpOAuthProvider.ts), возвращая Claude реальный, неизменённый токен доступа Entra. После этой передачи всё работает с обычным токеном-носителем, выданным Entra, точно так же, как и должно было бы.

Каждый запрос на чтение:

  1. Claude авторизуется через собственные конечные точки /authorize и /token этого сервера (src/auth/mcpOAuthProvider.ts), которые ретранслируют фактический вход в Entra через маршрут /oauth/callback и возвращают реальный токен доступа Entra (аудитория = регистрация приложения Entra этого приложения). Этот сервер только проверяет токены во входящих запросах (src/auth/tokenVerifier.ts — подпись через JWKS Entra, издатель, аудитория, срок действия) — сам он никогда не выпускает и не подписывает токены.

  2. path нормализуется и проверяется относительно настроенной корневой папки (src/files/pathScope.ts) до любого вызова Azure — путь, ведущий за пределы корня, отклоняется независимо от того, что в противном случае разрешил бы токен.

  3. Входящий пользовательский токен обменивается через поток on-behalf-of OAuth2 (src/auth/obo.ts, OnBehalfOfCredential из @azure/identity) на новый токен с областью https://storage.azure.com/.default. Каждый вызов Azure Files выполняется с этим токеном конкретного пользователя (src/files/shareClient.ts) — никогда с общим субъектом службы или статическим ключом.

  4. Разрешение NTFS файла/каталога извлекается и оценивается относительно SID, которыми владеет пользователь (src/graph/sidResolver.ts + src/acl/). Если доступ не предоставлен, инструмент возвращает ошибку отказа в доступе и ничего больше.

  5. Только если доступ предоставлен, инструмент возвращает список каталога или содержимое файла, уже полученные на шаге 3 — сначала преобразованные в обычный текст для PDF/Word/Excel (см. ниже).

  6. Каждый вызов — предоставлен, отклонён или с ошибкой — генерирует одну строку структурированного журнала аудита (src/audit/log.ts), записывающую пользователя, запрошенный путь и результат. См. «Журнал аудита» ниже.

Разрешение собственного SID/SID групп пользователя использует только приложение Graph client-credentials вызов (src/graph/sidResolver.ts), а не OBO. Это осознанно: это метаданные идентификации (в какие группы входит этот пользователь), а не данные файлов, поэтому общая идентичность приложения там не нарушает «никогда не использовать общие учётные данные для чтения данных файлов» — фактические чтения Azure Files остаются строго попользовательскими на протяжении всего процесса.

Поскольку resolveHeldSids выполняется при каждом вызове list_directory/read_file, его результат (собственный SID пользователя плюс все транзитивные SID групп) кэшируется в памяти для каждого пользователя на SID_CACHE_TTL_MS (по умолчанию 5 минут, см. .env.example) — попадание в кэш полностью пропускает Microsoft Graph. Изменения членства в группах происходят достаточно редко, чтобы это существенно сокращало задержку на запрос и нагрузку на Graph, не слишком расширяя окно устаревания при изменении разрешений. Установите SID_CACHE_TTL_MS=0, чтобы отключить кэширование (например, при отладке изменения разрешений, которое не проявляется).

Ретрансляция OAuth на шаге 1 отслеживает выполняющиеся входы в двух недолговечных одноразовых картах в памяти (pendingAuthorizations, issuedCodes в mcpOAuthProvider.ts). Это нормально для одного экземпляра App Service, но означает, что этот сервер нельзя масштабировать более чем на один экземпляр без переноса этого состояния в общее хранилище (например, Redis) — второй экземпляр будет случайным образом проваливать входы, начатые на другом экземпляре, чем тот, на котором они завершились.

Чтение PDF/Word/Excel файлов

read_file преобразует несколько распространённых бинарных форматов документов в обычный текст на стороне сервера (src/files/textExtract.ts), поскольку MCP-клиент, отображающий результаты этого коннектора, сам не может разобрать бинарный «ресурс» для анализа Claude — в чате читаемо только текстовое содержимое. Обрабатываются: .pdf, .docx, .xlsx. Не обрабатываются: устаревшие бинарные .doc/.xls (форматы Office до 2007 года), которые возвращают нечитаемый блоб, и сканированные/только изображения PDF, которые возвращают понятное сообщение «нет извлекаемого текста», а не мусор (без OCR). Выходные данные извлечения ограничены независимо от ограничения размера необработанного файла (MAX_READ_FILE_BYTES), поскольку текстовая форма плотной электронной таблицы может превышать её бинарный размер.

Журнал аудита

Доступ на чтение обеспечивается полностью в собственном коде этого сервера (см. выше), а не RBAC Azure, поэтому нигде больше нет журнала аудита — журнал аудита этого сервера (src/audit/log.ts) и есть он. Каждый вызов list_directory/read_file, независимо от результата, генерирует ровно одну строку JSON в stdout: временная метка, имя инструмента, запрошенный путь, oid и UPN вызывающего пользователя, решение (granted / denied / error), причина для всего, кроме granted, и сколько времени занял вызов. Он записывается как обычная строка JSON через console.log, а не через библиотеку логирования, чтобы он попадал в любой конвейер журналов, который целевое развёртывание уже собирает из stdout (например, поток журналов Azure App Service / Log Analytics) без дополнительной настройки.

Требуемая конфигурация Azure/Entra (не автоматизируется этим репозиторием)

Полные пошаговые инструкции — в SETUP.md. Краткое изложение того, что фактически нужно:

  • RBAC: назначьте Storage File Data Privileged Reader (только чтение; не используйте Contributor) группе Entra, члены которой должны иметь возможность использовать этот коннектор, с областью действия на саму учётную запись хранения. Это заменяет роль SMB Share Reader из исходной задачи — см. обоснование выше. Это грубый шлюз «может ли этот человек вообще спросить», а не настоящая проверка разрешений — реальные разрешения NTFS (применяемые в коде, см. выше) по-прежнему определяют, что фактически видит каждый пользователь.

  • Регистрация приложения: одна регистрация приложения выполняет три задачи — OAuth-клиент для Claude, идентичность для обмена On-Behalf-Of с Azure Storage и (обычно) идентичность только для приложения для Graph. Ей нужно:

    • Expose an API: URI идентификатора приложения api://<client-id> (по умолчанию) с областью с именем access_as_user.

    • Authentication: ровно один URI перенаправления Web, <PUBLIC_BASE_URL>/oauth/callback — собственный обратный вызов этого сервера, а не Claude. Общеплатформенный обратный вызов Claude (https://claude.ai/api/mcp/auth_callback) вообще не регистрируется в Entra; см. «Архитектура» выше, почему.

    • Секрет клиента.

    • Этот сервер не поддерживает Dynamic Client Registration — он распознаёт только один клиент (собственный идентификатор клиента/секрет этой регистрации приложения), который также настраивается как OAuth Client ID/Secret при добавлении этого коннектора в Claude.

  • Разрешения Graph API (приложение, с согласия администратора) на той регистрации приложения, на которую указывает GRAPH_CLIENT_ID: User.Read.All и GroupMember.Read.All (или более широкое Directory.Read.All) — необходимо для чтения onPremisesSecurityIdentifier для пользователей и их транзитивных членств в группах.

Конфигурация

Все настройки — переменные окружения — см. .env.example и полную справочную таблицу в SETUP.md. Важная для повторного использования: ROOT_PATH (плюс STORAGE_ACCOUNT_NAME/SHARE_NAME) — единственное, что нужно изменить, чтобы позже перенаправить этот сервер на другую папку, ресурс или клиента. Он читается один раз при запуске процесса и никогда не принимается как параметр инструмента, поэтому у вызывающей стороны нет способа расширить область действия во время выполнения.

Чтобы перенаправить на другую папку/ресурс:

  1. Обновите STORAGE_ACCOUNT_NAME, SHARE_NAME, ROOT_PATH в конфигурации App Service.

  2. Убедитесь, что целевая группа Entra имеет Storage File Data Privileged Reader на новой учётной записи хранения.

  3. Перезапустите приложение. Изменения кода или сборки не требуются.

Локальный запуск

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

Сборка, проверка типов, тесты

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

Развёртывание в Azure App Service

Полное прохождение см. в SETUP.md. Краткая версия:

  1. Упакуйте src/, package.json, package-lock.json и tsconfig.json в zip-архив — ни в коем случае не предварительно собранные dist/ или node_modules/. Сборщик Oryx от Azure компилирует их заново на стороне сервера при каждом развертывании (требуется параметр приложения SCM_DO_BUILD_DURING_DEPLOYMENT=true).

  2. Разверните zip-архив в плане App Service на Linux, Node 20+, ровно один экземпляр (см. примечание о состоянии ретрансляции OAuth в памяти в разделе «Архитектура» выше).

  3. Задайте все переменные из .env.example как параметры приложения App Service (а не как закоммиченный файл .env).

  4. PUBLIC_BASE_URL должен быть реальным HTTPS-URL App Service без завершающего слэша — иначе в генерируемых URL появятся двойные слэши, и это нарушит сопоставление redirect URI в Entra.

  5. Перед подключением Claude проверьте: GET /healthz возвращает ok, а GET /.well-known/oauth-protected-resource/mcp возвращает JSON-документ с метаданными (суффикс /mcp обязателен согласно RFC 9728, поскольку сам URL сервера ресурсов содержит компонент пути /mcp).

  6. Конечная точка MCP, к которой подключается Claude, — это POST {PUBLIC_BASE_URL}/mcp.

Этот сервер реализует OAuth вручную (валидация JWT, конечные точки метаданных /.well-known/oauth-* и теперь полная ретрансляция сервера авторизации — через mcpAuthRouter из MCP SDK), а не полагается на встроенную интеграцию MCP «Easy Auth» от App Service. Эта интеграция реальна, но всё еще находится в предварительной версии, и в документации Microsoft прямо предупреждают о недопустимости пересылки проверенного токена нижестоящему ресурсу — в любом случае пришлось бы самостоятельно писать обмен от имени пользователя (on-behalf-of) для Storage, так что это не сократило бы объем кода, а лишь добавило бы риски стадии предварительной версии.

Известные ограничения

  • Локальные доменные группы AD могут не разрешаться. Списки управления доступом NTFS сопоставляются с SID локального AD, которые разрешаются через onPremisesSecurityIdentifier из Microsoft Graph. Локальные доменные группы ненадежно синхронизируются/записываются обратно в Entra ID, поэтому ACE, предоставляющий доступ несинхронизированной локальной доменной группе, не может быть сопоставлен. Это завершается закрыто: неразрешенный SID группы никогда не может удовлетворить ACE типа ALLOW, поэтому худший случай — пользователь видит меньше, чем ему положено, но никогда больше (src/graph/sidResolver.ts, src/acl/evaluate.ts). Если в ACL целевой папки используются локальные доменные группы, проверьте во время тестирования, что это не занижает права реальных пользователей; если занижает, исправление — либо перенастройка ACL с универсальными/глобальными группами, либо добавление резервного поиска через LDAP (здесь не реализовано).

  • Обход каталога (FILE_TRAVERSE) для родительских папок отдельно не проверяется. Windows по умолчанию предоставляет «обход проверки обхода» (bypass traverse checking) аутентифицированным пользователям в большинстве реальных развертываний, поэтому это соответствует типичному реальному поведению, но если в окружении клиента на папках между корнем общего ресурса и ROOT_PATH действуют нестандартные ограничения обхода, перепроверьте это во время тестирования.

  • Только один экземпляр App Service — см. примечание о ретрансляции OAuth в разделе «Архитектура» выше.

  • Нет записи/удаления/переименования — это осознанное проектное решение, а не пробел.

  • Устаревшие бинарные файлы .doc/.xls и отсканированные PDF-файлы, содержащие только изображения, не читаются — см. раздел «Чтение PDF/Word/Excel» выше.

  • Требуется, чтобы локальный AD целевого клиента был синхронизирован с Entra (Entra Connect / Cloud Sync) с передачей локальных SID — на этом строится вся модель принудительного применения NTFS для каждого пользователя. Не будет работать для арендатора только в облаке, нативном для Entra, без локального 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