BoundBench

MS 365 MCP Server

MCP server for Microsoft 365 (Outlook, Calendar, OneDrive, Teams) via Graph API

github.com/Softeria/ms-365-mcp-server · 2026-10-03 · c471971

Defense-in-depth score

4.9 / 10

Minimal

As shipped, this server gives the model one delegated token that can read, write and send across the user's mail, OneDrive, calendar, contacts and notes, and it applies no approval of its own. The biggest risk is prompt injection through inbound email: one malicious message can lead the model to send private data out by email or delete items, and the only checks are the host client's prompts. Engineering hygiene is strong (encrypted token cache, log redaction, a structured audit log, a .env allowlist), but one tool's risk annotation is inaccurate and read-only mode does not cover every path. For least privilege, run with --read-only or a preset.

Key gaps (3)

  1. Default stdio mode holds one delegated token with write access across the user's mail (including send), OneDrive, calendar, contacts, tasks and notes; a hijacked model inherits the whole account. C1 · Identity & least privilege
  2. Default session reads attacker-controllable email and files, holds private data, and can send mail or delete items with no server-side gate (Rule of Two violated). C5 · Untrusted input blast radius
  3. A .env in the working directory silently sets the OAuth client ID, client secret, tenant and cloud when the operator has not, so a cloned repo can redirect which app registration and tenant the user authenticates to. C6 · Memory, context & configuration integrity

Criteria

C1 Identity & least privilege

Minimal 0.35 / 1.00

The server signs in as the user through Microsoft's login library and holds one delegated token for everything. It does request only the Graph permissions needed by the tools that are switched on, and operators can narrow that with --read-only, presets or an allowed-scopes list. Out of the box, though, that one token can read and write the user's mail, calendar, OneDrive, contacts, tasks and notes and can send mail. Nothing checks individual requests against a policy. If the model is hijacked, it has the user's whole personal Microsoft 365 account.

C2 Approval gates

Minimal 0.25 / 1.00

The server has no approval step of its own; it relies on the host to ask the user, and gives the host risk labels for each tool. Labels for the 300+ Graph tools follow the HTTP method and are accurate, but one local-file tool's label is inaccurate, the account-management tools have no labels, and graph-batch can mix reads and writes in one call. The optional confirm gate is off by default and is satisfied by the model passing confirm: true, so it is not human approval. By default the model can send email and delete items without any server-side check.

C3 Tool & action scoping

Minimal 0.45 / 1.00

Most tools are narrow, one per Microsoft Graph endpoint, with typed schemas, URL-encoded path values and a fixed Graph host. Three general tools widen that: graph-batch sends up to 20 arbitrary Graph requests of any method, download-bytes reads any Graph path, and download-bytes-to-file writes downloaded bytes to any absolute path the model chooses. That last one only refuses to overwrite and uses owner-only permissions; it has no directory restriction. Presets, regex filters and --read-only can shrink the tool set, but by default every personal-mode write tool is on.

C4 Code-execution isolation

N/A · full credit 1.00 / 1.00

The server never runs model-generated code or commands. The only child process is an optional token-cache helper that the operator configures through an environment variable as an absolute path, run without a shell; it cannot be set from a project .env file or by the model. The local file writer is scored under tool scoping; the server itself interprets nothing as code.

C5 Untrusted input blast radius

Minimal 0.25 / 1.00

The server feeds the model content anyone can write: inbound email, calendar invites, shared files and OneNote pages. Results come back as structured Graph JSON, but nothing marks them as untrusted or limits what the model can do afterwards. In the same default session the model can read private mail and files, send email to any address, delete items, and write attachment bytes to the local disk. One malicious email can therefore lead to data leaving by mail and to irreversible actions with no server-side check.

C6 Memory, context & configuration integrity

Minimal 0.25 / 1.00

The server keeps no memory and loads no instruction files. It does read a .env file from whatever directory it is started in, which MCP clients often set to the open project. After a past advisory this was narrowed to four keys, but those keys are the app registration's client ID and secret, the tenant and the Microsoft cloud. A cloned repository can therefore silently point the sign-in at a different app registration, tenant or cloud whenever the operator has not set them.

C7 Third-party extensions

N/A · full credit 1.00 / 1.00

The server loads no plugins, remote tools or third-party MCP servers. Every tool comes from the bundled endpoints.json, and the only dynamic imports are its own packaged dependencies (keytar, Azure identity, the browser opener). Supply-chain risk in those dependencies is outside this criterion.

C8 Secrets & sensitive-data protection

Minimal 0.45 / 1.00

Tokens are handled well. The token cache is encrypted with AES-256-GCM, with the key kept in the OS keychain when one is available. Log output is scrubbed of tokens and email addresses by default, and tokens never appear in tool results. Gaps: redaction can be switched off with an environment variable, every tool's full arguments (message bodies included) are written to the operational log, crash dumps go to stderr unredacted, and get-download-url deliberately hands the model a pre-authenticated download link. The refresh token is long-lived and carries broad write scopes.

C9 Audit & traceability

Minimal 0.38 / 1.00

There is a separate JSON audit log, on by default and kept in the user's home directory with owner-only permissions. It records each Graph and utility tool call with a request ID, tool, HTTP method, status, duration, target resource and recipient domains, plus calls refused by tool filters. In the default local mode it records no user identity, because that is read from a bearer token only present in HTTP mode. Account tools such as select-account and logout, and calls refused by the confirm gate, are not recorded. Writes are best-effort, and a single environment variable turns the log off.

C10 Limits & kill switch

Moderate 0.50 / 1.00

Every Graph call has a 100-second timeout, a capped number of retries and a circuit breaker. Paginated reads stop after 100 pages or 10,000 items, and all of these limits have sensible defaults the operator can change. Rate limiting exists only in HTTP mode, so the default local mode has none. Nothing stops a cancelled request from finishing, and file downloads to disk have no size cap.