Microsoft 365 MCP

Eigener MCP-Server unter mcp-m365 (~/source/mcps/mcp-m365). Zugriff auf SharePoint-Sites, Drive-Files, Excel-Workbooks (lesen/schreiben), Lists und Outlook-Mail via Microsoft Graph API.

Kernentscheidung: Service-Principal (App-Only) statt Delegated Auth — der Agent laeuft headless beim Kunden, kein User-Login noetig. Trade-off: Tenant-Admin muss einmalig App-Registration, Permissions und (je nach Modul) Sites.Selected-Grants bzw. ApplicationAccessPolicy anlegen.

Vergleich mit existierenden MCPs (Softeria ms-365-mcp-server, Microsoft Work IQ, Arcade) ergab: alle nur Delegated → Eigenbau noetig.

Tools (24)

Sites & Files list_sites, get_site, list_drive_items, search_files, get_drive_item, download_file

Excel Workbook read_excel_workbook_metadata, read_excel_range, read_excel_used_range, read_excel_table, write_excel_range, add_excel_worksheet

Lists list_lists, query_list_items

Mail (Outlook, neu in v0.2.0) list_mail_folders, list_messages, get_message, search_messages, send_mail, reply_message, create_draft, send_draft, list_attachments, download_attachment

user_id ist immer der UPN (christoph@vibe-factory.de) oder Object-ID der Mailbox — Application-Auth kennt kein „me”.

Setup beim Kunden — Entra App Registration

  1. Entra Admin Center → App registrations → New registration

  2. Certificates & secrets → New client secret → Value direkt kopieren (nur einmal angezeigt)

  3. API permissions → Microsoft Graph → Application permissions:

    ModulMinimal-PermissionHinweis
    SharePoint / Files / Excel / ListsSites.Selectedleast privilege, pro Site explizit whitelisten
    Files site-uebergreifendFiles.ReadWrite.Alltenant-weit
    Mail lesen / Drafts / AttachmentsMail.ReadWritetenant-weit — Einschraenkung via ApplicationAccessPolicy
    Mail sendenMail.Sendtenant-weit — gleiches Muster
  4. Grant admin consent klicken — braucht Tenant-Admin

Sites.Selected pro Ziel-Site freischalten

curl -X POST -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
  "https://graph.microsoft.com/v1.0/sites/<site-id>/permissions" \
  -d '{
    "roles": ["write"],
    "grantedToIdentities": [{"application": {"id": "<client-id>", "displayName": "mcp-m365"}}]
  }'

ApplicationAccessPolicy fuer Mail (empfohlen)

Mail.* ist per Default tenant-weit. Einschraenkung ueber Exchange Online PowerShell:

Connect-ExchangeOnline
New-DistributionGroup -Name "mcp-m365-allowed" -Type Security -Members @(
    "christoph@vibe-factory.de", "agent@vibe-factory.de"
)
New-ApplicationAccessPolicy `
    -AppId "<client-id>" `
    -PolicyScopeGroupId "mcp-m365-allowed@vibe-factory.de" `
    -AccessRight RestrictAccess `
    -Description "mcp-m365 darf nur diese Mailboxes lesen/senden"
Test-ApplicationAccessPolicy -Identity "ceo@vibe-factory.de" -AppId "<client-id>"
# erwartet: Denied

Policy greift innerhalb ~1h. Beim Kunden IMMER setzen bevor Mail.* live geht.

Install + Start

uv tool install --force --editable ~/source/mcps/mcp-m365
cp ~/source/mcps/mcp-m365/.env.local.example ~/source/mcps/mcp-m365/.env.local
# .env.local befuellen: TENANT_ID, CLIENT_ID, CLIENT_SECRET
~/source/mcps/mcp-m365/start.sh
# HTTP-Server auf http://127.0.0.1:8765/mcp

In ~/.claude.json registrieren:

"m365": { "type": "http", "url": "http://127.0.0.1:8765/mcp" }

Gotchas

  • user_id ist Pflicht bei allen Mail-Tools — Application-Auth kennt kein me.
  • $search + $filter nicht mischbar. Modul bevorzugt search wenn gesetzt.
  • Mail.Send ist tenant-weit ohne ApplicationAccessPolicy — DSGVO-Risiko fuer Kunden.
  • 403-Meldungen werden umgeschrieben mit Hinweis welche Permission vermutlich fehlt — macht Kunden-Debugging leichter.
  • Attachments > 3 MB brauchen Upload-Session (Graph-Limit). download_attachment nutzt contentBytes direkt — nur fuer kleine Anhaenge.
  • Pagination via @odata.nextLink — als next_link an Folge-Call uebergeben.