ms365-mcp

ms365-mcp

Provides a minimal Microsoft 365 MCP server with fourteen hand-written tools for email, people, and drafts, designed to fit small local models.

Category
Visit Server

README

ms365-mcp

A Microsoft 365 MCP server sized for small local models.

Most MCP servers are built for frontier models with large context windows, so they expose everything the vendor API offers and let the model sort it out. The reference Microsoft 365 server exposes 326 tools generated from Microsoft's Graph OpenAPI spec.

That does not work for an 8B model at 8k context, where the entire tool list sits in the system prompt and the model starts choosing at random somewhere past twenty-five tools. This server takes the opposite approach:

  • Fourteen hand-written tools, with a hard ceiling of twenty. The tool list is a budget, not a feature list.
  • Descriptions written for a small model, not salvaged from API metadata.
  • Draft-first. Exactly one tool can transmit to another person, it takes a draft id rather than a recipient, and it is annotated destructive.
  • Every tool declares its own effect through MCP readOnlyHint and destructiveHint annotations, so a host can classify it without guessing from the tool's name.

Built for the Edge Harness, and useful to any MCP client.

Status

Feature complete, not yet run against a real account. All fourteen tools are implemented and 171 tests pass, none of which touch the network. What has not happened is the part only a real tenant can prove: a live sign-in and a Graph call that gets answered. See docs/PLAN.md for the ten commits and the reasoning behind each decision.

Scaffold, licence, CI done
MCP protocol over stdio done
The fourteen-tool surface done, all fourteen implemented
Sign-in done, untested against a live tenant
Microsoft Graph done, tested against recorded shapes only
Retries and circuit breaker done

Seeing the tool surface

--list-tools starts nothing and needs no account, so it answers "what would a host be offered?" before any of it is wired up:

.venv/bin/python -m ms365_mcp --list-tools

Add --json for a machine-readable version.

   read  whoami          Return the display name, email address and timezone…
   read  resolve_person  Look up a person by name or partial name and return…
   read  list_messages   List messages in a mail folder, newest first, as se…
  write  create_draft    Create a draft message. This does NOT send anything…
! write  send_draft      Send a draft that already exists, by its id. This r…
   ...
14 tools, 6 that change something, 4 marked destructive (!)

Handlers are stubs today: a call returns a sentence saying the tool is declared but not implemented. The surface is deliberately fixed before any of it is built, because the tool names, descriptions and effect classes are what a host classifies and a model is prompted against, and those are expensive to change later.

Signing in

Sign-in is the Microsoft device code flow, so there is no redirect URL to host and no secret to store. You need one thing first: the application id of an Azure app registration that has the delegated Graph permissions this server's tools declare.

export MS365_MCP_CLIENT_ID=<your app registration's application id>
export MS365_MCP_TENANT_ID=organizations   # 'consumers' for outlook.com
.venv/bin/python -m ms365_mcp --login

Then check it, before wiring anything into a host:

.venv/bin/python -m ms365_mcp --verify-login

--verify-login prints the client id, tenant, authority, scopes and cache location, and if an account is cached it calls Graph once. That last part is the point. A cached token can look completely healthy and still be refused, because a work or school tenant can withhold consent for a scope the surface needs, and that only ever shows up as a 403 on a real request. --logout forgets the account and removes the cache.

The token cache lives in the operating system's config directory, never in this repository. It is created with mode 0600 rather than written and then made private, since the gap between those two is a window where a refresh token is readable by every account on the machine. It is replaced by an atomic rename, so an interrupted write costs nothing.

Using it from the Edge Harness

The harness launches this server as a subprocess over stdio, so it runs under its own interpreter and shares no dependencies with the host. That is not incidental: the harness has a local package called mcp/ and the official SDK on PyPI is also called mcp, so installing this server into the harness's virtualenv shadows the harness's own package.

Its registry entry there is ms365-own, and pressing Check in the Agent Lab run options lists every tool with its effect class. All fourteen classify as declared, meaning the harness took them from this server's annotations rather than guessing from tool names, and the entry needs no overrides.

In the harness's default draft mode, send_draft is withheld and the other thirteen are available, so a run cannot reach a person.

Errors are raised as the SDK's ToolError rather than allowed to propagate. That is worth knowing if you are writing a handler here: the SDK treats any other exception as a crash and replaces its message with a generic one, so a sentence like "No signed-in account, run --login" is discarded before it ever reaches the model. ToolError is the deliberate-failure channel and its text survives, while the result is still flagged as an error.

Security

This repository is public and holds no credential of any kind.

Authentication uses the Microsoft device-code flow. The resulting token cache is written to the operating system's config directory with owner-only permissions, never into this repository or its working tree. .gitignore covers token and cache filenames anyway, and a pre-commit hook scans staged changes for credential-shaped strings, so committing one takes deliberate effort rather than a slip.

A line ending in # pragma: allowlist secret is skipped by the hook. That exists so this repository's own tests can contain fake credentials, and it leaves a marker a reviewer can see in the diff, which --no-verify does not. Use it only for data that is provably fake.

Development

python3 -m venv .venv
.venv/bin/pip install -e ".[dev]"
.venv/bin/pytest -q

Enable the secret-scan hook once per clone:

git config core.hooksPath .githooks

Licence

MIT. See LICENSE.

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
Neon Database

Neon Database

MCP server for interacting with Neon Management API and databases

Official
Featured
E2B

E2B

Using MCP to run code via e2b.

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
Qdrant Server

Qdrant Server

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

Official
Featured