M365 Graph MCP Server

M365 Graph MCP Server

Model Context Protocol server wrapping Microsoft Graph API for OneDrive, SharePoint, and Calendar with read and write operations, plus Teams meeting transcript support.

Category
Visit Server

README

M365 Graph MCP Server

@juvantlabs/m365-graph-mcp-server — Model Context Protocol server wrapping the Microsoft Graph API for OneDrive, SharePoint, and Calendar (read + write). Designed to be consumed by Juvant OS agents (or any MCP-aware client) via npx.

Fulfills the m365-graph role per docs/adr/0002-mcp-abstract-roles.md in the handbook (concrete-only — Microsoft Graph is the single provider). The scope of this server (files + calendar; no mail send, no Teams chat) follows the threat-model boundary rule in docs/adr/0003-mcp-server-scope-boundaries.md: mail and Teams chat have materially different blast radius and would ship as separate <vendor>-<capability>-mcp-server packages if a real Juvant OS need surfaces; outbound Teams notifications go through webhooks (Adaptive Cards), not MCP. Per-company instance config binds this server in .juvant/config.json.

What's new in v0.2.0

Teams meeting transcript support. Two new read-only tools:

  • m365-graph:list_meeting_transcripts — given a calendar event ID, lists available post-meeting transcripts. Returns transcript IDs to pass to get_transcript.
  • m365-graph:get_transcript — fetches the transcript content, strips VTT timing markers, and returns clean readable text (capped at 30 000 chars).

Two new delegated scopes required (admin consent — see § Tools): OnlineMeetings.Read and OnlineMeetingTranscript.Read.All. Re-run npm run setup after upgrading to acquire them.

See CHANGELOG.md for the full change list.

Status

Published. v0.2.0 on npm (@juvantlabs/m365-graph-mcp-server). 19 tools across files (OneDrive + SharePoint), Outlook Calendar, and Teams meeting transcripts. Published via npm Trusted Publishing (OIDC-based auth from GitHub Actions; no static NPM_TOKEN) with provenance attestation; manual approval gate on the production GitHub Environment guards the publish step.

Originally generated by juvantlabs/juvant-tools scaffold mcp-server on 2026-05-03, conforming to the mcp-server.md spec. See CHANGELOG.md for the per-version history.

Install + run

# One-time OAuth (opens browser, persists tokens in OS keychain):
npx @juvantlabs/m365-graph-mcp-server setup

# Run the MCP server on stdio (default subcommand):
npx @juvantlabs/m365-graph-mcp-server

Requires Node ≥ 20. Both invocations expect the env vars below (typically loaded from .env.local via --env-file, or set by your MCP client when it spawns the server).

Environment variables

Required:

Variable Purpose
M365_CLIENT_ID Microsoft Entra application (client) ID for the registered app.
M365_CLIENT_SECRET Client secret for the registered app. Stored only in the consumer's environment; never in .juvant/config.json.
M365_TENANT_ID Microsoft Entra tenant ID (UUID). The canonical adoption pattern is single-tenant — see ARCHITECTURE.md § Tenancy model. The regex also accepts common / organizations / consumers for technical compatibility with Microsoft authority strings, but multi-tenant operation is not the supported deployment shape.

Optional:

Variable Purpose
MCP_SERVER_LOG_LEVEL Log level for diagnostics on stderr (default info).
M365_DOWNLOAD_DIR Override the per-tenant sandbox directory used by download_file. Default: $XDG_CACHE_HOME/m365-graph-mcp-server/<tenant-id> or ~/.cache/m365-graph-mcp-server/<tenant-id>.

CI enforces that every variable documented in this section is actually read from process.env.<NAME> somewhere in src/ — placeholder names containing <> are skipped. Documenting an env var without wiring it up will fail the build (handbook anti-pattern S2).

OAuth scope minimization is per-tool; see the tool catalog and ARCHITECTURE.md for the per-tool scope justifications.

Binding

The Juvant OS adopter binds this server in .juvant/config.json:

{
  "m365-graph": {
    "provider": "microsoft",
    "mcp_server": "npx @juvantlabs/m365-graph-mcp-server@0.1.3",
    "scope": "rw"
  }
}

Pinning the version in mcp_server keeps installs reproducible. The canonical inventory entry, with the same pin, is at juvant-os/docs/MCP_INVENTORY.md — refer to it for the matrix-side bindings (which agents have m365-graph:rw granted by default in the v0 seed) and the wizard Step 8.5 cross-check semantics.

Tools

Tool Purpose Input Output Required scope
m365-graph:list_drives Lists the drives the user has access to (primary OneDrive + shared document libraries). (none) { primary, accessible: [] } with id / driveType / name / webUrl / owner. Files.Read
m365-graph:list_items Lists immediate children (files + folders) of a folder. Defaults to the drive root. drive_id?, item_id?, limit? (1–100, default 50) { count, items: [] } with id / name / type / size / child_count / lastModified / webUrl. Files.Read
m365-graph:search_files Searches files by name and content within a drive. query (required), drive_id?, limit? (1–50, default 20) { count, results: [] } with id / name / path / size / is_folder / lastModified / webUrl. Files.Read
m365-graph:download_file Downloads a file to a per-tenant local sandbox. Returns the local path; agent reads via a filesystem-aware tool. Streams, capped at 200 MB. item_id (required), drive_id? { local_path, size_bytes, name, content_type } Files.Read
m365-graph:list_calendars Lists the user's calendars (primary + group / shared). limit? (1–100, default 50) { count, calendars: [] } with id / name / color / owner / is_default / can_edit / can_share. Calendars.Read
m365-graph:list_events Lists events in a date window. Recurrences are expanded — each occurrence is its own event. start + end (ISO 8601, required), calendar_id?, limit? (1–200, default 100) { window, count, events: [] } with id / subject / start / end / location / organizer / attendees / web_url. Calendars.Read
m365-graph:search_events Searches events by subject substring (Graph $search isn't supported on Events; subject-only via contains()). Returns recurrence series masters, not occurrences. query (required), limit? (1–50, default 20) { count, results: [] } (same event shape). Calendars.Read
m365-graph:get_event Fetches full details for a single event — body (capped at 8000 chars), attendees with response statuses, location, recurrence rule. event_id (required) event summary + body / body_content_type / body_truncated / recurrence. Calendars.Read
m365-graph:upload_file Uploads a local file to a drive. Auto-routes between single PUT (≤ 4 MB) and resumable upload session (> 4 MB, 10 MB chunks). 200 MB hard cap. local_path (required), drive_id?, parent_item_id?, name?, conflict_behavior? (fail/replace/rename, default fail) { uploaded: { id, name, size, webUrl, upload_path } } Files.ReadWrite
m365-graph:create_event Creates a new event on the user's primary calendar (or a specified calendar). Sends invitations to attendees by Graph default. subject + start + end (required), timezone? (default UTC), body?, body_content_type? (text/html), location?, attendees?, is_all_day?, calendar_id? { created: <event summary> } Calendars.ReadWrite
m365-graph:update_event Updates an existing event. All fields except event_id are optional; only provided fields are PATCHed. Attendees: full replacement, not merge — pass the full intended list. event_id (required), then any subset of subject/start+end+timezone/body+body_content_type/location/attendees/is_all_day { updated: <event summary> } Calendars.ReadWrite
m365-graph:copy_file Async copy with polling. POSTs to /items/{id}/copy, polls the monitor URL with exponential backoff (1s → 2s → … capped at 30s) until completion. Falls back to list-by-name if the monitor's completed response omits resourceLocation (common Graph quirk). item_id + target_parent_id (required); source_drive_id?, target_drive_id?, new_name?, wait_max_seconds? (1–1800, default 300) { status: "completed", copied: { id, name, ... } } Files.ReadWrite
m365-graph:move_file Synchronous move within a drive (PATCH parentReference). Cross-drive moves are not supported here — use copy_file + delete_file for those. item_id + target_parent_id (required); drive_id?, new_name? { moved: { id, name, ... } } Files.ReadWrite
m365-graph:delete_file Two-phase spec/approval: 1st call returns preview + confirmation_token; 2nd call (same args + token) executes the DELETE. Token single-use, 5 min expiry, tied to exact spec (canonical-JSON SHA-256). item_id (required), drive_id?, confirmation_token? preview { item, confirmation_token, expires_at } or execute { deleted: { ... } } Files.ReadWrite
m365-graph:cancel_event Two-phase like delete_file. Cancels a meeting the user organizes (sends cancellation notice to attendees). event_id (required), comment?, confirmation_token? preview or { cancelled: { event_id } } Calendars.ReadWrite
m365-graph:decline_event Two-phase. Declines an event the user is invited to (as attendee — distinct from cancel which is for events the user organizes). Sends a decline RSVP unless send_response: false. event_id (required), comment?, send_response? (default true), confirmation_token? preview or { declined: { event_id, send_response } } Calendars.ReadWrite
m365-graph:search_events_content Subject + body content search via the Microsoft Search API (POST /search/query). Distinct from search_events (subject-only via $filter). Returns recurrence series masters; for occurrences in a window use list_events. query (required), limit? (1–50, default 25), from? (pagination offset, default 0) { count, total, results: [<event summary>] } Calendars.Read
m365-graph:list_meeting_transcripts List available transcripts for a Teams meeting identified by its calendar event ID. Transcripts are post-meeting only and require recording to have been enabled by the organizer. event_id (required) { event_id, meeting_id, count, transcripts: [{ id, meeting_id, created_at, end_at }] } Calendars.Read, OnlineMeetings.Read ¹, OnlineMeetingTranscript.Read.All ¹
m365-graph:get_transcript Fetch the text content of a Teams meeting transcript. VTT timing markers are stripped; returns clean readable text capped at 30 000 chars. meeting_id + transcript_id (both required, from list_meeting_transcripts) { meeting_id, transcript_id, char_count, truncated, transcript } OnlineMeetingTranscript.Read.All ¹

¹ Admin consent required. OnlineMeetings.Read and OnlineMeetingTranscript.Read.All must be granted in the Entra app registration under API permissions → Add a permission → Microsoft Graph → Delegated → Grant admin consent. Without admin consent these tools return 403 Forbidden.

That's 4 read + 4 write on files, 5 read + 4 write on calendars, 2 read on meeting transcripts — 19 tools total. Read tools exercise delegated Files.Read + Calendars.Read; write tools require Files.ReadWrite + Calendars.ReadWrite; transcript tools require OnlineMeetings.Read + OnlineMeetingTranscript.Read.All (all separately granted + admin-consented in the Entra app).

Local development

The repo expects a .env.local file with your tenant's credentials. Bootstrap from the template:

cp .env.example .env.local
# then edit .env.local with your M365_TENANT_ID, M365_CLIENT_ID,
# and M365_CLIENT_SECRET — see ARCHITECTURE.md § Authentication
# for the Entra app registration flow.

.env.local is gitignored.

One-time OAuth setup

The first time you run the server, you need to complete an OAuth flow to populate the OS keychain with refresh tokens:

npm run setup

This opens your browser, signs you in to your tenant, captures the authorization code via a one-shot listener at http://localhost:3000/auth/callback, and persists the resulting tokens via @napi-rs/keyring (macOS Keychain / Linux Secret Service / Windows Credential Manager). After that, the server uses cached tokens silently — refreshes as needed via the cached refresh grant.

Run the MCP server

npm run dev

Listens on stdio. Useful when developing alongside an MCP client like Claude Code: configure the client to spawn npm run dev (or tsx --env-file=.env.local src/index.ts) as its MCP server command.

Architecture

See ARCHITECTURE.md for design rationale: scope, OAuth model with @azure/msal-node, token persistence via @napi-rs/keyring, per-tool scope minimization, filesystem sandboxing for upload/download tools, and async-op polling for copy / move.

Contributing

See CONTRIBUTING.md. The repo follows the juvantlabs/handbook conventions for MCP server repos.

Security

See SECURITY.md for the disclosure process. Per the handbook security disclosure process, report vulnerabilities privately via GitHub Security Advisory or security@juvant.io.

License

MIT. Copyright (c) 2026 Juvant Srls.

Recommended Servers

playwright-mcp

playwright-mcp

A Model Context Protocol server that enables LLMs to interact with web pages through structured accessibility snapshots without requiring vision models or screenshots.

Official
Featured
TypeScript
Magic Component Platform (MCP)

Magic Component Platform (MCP)

An AI-powered tool that generates modern UI components from natural language descriptions, integrating with popular IDEs to streamline UI development workflow.

Official
Featured
Local
TypeScript
Audiense Insights MCP Server

Audiense Insights MCP Server

Enables interaction with Audiense Insights accounts via the Model Context Protocol, facilitating the extraction and analysis of marketing insights and audience data including demographics, behavior, and influencer engagement.

Official
Featured
Local
TypeScript
VeyraX MCP

VeyraX MCP

Single MCP tool to connect all your favorite tools: Gmail, Calendar and 40 more.

Official
Featured
Local
graphlit-mcp-server

graphlit-mcp-server

The Model Context Protocol (MCP) Server enables integration between MCP clients and the Graphlit service. Ingest anything from Slack to Gmail to podcast feeds, in addition to web crawling, into a Graphlit project - and then retrieve relevant contents from the MCP client.

Official
Featured
TypeScript
Kagi MCP Server

Kagi MCP Server

An MCP server that integrates Kagi search capabilities with Claude AI, enabling Claude to perform real-time web searches when answering questions that require up-to-date information.

Official
Featured
Python
E2B

E2B

Using MCP to run code via e2b.

Official
Featured
Neon Database

Neon Database

MCP server for interacting with Neon Management API and databases

Official
Featured
Qdrant Server

Qdrant Server

This repository is an example of how to create a MCP server for Qdrant, a vector search engine.

Official
Featured
Exa Search

Exa Search

A Model Context Protocol (MCP) server lets AI assistants like Claude use the Exa AI Search API for web searches. This setup allows AI models to get real-time web information in a safe and controlled way.

Official
Featured