Skip to main content
Glama

office365-mcp

Многопользовательский удалённый MCP-сервер для Microsoft 365 — почта Outlook, Teams и SharePoint/OneDrive через Microsoft Graph. Каждый пользователь подключается со своей учётной записью Microsoft через обычный вход в браузере; сервер сохраняет эту авторизацию в зашифрованном виде и действует от имени этого пользователя при каждом последующем вызове инструмента, так что подключение, установленное один раз, продолжает работать без хранения токена Microsoft у клиента. Общие и служебные почтовые ящики (support@, billing@, info@) являются полноценной идентичностью, а не параметром, прикрученным к нескольким инструментам.

Одна кодовая база на TypeScript, три цели развёртывания:

Платформа

Точка входа

Сборка / развёртывание

AWS Lambda (Function URL)

src/entries/lambda.ts

npm run build:lambda && npm run deploy:lambda

Проверьте конфигурацию, не трогая AWS:

DRY_RUN=1 npm run deploy:lambda

| Azure Functions (v4 Node) | src/entries/azure.ts | npm run build:azure && func azure functionapp publish … | | Обычный Node (dev / self-host) | src/entries/node.ts | npm run dev |

Чем это отличается от других MCP-серверов для Microsoft 365

Поле open-source велико, и несколько проектов хороши. Этот построен вокруг другой модели развёртывания и идентичности, а не вокруг более широкого покрытия Graph.

  • Сервер выступает посредником для refresh-токенов; клиент никогда не видит токен Microsoft. Большинство существующих серверов хранят один токен для одного пользователя на одной машине — ~/.outlook-mcp-tokens.json, ~/.microsoft_mcp_token_cache.json, ~/.office-mcp-tokens.json — в открытом виде. Единственный зрелый удалённый сервер (Softeria's ms-365-mcp-server, в HTTP-режиме) прямо заявляет, что обновление токена — обязанность клиента, поэтому сессия умирает, когда access-токен Graph истекает примерно через час. Здесь per-user refresh-токен Entra запечатывается с помощью AES-256-GCM и хранится на стороне сервера, а сервер незаметно обновляет access-токены от имени пользователя.

  • Он не имеет состояния и имеет форму serverless. Никакой привязки к SSE-сессии, никакого долгоживущего процесса, всё состояние сессии и учётных данных — в DynamoDB. Альтернативы с поддержкой HTTP предполагают постоянно работающий контейнер (Express, Azure Container Apps, бэкенд App Service за локальным stdio-шимом).

  • Общие почтовые ящики смоделированы, включая путь app-only. Если у вызывающего есть права Exchange, сервер использует его собственный делегированный токен против /users/{mailbox}; если в почтовый ящик вообще никто не входит, он может использовать ограниченные учётные данные приложения. Ни один другой сервер с открытым исходным кодом не предлагает этот второй, управляемый путь, а ограничение на стороне Exchange поставляется здесь в виде скрипта (deploy/entra/scope-app-only.ps1), а не оставлено как упражнение.

  • Пользователь сам выбирает, какими почтовыми ящиками может пользоваться ассистент. В Entra нет согласия на уровне почтового ящика для делегированных Mail.*.Shared: предоставление этих областей даёт токен, который может открыть любой почтовый ящик, который Exchange позволяет открыть этому человеку, и Microsoft не предлагает способа это сузить. Поэтому после входа следует страница одобрения, которую обслуживает этот сервер, и то, что пользователь отмечает там, принудительно применяется на стороне сервера при каждом запросе. См. Одобрение почтовых ящиков.

  • Есть история управления. Списки разрешённых инструментов для каждого пользователя, которые по умолчанию запрещают новые инструменты, ограничения скорости для каждого пользователя, потолок почтовых ящиков администратора поверх собственного одобрения пользователя, блокировка необратимого удаления на уровне развёртывания и структурированная запись аудита для каждого вызова с указанием пользователя, инструмента, аргументов и фактически затронутого почтового ящика.

  • Поверхность инструментов намеренно мала. 27 инструментов в форме задач, а не 300 в форме конечных точек. Широта — не то, с чем этот проект конкурирует: Softeria покрывает диапазоны Excel и страницы OneNote, а собственные серверы Work IQ от Microsoft имеют семантический поиск и трассировку уровня Defender. Чего не предлагает ни один из них — это сервер, который вы размещаете сами, в своём регионе, без лицензии Microsoft 365 Copilot.

Существуют два собственных варианта, о которых стоит знать. MCP Server for Enterprise от Microsoft бесплатен, но только для чтения и ограничен данными каталога Entra — это дополнение, а не конкурент. Agent 365 / Work IQ действительно покрывает почту, календарь, Teams и SharePoint, но находится в предварительной версии, размещается только у Microsoft и требует лицензии Microsoft 365 Copilot.

Быстрый старт

1. Зарегистрируйте приложение Entra. Это шаг, который чаще всего идёт не так. Следуйте deploy/entra/SETUP.md — в частности, зарегистрируйте платформу как Web, а не SPA (redirect URI SPA незаметно ограничивает refresh-токены 24 часами, и этот срок наследуется каждым токеном, производным от них, что разрушило бы предпосылку «подключился один раз»).

2. Сгенерируйте два ключа. Это разные ключи с разными задачами, и ни один не может заменить другой.

npm install
npm run gen:oauth-key   # RS256 keypair — signs the tokens Claude presents to US
npm run gen:enc-key     # AES-256-GCM key — seals the tokens WE present to Microsoft

Сделайте резервную копию вывода gen:enc-key в менеджере секретов, отдельно от развёртывания. Его потеря делает все сохранённые подключения нерасшифровываемыми и вынуждает всех пользователей немедленно войти заново.

3. Разверните.

npm run build:lambda
npm run deploy:lambda        # wraps `sam deploy` against deploy/aws/template.yaml

Стек выводит EntraRedirectUri. Зарегистрируйте этот точный URI в регистрации приложения — это единственный шаг, который нельзя автоматизировать.

4. Подключите клиента. Добавьте https://<your-deployment>/mcp в качестве пользовательского коннектора. Клиент обнаруживает конечные точки OAuth из /.well-known/oauth-protected-resource, регистрируется и отправляет пользователя через вход Microsoft. За экраном согласия Microsoft следует собственная страница одобрения почтовых ящиков этого сервера, где пользователь отмечает, какими общими почтовыми ящиками может пользоваться ассистент; клиент получает свой токен только после этого. Затем вызовите o365_whoami — он сообщает состояние подключения, предоставленные разрешения, почтовые ящики, одобренные пользователем, и какие инструменты в данный момент доступны вызывающему.

Для локальной разработки поместите переменные из Конфигурации в файл .env — как минимум регистрацию Entra, два ключа и MCP_USERS_FILE + MCP_GRAPH_FILE + MCP_OAUTH_FILE для хранилищ на основе файлов, что позволяет npm run dev запускать реальный вход в браузере и процесс одобрения почтовых ящиков без AWS. .env.example — аннотированная версия. Затем:

npm run dev                              # http://localhost:3000/mcp

Добавьте http://localhost:3000/oauth/callback в качестве второго redirect URI в регистрации приложения.

Архитектура

  Claude / MCP client
        │  1. POST /mcp  (Bearer: our RS256 JWT)
        ▼
┌──────────────────────────────────────────────────────────┐
│  office365-mcp   (Lambda Function URL / Azure Fn / Node)  │
│                                                           │
│  Hono ── /mcp ── JSON-RPC 2.0 ── tool registry            │
│    │                                                       │
│    ├─ OAuth 2.1 authorization server (for the MCP client)  │
│    │    /.well-known/*  /oauth/register  /authorize        │
│    │    /callback  /consent  /token  /jwks.json            │
│    │                                                       │
│    └─ Graph token broker ── actor resolution ── client     │
└───────┬──────────────────────────┬────────────────────────┘
        │                          │
        │ 2. browser sign-in       │ 5. Bearer: Graph access token
        ▼                          ▼
  Microsoft Entra ID        Microsoft Graph
  login.microsoftonline     graph.microsoft.com/v1.0
        │
        │ 3. refresh token ──► AES-256-GCM ──► DynamoDB (MCP_GRAPH_TABLE)
        │                       (row-bound AAD)
        ▼
  4. the browser lands back here, on /oauth/consent — the user ticks
     which mailboxes the assistant may use, and only then is the
     authorization code handed to the MCP client

Транспорт. Streamable HTTP, режим без состояния. JSON-RPC 2.0 к POST /mcp, одно сообщение на запрос — пакетная обработка JSON-RPC отклоняется с -32600, потому что она была удалена из MCP в редакции 2025-06-18 и потому что массив вызовов попал бы под единый тариф ограничения скорости. Неаутентифицированный запрос получает 401 с вызовом RFC 9728 WWW-Authenticate: Bearer realm="mcp", resource_metadata=…, что заставляет клиент запустить поток OAuth коннектора. Документ защищённого ресурса обслуживается как по адресу /.well-known/oauth-protected-resource, так и по /.well-known/oauth-protected-resource/mcp и сообщает resource как {origin}/mcp — конечную точку, которую фактически ввёл пользователь и с которой клиент сравнивает.

Модель идентичности

Существуют два отдельных отношения OAuth, и их различие — вся суть дизайна:

  1. MCP-клиент ↔ этот сервер. Мы — сервер авторизации. Клиент регистрируется динамически (RFC 7591), выполняет поток authorization-code + PKCE против /oauth/authorize и /oauth/token и получает подписанный нами JWT RS256. Entra не поддерживает динамическую регистрацию клиентов и никогда не видит этот обмен.

  2. Этот сервер ↔ Entra. Мы — конфиденциальный клиент с одним статическим Web redirect URI. Во время входа пользователя мы запускаем собственную, независимую цепочку PKCE к Entra, обмениваем код на наш client secret или сертификат и получаем id_token, access-токен Graph и — поскольку мы запрашиваем offline_accessrefresh-токен.

Этот refresh-токен и есть продукт. Он запечатывается с помощью AES-256-GCM под дополнительными аутентифицированными данными, привязанными к строке ({tid}:{oid}:refresh), так что blob, извлечённый из записи одного пользователя, нельзя воспроизвести в записи другого, и записывается в таблицу, которая существует только для учётных данных. Каждый последующий вызов инструмента идёт: JWT → (tid, oid) → кэшированный access-токен или обмен refresh-токена → Graph. Ключ пользователя — неизменяемая пара (tid, oid) из id_token, никогда не email, preferred_username или upn — все они изменяемы и управляются администратором.

Между ними /oauth/callback делает паузу. После сохранения авторизации Microsoft он не передаёт MCP-клиенту свой authorization code; он перенаправляет браузер на /oauth/consent с одноразовым билетом, и пользователь выбирает, какими почтовыми ящиками этот ассистент может адресовать. Только когда они отправляют эту страницу, код создаётся и клиент перенаправляется домой. См. Одобрение почтовых ящиков.

Список разрешённых тенантов (O365_ALLOWED_TENANTS) повторно проверяется при каждом запросе, а не только при входе, поэтому удаление тенанта вступает в силу немедленно, а не при следующем входе.

Делегированный и app-only

Каждый вызов Graph сначала детерминированно определяет актора из конфигурации — модель никогда не может заставить сервер повысить свои права:

Актор

Адрес

Когда

Видит

delegated-self

/me

Нет аргумента mailbox, или это собственный адрес вызывающего

Ровно то, что видит вошедший пользователь

delegated-shared

/users/{upn}

Другой почтовый ящик, который пользователь одобрил при подключении, политика администратора разрешает, и у вызывающего есть права Exchange

То, что Exchange предоставил этому пользователю на этом почтовом ящике

app-only

/users/{upn}

Почтовый ящик находится в O365_APP_ONLY_MAILBOXES, app-only включён, и политика вызывающего имеет allowAppOnly

Всё, к чему Exchange RBAC ограничивает приложение

Каждый актор, кроме delegated-self, сначала проходит policyAllowsMailbox — собственное одобрение пользователя, затем потолок администратора — прежде чем Graph будет задан какой-либо вопрос. Делегированный — это по умолчанию и обычный путь: после этих двух ворот существующие разрешения тенанта остаются реальными воротами, и отклонённый вызов даёт честный 403, который сервер переводит в «попросите администратора предоставить Full Access на этом почтовом ящике». App-only существует только для почтовых ящиков, в которые никто не входит, по умолчанию выключен и полностью описан ниже. /me никогда не означает общий почтовый ящик — нет пути /me к нему — и app-only токен вообще не может использовать /me, потому что нет вошедшего пользователя.

Teams — только делегированный по дизайну, навсегда. См. Ограничения.

Инструменты, доступные модели

27 инструментов. W отмечает инструмент, который изменяет состояние тенанта; они отключены по умолчанию для только что подготовленного пользователя и дополнительно требуют allowWrites в его политике. Каждый инструмент Outlook принимает необязательный аргумент mailbox (UPN или SMTP-адрес), выбирающий почтовый ящик для действия; опустите его для своего собственного. Все возвращаемые идентификаторы — непрозрачные идентификаторы Microsoft Graph — передавайте их обратно дословно и никогда не конструируйте свои.

Outlook — чтение

Инструмент

Что делает

o365_mail_search

Полнотекстовый поиск по почтовому ящику с использованием синтаксиса поиска Outlook (from:, subject:, attachment:, hasAttachments:true, …). Всегда сортируется по дате, ограничен Microsoft до 1 000 результатов и не может сочетаться с фильтрами.

o365_mail_list

Список сообщений в папке со структурными фильтрами и сортировкой — только непрочитанные, отправитель, диапазон дат, порядок. Аналог o365_mail_search: точная фильтрация без поиска по ключевым словам.

o365_mail_get

Одно сообщение целиком, тело в виде обычного текста, опционально с интернет-заголовками сообщения и метаданными вложений. Длинные тела обрезаются с указанием исходной длины.

o365_mail_folders

Список почтовых папок — верхнего уровня или всего дерева — с количеством сообщений и непрочитанных. Используйте его для определения идентификатора папки перед перемещением сообщений.

o365_mail_attachments_list

Имена вложений, типы, размеры и inline-флаги для одного сообщения. Только метаданные — никогда содержимое файлов.

o365_mail_attachment_download

Скачивает вложение и возвращает кратковременный предварительно подписанный URL. Никогда не возвращает байты inline.

Outlook — написание

Инструмент

Что делает

W o365_mail_send

Отправляет сообщение немедленно, составленное inline или из черновика. Graph принимает к доставке и не возвращает идентификатор, поэтому отчёт — «принято», а не «доставлено».

W o365_mail_reply

Ответ, ответ всем или пересылка существующего сообщения одним шагом. Ваш текст помещается над цитируемым оригиналом.

W o365_mail_draft_create

Создаёт неотправленный черновик — с нуля или как ответ/пересылку, уже цитирующую оригинал. Возвращает идентификатор черновика.

W o365_mail_draft_update

Редактирует тему, тело или получателей неотправленного черновика. Работает только с черновиками.

W o365_mail_move

Перемещает или копирует сообщение в другую папку. Перемещение изменяет идентификатор сообщения — возвращается новый, а старый перестаёт работать.

W o365_mail_delete

Удаляет сообщения: trash (восстановимо, по умолчанию), soft или permanent — необратимо и дополнительно ограничено O365_ALLOW_PERMANENT_DELETE.

W o365_mail_flags

Отмечает прочитанным/непрочитанным, ставит флажок, категоризирует и задаёт важность до 20 сообщений за раз.

W o365_mail_folder_manage

Создаёт, переименовывает, перемещает или удаляет почтовую папку.

W o365_mail_attachment_add

Прикрепляет файл к черновику. До 3 МБ inline, до 150 МБ через сеанс фрагментированной загрузки.

Teams

Инструмент

Что делает

o365_teams_list

Ваши команды или каналы в одной команде. Способ сопоставить имя команды или канала с идентификаторами, которые нужны другим инструментам Teams.

o365_teams_chats_list

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

o365_teams_messages_list

Читает сообщения из канала или чата. Чаты поддерживают диапазон дат; каналы — нет, потому что API каналов Graph не принимает фильтр по дате.

W o365_teams_message_send

Публикует от вашего имени в канал, в ветку канала или в чат — в том числе людям по электронной почте, что находит или создаёт чат один на один. Не принимает аргумент user: Teams приписывает каждое сообщение вошедшему пользователю, поэтому такой аргумент предполагал бы невозможную выдачу себя за другого.

o365_teams_search

Поиск по ключевым словам во всех чатах и каналах, которые вам видны. Единственный способ поиска в Teams; API списков вообще не имеют поиска. Также не принимает аргумент user/search/query ограничен владельцем токена и не имеет параметра «поиск от имени».

SharePoint и OneDrive

Инструмент

Что делает

o365_files_search

Поиск файлов в SharePoint и OneDrive или в пределах одного сайта или библиотеки. Поддерживает термины KQL (filetype:, author:, path:).

o365_files_sites

Находит сайты или перечисляет библиотеки документов сайта и их идентификаторы дисков. Отправная точка для работы с SharePoint.

o365_files_list

Перечисляет папку в OneDrive или библиотеке документов — по drive + item id, по пути или корень вашего OneDrive.

o365_files_get

Сведения об одном файле или папке — в том числе из вставленного URL общего доступа, который он распознаёт. Опционально сообщает, у кого есть доступ.

o365_files_download

Скачивает файл как предварительно подписанный URL, опционально конвертируя в PDF на выходе. Никогда не inline.

W o365_files_share

Предоставляет доступ по ссылке или приглашая людей. Сообщает о фактически предоставленном доступе, поскольку политика общего доступа клиента может незаметно понизить запрошенный уровень.

Основное

Инструмент

Что делает

o365_whoami

Кто вы под учётной записью, активна ли связь с Microsoft и когда она обновлялась в последний раз, какие разрешения предоставлены, какие инструменты у вас есть, какие общие почтовые ящики вы одобрили и можете использовать прямо сейчас, и для каждого из них — будет ли вызов выполняться от вашего имени или от имени сервисной учётной записи. Вызывайте это первым при сбоях.

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

Всё настраивается только через переменные окружения. Авторитетный список — тип Env в src/config.ts.

Регистрация приложения Entra

Var

Required

Description

OAUTH_ENTRA_TENANT_ID

да

GUID клиента или проверенный домен. Каждый вызов направляется в этот конкретный клиент — /common вызывает промахи кэша токенов и лишнюю повторную аутентификацию, а для client credentials недопустим. common / organizations превращают развёртывание в мультитенантное.

OAUTH_ENTRA_CLIENT_ID

да

Идентификатор приложения (клиента). Регистрация должна использовать тип платформы Web.

OAUTH_ENTRA_CLIENT_SECRET

один из

Секрет клиента. Самый простой вариант, но Entra ограничивает его срок действия 24 месяцами.

OAUTH_ENTRA_CLIENT_CERT_PEM

один из

Закрытый ключ PEM в формате PKCS#8 (буквально или в base64) для аутентификации клиента по сертификату. Предпочтительно в производственной среде.

OAUTH_ENTRA_CLIENT_CERT_THUMBPRINT

с сертификатом

SHA-1 отпечаток в шестнадцатеричном виде, как показано на портале. Entra сопоставляет утверждение с сертификатом по отпечатку, поэтому требуются обе части.

O365_ALLOWED_TENANTS

Идентификаторы клиентов через запятую, принимаемые при работе в мультитенантном режиме. Проверяется при входе и повторно при каждом запросе, поэтому удаление клиента отсюда немедленно блокирует его существующие подключения, а не при следующем входе. Пустое значение с common означает, что подключиться может любой клиент, давший согласие; сервер громко предупреждает об этом при запуске.

Ключи

Var

Required

Description

OAUTH_SIGNING_KEY_PRIVATE

да

Закрытый ключ PKCS#8 PEM в base64 для подписи RS256 наших токенов доступа MCP. npm run gen:oauth-key.

OAUTH_SIGNING_KEY_PUBLIC

да

Открытый ключ SPKI PEM в base64. Публикуется по адресу /.well-known/jwks.json.

OAUTH_SIGNING_KEY_KID

Идентификатор ключа в JWKS и заголовках токенов. Меняйте вместе с ротацией пары ключей. По умолчанию primary.

O365_TOKEN_ENC_KEY

да

<kid>:<base64 32 байта> — запечатывает каждый сохранённый refresh-токен Entra и кэшированный токен доступа. npm run gen:enc-key. Его потеря навсегда ломает все сохранённые подключения.

O365_TOKEN_ENC_KEYS_PREVIOUS

Выведенные из эксплуатации ключи через запятую в том же формате, принимаются только для расшифровки. Именно это превращает ротацию в плавную операцию, а не в «день переключения».

Хранилище

Var

Required

Description

MCP_GRAPH_TABLE

да¹

Таблица DynamoDB для запечатанных refresh-токенов (без TTL) и кэшированных токенов доступа (с TTL). Намеренно отделена от таблицы OAuth, чтобы учётные данные имели собственную границу IAM и политику резервного копирования.

MCP_GRAPH_FILE

Запасной вариант на JSON-файле для самостоятельного хостинга и разработки. Игнорируется, если задан MCP_GRAPH_TABLE; неприменимо на Lambda.

MCP_OAUTH_TABLE

да¹

Таблица DynamoDB для собственного состояния OAuth — зарегистрированных клиентов, состояний входа, кодов авторизации, refresh-токенов. TTL по expiresAt.

MCP_OAUTH_FILE

Запасной вариант на JSON-файле для того же состояния, чтобы npm run dev мог запускать реальный браузерный процесс входа без AWS. Игнорируется, если задан MCP_OAUTH_TABLE; неприменимо на Lambda, где каждый контейнер видел бы разное состояние.

MCP_USERS_TABLE

Таблица DynamoDB для пользователей и политик, с GSI keyPrefix-index и oid-index.

MCP_USERS_FILE

JSON-хранилище пользователей для самостоятельного хостинга и разработки. Игнорируется, если задан MCP_USERS_TABLE.

MCP_SHARED_SECRET

Устаревший единый bearer-токен администратора, обходящий хранилище пользователей. Полезен для смоук-тестов. У него нет собственного подключения к Graph, поэтому инструменты Graph возвращают ошибку переподключения, если он не сопоставлен с пользователем, выполнившим вход.

¹ или соответствующий вариант _FILE (MCP_GRAPH_FILE / MCP_OAUTH_FILE) для локальной разработки.

Поведение Graph

Var

По умолчанию

Description

O365_SCOPE_PROFILE

work

work включает области общей почты, сайтов SharePoint и каналов Teams, для нескольких из которых требуется согласие администратора клиента. personal запрашивает только области, доступные для согласия пользователя.

O365_SCOPES

производное

Полная замена списка областей через пробел. Проверяется при загрузке: /.default нельзя смешивать с именованными областями (AADSTS70011), а offline_access обязателен.

O365_GRAPH_BASE

https://graph.microsoft.com/v1.0

Меняйте только для суверенных облаков, где некоторые используемые здесь возможности отсутствуют.

O365_IMMUTABLE_IDS

true

Отправляет Prefer: IdType="ImmutableId" в вызовах Outlook, чтобы идентификаторы переживали перемещение. Решите один раз при первом развёртывании и никогда не меняйте на работающем стеке.

O365_BODY_FORMAT

text

text запрашивает тела в виде обычного текста — именно это нужно серверу, ориентированному на модели; HTML-тела переполнены трекинговой разметкой.

O365_GRAPH_TIMEOUT_MS

30000

Таймаут на запрос, выдерживается значительно ниже 300-секундного таймаута инструмента клиента.

O365_MAX_CONCURRENCY_PER_MAILBOX

4

Ограничение одновременных запросов на (приложение, почтовый ящик). Exchange допускает ровно четыре; значение ограничивается этим числом, потому что его повышение лишь превращает пропускную способность в ошибки 429.

O365_USER_AGENT

NONISV|SelfHosted|office365-mcp/0.1.0

Microsoft понижает приоритет трафика без оформления. Сохраняйте документированную форму и вставляйте название своей компании в среднее поле.

O365_SEARCH_REGION

auto

География SharePoint (NAM, EUR, APC) для POST /search/query. Требуется для поиска только от имени приложения; задавайте явно для мультигеографических клиентов.

Ограничения и вывод

Var

По умолчанию

Описание

O365_APP_ONLY_ENABLED

false

Главный переключатель режима «только приложение». Пока он равен false, этот путь кода недостижим независимо от любого списка разрешений.

O365_APP_ONLY_MAILBOXES

пусто

Разделённые запятыми адреса почтовых ящиков, доступные с учётными данными приложения. Подстановочный знак отклоняется сразу.

O365_APP_ONLY_SITES

пусто

Разделённые запятыми идентификаторы сайтов или URL-адреса, доступные с учётными данными приложения; применяется к каждому вызову, адресованному сайту или диску, совершённому субъектом в режиме «только приложение». Пустой список означает, что режим «только приложение» вообще не достигает SharePoint, а вызов в режиме «только приложение», не указывающий сайт, отклоняется, а не разрешается. Сопоставление нечувствительно к регистру и является либо точным, либо префиксом, заканчивающимся на границе пути /, так что запись может покрывать сайт и всё, что под ним, без расширения доступа более короткой ссылкой. Сочетается с регистрацией Sites.Selected.

O365_ALLOW_PERMANENT_DELETE

false

Блокировка на уровне развёртывания для режима permanent инструмента o365_mail_delete, поверх политики на пользователя.

MCP_OUTPUT_FORMAT

toon

toon выдаёт компактный табличный вывод, который существенно сокращает расход токенов при списках; json выдаёт аккуратный JSON для программных потребителей.

MCP_ARTIFACT_BUCKET

S3-корзина для загрузок. Требуется каждым инструментом загрузки — запасного варианта с base64 нет по замыслу.

MCP_ARTIFACT_URL_TTL_SECONDS

3600

Время жизни предварительно подписанных URL-адресов. Любой, кто владеет таким URL, может получить файл без аутентификации, поэтому держите его коротким.

MCP_ARTIFACT_REGION

AWS_REGION

Переопределяет регион для корзины артефактов.

MCP_AUDIT_FILE

Приёмник JSONL для аудита и записей безопасности, в дополнение к stderr. Для самостоятельных развёртываний; на Lambda stderr уже попадает в CloudWatch.

MCP_AUDIT_READS

не задано

1 включает аудит инструментов чтения, а также записи. По умолчанию выключено, потому что чтение доминирует по объёму. Независимо от этой настройки, каждый изменяющий вызов, каждый сбой и каждый вызов, называющий чужой почтовый ящик или пользователя, всегда записывается — действие с чужим почтовым ящиком — это именно то, о чём спрашивает проверка соответствия.

PORT

3000

Порт прослушивания для обычной точки входа Node. Не используется на Lambda и Azure Functions.

Доступ к общему почтовому ящику

Это функция, вокруг которой построена архитектура, и сама возможность зависит от клиента, а не от этого сервера.

Чтобы Graph вообще это сделал, должны выполняться оба условия. Соединению нужны делегированные области Graph .Shared (Mail.Read.Shared, Mail.ReadWrite.Shared, Mail.Send.Shared — все присутствуют в профиле областей work), и Exchange Online должен предоставить вошедшему пользователю права на целевой почтовый ящик. Область лишь разблокирует возможность; Exchange — это фактический шлюз. Без предоставления в Exchange Graph возвращает 403 независимо от того, на что было дано согласие.

Администратор предоставляет одно или несколько из этих прав в центре администрирования Exchange (Получатели → Почтовые ящики → общий почтовый ящик → Делегирование):

Право

Что оно даёт

Эффект

Полный доступ

Чтение, перечисление, перемещение, удаление, черновики в почтовом ящике

Требуется для каждого инструмента чтения и записи в этом почтовом ящике. Также требуется, если отправка должна поместить копию в «Отправленные» общего почтового ящика.

Отправлять как

Отправка с общим почтовым ящиком в качестве отправителя

Получатель видит только общий почтовый ящик.

Отправлять от имени

Отправка от имени почтового ящика

Получатель видит «пользователь от имени общего почтового ящика». Пользователи могут предоставить это сами в Outlook; только администратор может предоставить «Отправлять как».

Вступление разрешений в силу может занять до часа после их предоставления. 403 сразу после предоставления — это обычно как раз это, и сервер так и сообщает в ошибке.

Поверх этих двух, этот сервер добавляет свои два: пользователь должен был одобрить почтовый ящик при подключении, и потолок allowedMailboxes администратора должен это допускать. Оба проверяются до того, как Graph что-либо спросят — см. Одобрение почтового ящика.

Затем просто передайте адрес: o365_mail_list({ mailbox: "support@contoso.com", unreadOnly: true }). Сервер определяет субъекта, вызывает /users/support@contoso.com/… и никогда /me — пути /me в общий почтовый ящик нет. Адрес также должен быть одобрен пользователем при подключении; см. Одобрение почтового ящика ниже.

Два ограничения, о которых стоит знать заранее. Не существует API Graph, который перечисляет почтовые ящики, на которые у пользователя есть права — именно поэтому страница одобрения проверяет адрес-кандидат, а не перечисляет их для вас, и почему вызывающий по-прежнему называет почтовый ящик в вызове инструмента. И вошедшему пользователю обычно нужен собственный лицензированный почтовый ящик, хотя сам общий почтовый ящик в лицензии не нуждается.

Дальнейшее сужение. policy.allowedMailboxes — это потолок администратора, поверх того, что одобрил сам пользователь; фактический доступ — это пересечение двух. По умолчанию он равен "*", что передаёт оставшееся решение Exchange, где ему и место. Установите его в явный список, чтобы ограничить пользователя ниже его прав Exchange, или в null, чтобы отклонять аргумент mailbox outright:

npm run user -- add alice --mailboxes=support@contoso.com,billing@contoso.com

Одобрение почтового ящика

Адреса вводятся вручную. Microsoft не предоставляет API, который перечисляет почтовые ящики, которые может открыть человек, а вывод этого из того, с кем они переписываются, давал список, который был в основном неверен — поэтому страница не угадывает. Что она делает, так это проверяет: каждый введённый адрес проверяется в Exchange до того, как его можно одобрить, и показывается как доступный или недоступный с указанием причины.

Собственный почтовый ящик вошедшего пользователя — обычная запись в этом списке, и его можно удалить. Ассистент, построенный вокруг общего почтового ящика поддержки, не должен читать личный входящий ящик оператора, поэтому это должно быть выразимо. Если его не указать, каждый инструмент Outlook откажет в вызове, который не называет другой одобренный почтовый ящик; Teams и SharePoint не затрагиваются, потому что ни один из них не проходит через шлюз почтового ящика.

Microsoft не может ограничить это разрешение — поэтому это делает сервер. В Entra нет согласия в разрезе отдельных почтовых ящиков для делегированных Mail.*.Shared. Как только пользователь предоставляет эти области действия (scopes), полученный токен может открыть каждый почтовый ящик, который Exchange позволит открыть этому человеку, и на стороне Microsoft нет способа это сузить — SharePoint в 2024 году получил делегированное Sites.Selected, а у Exchange нет ни аналога, ни записи в плане разработки. Страница одобрения ниже — поэтому не прихоть, а единственное, что ограничивает данное разрешение, и применяется оно на стороне сервера, при каждом запросе, до любого вызова Graph (policyAllowsMailbox в src/users.ts, вызываемый из resolveActor).

Как устроен процесс. /oauth/authorize → вход в Entra → /oauth/callback сохраняет запечатанный refresh-токен и, вместо того чтобы отдать MCP-клиенту его код авторизации, перенаправляет на /oauth/consent с одноразовым билетом (15 минут). Страница показывает собственный почтовый ящик пользователя — он всегда включён, его нельзя убрать — а также потенциальные общие ящики, каждый уже проверен: адрес, который нельзя открыть, показывается серым с причиной, а не принимается заранее и не отваливается потом. Пользователь отмечает, что этот ассистент может использовать, и только после этого выпускается код авторизации и клиент перенаправляется домой. При повторном подключении страница открывается снова с уже отмеченным выбором; так же пользователь позже убирает почтовый ящик.

Чего проверка увидеть не может. У ошибки 403 на «Входящих» есть три причины, и лишь одна из них — «доступа нет вовсе». Пользователь, у которого есть только разрешение Send As (только отправка от имени) или доступ к одной папке, а не к всего ящику, не проходит проверку «Входящих», хотя этого более узкого доступа достаточно для нужной ему операции. Страница говорит об этом там, где отображается почтовый ящик. Честный итог: проверка выполняет скорее занижение, чем завышение — она никогда не даёт ложных срабатываний: адрес, ответивший на проверку 200, действительно открывается сервером.

Два ограничивающих уровня, оба на стороне сервера. grantedMailboxes — что одобрил пользователь; allowedMailboxesпотолок, заданный администратором. Почтовый ящик достижим только тогда, когда он присутствует в обоих списках, и o365_whoami возвращает это пересечение как usableSharedMailboxes, чтобы модель видела только те ящики, которые она действительно может использовать. Собственный почтовый ящик пользователя всегда разрешён и не фигурирует ни в одном из этих списков.

Сервисный аккаунт с API-ключом никогда не увидит этой страницы — у него нет ни браузера, ни человека, у которого можно спросить, — поэтому ограничение согласия на него не распространяется. Это осознанный выбор: стороной, дающей согласие, выступает администратор, создавший ключ, а allowedMailboxes остаётся единственным текущим органом. Граница проводится по тому, есть ли у учётной записи Entra oid, то есть проходила ли она через вход в браузере.

Режим только для приложения

Режим только для приложения (app-only) существует для одного случая: почтовый ящик, в который никто не входит, на который никто не имеет делегированных прав, но который агент всё равно должен разбирать. Используются собственные учётные данные приложения, а не пользователя, поэтому нет вошедшего пользователя, а /me недействительн.

По умолчанию этот режим выключon and должен оставаться выключенным, если в нём реальной необходимости: разрешение приложения Mail.ReadWrite с согласием администратора даёт доступ к каждому почтовому ящику в организации. Для включения нужно три независимых вещи: O365_APP_ONLY_ENABLED=true, почтовый ящик в списке O365_APP_ONLY_MAILBOXES и allowAppOnly в политике вызывающего пользователя. Ящик, который есть в белом списке, но у вызывающего нет allowAppOnly, просто откатывается на делегированный сценарий и позволяет отвечать Exchange. Переход на app-only не обходит два ограничителя почтовых ящиков: resolveActor применяет их до того, как вообще переходит к ветке app-only, поэтому вошедший пользователь всё равно должен одобрить адрес на странице одобрения. Типичный вызывающий за app-only — сервисный аккаунт с API-ключом, его никогда не спрашивают, и для него allowedMailboxes и является полным контролем.

O365_APP_ONLY_MAILBOXES — это только половина контроля, и менее сильная. Он ограничивает то, что этот код попросит у Graph. Сам кредыал? this value он программно не меняет: любой, кто получит доступ к учётным данным, открывает reaches всех ящиков в клиенте. Настоящий контроль — это Exchange RBAC for Applications на стороне клиента. deploy/entra/scope-app-only.ps1 автоматизирует этот процесс: регистрирует простой service principal in Exchange, creates an operational scope decreases group for security, assigns a role Application Mail.Checkout, соз sq restricted, and verifies everything finishing with Test-ServicePrincipalAuthorization.

Ловушка, которая ломает всё: RBAC-гранты действуют аддитивно с грантами Entra. Если на регистрации приложения осталось соглас соответствует неприобластное разрешение для приложения, то действует объединение обоих, и ограничение обесценивается. Разнение приложения в Entra нужно обязательно удалить. Так учтите право на кэш: изменения вступают в силу через 30 минут — 2 hours (Test-ServicePrincipalAuthorization обходит кэш — именно поэтому скрипт заканчивается им).

Teams nowode has no app-only case at all — see below.

Разрешения на инструменты для каждого пользователя

Когда настроено хранилище пользователей (MCP_USERS_TABLE or MCP_USERS_FILE for development), в каждой записи пользователя есть поле policy:

Field

Meaning

allowedTools

* даёт всё, including tools, which appear in the future — full access for admins andsecretaries. Array is a fixed list.

toolPermissions

Map of {name: boolean} per tool: default = denied — permission to call appears only when the value is true. Overrides the array allowedTools; "*" remains accessible.

allowWrites

Large amount to be for every tool in the mutating category.

grantedMailboxes

Which user on approval page at the moment connected to proxy properties, absent — didn't ask; own mailbox only. Written by /oauth/consent, not by an admin CLI.

allowedMailboxes

The administrator's maximum line: "*" (default), an explicit list of addresses or null for own mailbox only. Effective access is the intersection of grantedMailboxes. For an API key identity that never sees it on the page, it is exhaustive.

allowAppOnly

Per-user gate for the app-only subject. Default false: so a negligent or compromised user cannot secretly gain rights above Exchange.

rateLimitPerMin

Per-user call limit. Default 60.

disabled

Disables identity without limits. It doesn't delete the record.

Tools the user doesn't have are also hidden from any tools/list, so the model can't see them. On every OAuth login, map is aligned with the actual registry: newly included tools appear as false — potentially damaging, so you won't be allowed silently; removed tools are cut out. Storage is written only when there is some change.

A newly provisioned user has all read-only tools enabled and all modifying disarmed.

Admin CLIs

npm run user -- list
npm run user -- add alice --writes --mailboxes=support@contoso.com --app-only
npm run user -- tools alice                      # the effective 27-tool map
npm run user -- tools alice --enable=o365_mail_send
npm run user -- rotate alice                     # new API key, old one dead
npm run user -- disable alice
npm run connection -- list                       # who is connected, scopes, last refresh — never token material
npm run connection -- test alice@contoso.com     # one live Graph call, proves the stored credential still redeems
npm run connection -- revoke alice@contoso.com   # server-side kill switch: delete the row, purge cached tokens
npm run connection -- rewrap                     # re-seal every stored secret under the current encryption key

connection revoke means this server no longer uses the credential. The standard tenant-side switch is Revocation button "Sessions" in the user's object in Entra — remember: changing the password alone doesn't not reset the refresh token of a confidential client (matrix revocation see in SECURITY.md).

Constraints and known issues

  • На AWS header WWW-Authenticate is renamed. The Lambda Function URL rewrites it to x-amzn-Remapped-WWW-Authenticate — nothing in a function can prevent it. Discovery doesn't suffer: the MCP spec requires clients to load directly /.well-known/oauth-protected-resource/mcp (and then root) as fallback anyway, and reference SD KON on 401 does it silently — that's why the server returns both and resource is byte-identical to its MCP endpoint. If you encounter a client that actually needs a header, place CloudFront and add a Lambda@Edge function at level origin-response that copies the remapped name back; level viewer-response won't work, because CloudFront doesn't cause those when origin reserves to 400 or more.

State directly: otherwise most of this will be mistaken for bugs.

Refresh tokens expire in opaque ways. The 90-day window is inactive, not a fixed term: a user who interacts weekly not in practice, while one who has been silent for 91 days returns without a lifespan (AADSTS70008/700082). Independent of this, a Conditional Access frequency sign-in forces reauthentication on its own schedule — any server code can't cancel it. Password reset through the Entra/ Microsoft 365 admin center returns: admin info, not is diff; but a change by the user does not. Every one of these returns as one explicit error with reconnection link.

Secret expiry of the application client is a cliff, not a scale. Entra caps the lifetime of a secret to 24 months and if it's expiry, then now everyone in the deployment will fail with with AADSTS7000222 — not gradual, not one at. Certificate subject can save from this degradation; try to renew key long prior.

Loss cryptographic data unrecoverable. Without a key, no stored connections; each user must sign in again, simultaneously. Backup is separate rotation via O365_TOKEN_ENC_KEYS_PREVIOUS + connection rewrap, the surface cannot replace.

Teams sending is possible only in delegated mode, and it's permanently so. All Graph send endpoints list Teamwork.Migrate.All as the only application scope, and Microsoft notes this for migration scenarios. There's a compliant way to post a service account banner for missing one in this orientation: only in a Bot Framework bot or a team app with resource-specific consent (install for each), and none of those suit an HTML-based standalone MCP server. Unattended texting in Teams is not supposed. Teams payments aren't even included in use-case: model A / model B are stopped from August 25, 2025, despite what most existing RFC says.

(Note: I had "deprecated" examples; let me not mention that.)

As a final form, I need to produce translation with these technical terms. Вот итоговый ответ.

Scopes для каналов Teams требуют согласия администратора тенанта. ChannelMessage.Read.All, в частности, нельзя согласовать самостоятельно. Тот, кто поднял сервер у себя (self-hoster) без прав администратора и запускает O365_SCOPE_PROFILE=personal, получает рабочую почту, файлы и чат; инструменты каналов при этом дают сбой, но с объяснением, а не с загадочным 403. Профиль personal также не включает User.ReadBasic.All, поэтому @-упоминание человека, которого нет в беседе, не может быть сопоставлено с пользователем: сообщение всё равно отправляется, имя остаётся в тексте письма как обычный текст, а инструмент возвращает предупреждение, что адресат не был уведомлён.

Страница подтверждения почтовых ящиков занижает список, но никогда не завышает. Она определяет, можно ли использовать ящик, пытаясь открыть его папку «Входящие», и у 403 здесь три причины. Если у вас только право Send As (отправка от имени) или доступ к одной папке, а не ко всему ящику, адрес показывается как недоступный и отметить его нельзя, хотя этого более узкого доступа было бы достаточно для вашей задачи. Обратная ошибка невозможна: адрес, который можно отметить, сервер действительно может открыть.

У поиска есть жёсткие потолки, которые выглядят как потеря данных. $search в Outlook возвращает не более 1 000 результатов и не сочетается с фильтрами или собственной сортировкой. Поиск Teams возвращает количество страниц, а не общий итог, поэтому его никогда нельзя выдать за количество совпадений. Глубокая страничная выдача SharePoint обрывается после 1 000-го результата, а поиск в режиме только приложения по умолчанию исключает личный контент OneDrive — включение такой возможности разворачивает новый индекс, который может строиться от пары дней до недели, и всё это время результаты молча неполны, без какой-либо ошибки.

Политика общего доступа тенанта молча переписывает результат o365_files_share. Настройки на уровне организации и отдельного сайта могут понизить анонимную ссылку до уровня «только для организации», принудительно задать срок действия или сделать ссылки только на просмотр. Хуже того, createLink идемпотентен для каждой пары (приложение, тип ссылки): запрос свежей семидневной ссылки может вернуть выданную годы назад бессрочную ссылку с другим уровнем доступа. Инструмент всегда считывает результат обратно и возвращает фактически выданный доступ — это единственная защита.

Идентификаторы сообщений меняются при перемещении сообщений, а в Teams они не глобально уникальны. O365_IMMUTABLE_IDS включён по умолчанию именно ради этого, но это по сути односторонняя дверь: идентификаторы, выданные в одном формате, не работают в другом, а переключение флага на живом стенде вызывает ErrorInvalidIdMalformed. Отдельно отметим: идентификатор сообщения Teams уникален только в пределах своего чата или канала, поэтому идентификаторы сообщений всегда возвращаются вместе с координатами беседы.

Троттлинг — самый частый повседневный сбой. Outlook допускает четыре одновременных запроса на каждую пару (приложение, почтовый ящик) и 10 000 запросов за десять минут; Teams — примерно один запрос в секунду на канал, на чат и на пользователя; SharePoint списывает пять единиц ресурсов за каждый вызов для работы с разрешениями и троттлит поиск заметно жёстче, чем остальной Graph. Батчинг не помогает: Graph передаёт в Outlook одновременно не более четырёх подзапросов из пачки. Сервер сам ограничивает собственный веер запросов (fan-out) и в точности соблюдает Retry-After, но настойчивый агент всё равно рано или поздно встретит 429.

Вложения крупнее 3 МБ не работают в общем почтовом ящике. Microsoft документирует: делегированный вызывающий получает 403 при вложении большого файла в сообщение в общем или делегируемом ящике. До 3 МБ всё работает. Инструмент сам объясняет это, а не показывает голый 403.

В суверенных облаках функции незаметно отсутствуют. Безвозвратное удаление, дельта-запросы чатов и экспортные API Teams недоступны в округах US Government L4/L5 и China 21Vianet; к росс-гео достпупа не работает по причинам на права приложений. Мультигео-тенантам нужен отдельный поисковый запрос по каждому региону, иначе контент в других регионах молча пропадает из результатов.

Гонки при ротации refresh-токенов безопасны, но реальны. Entra при каждом обмене выдаёт новый refresh-токен и не отзывает старый, поэтому оба параллельных вызова для одного пользователя получают по валидному новому токену. Условная запись (conditional-write) leads том, что один вызов выигрывает, а проигравший отбрасывает свою копию; потерянная гонка никогда не пагубается вызову инструмента. Именно кэш access-токенов делает такие гонки редкими.

Дорожная карта

Это намеренно урезаная функциональность v1, а не упущения:

  • Календарь. Второе по популярности после почты. Для Calendars.ReadWrite возможно согласие пользователя, акторская модель, уже построенная для общих почтовых ящиков, без изменений переносится на общие календари. Следующий пункт в очереди.

  • Загрузка файлов в OneDrive и SharePoint. В v1 уже покрыты поиск, список, чтение, скачивание и раздача доступов.

  • Контакты / поиск людей, чтобы находить адрес по имени. Сейчас каждый инструмент передачи считает, что у вызывающего адрес уже есть.

  • Правила входящих (messageRules) — нужен MailboxSettings.ReadWrite — для серверной автоматизации сортировки поступающих.

Также рассматриваются: подключаемый приёмник аудита (Firehose → S3 → Athena) помимо stderr; федерация identity рабочей нагрузки (workload identity federation) как третья разновидность клиентских учётных данных в AWS, чтобы больше не существовало долгоживущих секретов, и непрозрачные страница-дескрипторы, выпускаемые сервером, вместо сырых строк @odata.nextLink.

Администрирование каталога сознательно оставлено за кадром: бесплатный Microsoft MCP Server for Enterprise уже закрывает запросы только для чтения к Entra.

Участие в разработке

Смотрите CONTRIBUTING.md. Проблемы безопасности: SECURITY.md — пожалуйста, не открывайте публичный issue.

Лицензия

MIT. Смотрите LICENSE.

-
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

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/LotzerDigital/aws-office365mcp'

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