@bold-tech-npm/graph-mcp
v0.6.0
Published
Microsoft Graph MCP server with device-code auth and smart inbox reads (unread + date filter, thread collapse by conversation or normalized subject). By Bold Tech.
Maintainers
Readme
@bold-tech-npm/graph-mcp
A small MCP server that exposes Microsoft Graph (Outlook) to an AI client, with two things most Graph connectors get wrong:
- Device-code auth — no client secret, no redirect URI. The signed-in user consents in a browser; the server acts as that user (delegated, security-trimmed), so it only ever sees what they're permitted to see.
- Smart inbox reads — an overnight burst of long, multi-message threads is collapsed to one
item per thread (by
conversationIdor normalized subject), with unread and date-window filters, so the model gets a clean list instead of a confusing pile.
Configuration is entirely via environment variables, so one published package serves many tenants/clients.
Configure
| Env var | Required | Default | Notes |
|---|---|---|---|
| GRAPH_CLIENT_ID | ✅ | — | Entra app registration (public client, device-code enabled) |
| GRAPH_TENANT_ID | ✅ | — | Tenant GUID for a single-tenant app, or organizations for a multi-tenant one (see below) |
| GRAPH_SCOPES | | User.Read,Mail.Read | Add Mail.ReadWrite to enable draft tools |
| GRAPH_TOKEN_CACHE | | ~/.bold-tech/graph-mcp/<tenant>/tokens.json | Token cache |
| GRAPH_ACCOUNT | | — | Pin which signed-in account to run as, by username. Only needed when several are cached |
| GRAPH_ACTIVE_ACCOUNT | | next to the cache | Pointer file recording the account connect last signed in as |
Multi-tenant: one app, many mailboxes
GRAPH_TENANT_ID is interpolated straight into the authority, so:
- a tenant GUID → single-tenant. Only that directory's users can sign in.
organizations→ multi-tenant. Any work/school account can sign in, provided the app registration is set toAzureADMultipleOrgsand the target tenant has consented. (commonalso works but additionally allows personal Microsoft accounts — usually not what you want.)
Multi-tenant makes "which mailbox am I?" a real question, because one cache directory now holds accounts from different tenants — a test mailbox and a customer's, say. Getting that wrong doesn't error; it produces confident output built on the wrong inbox. So account selection is explicit, in this order:
GRAPH_ACCOUNT, if set — errors if that user isn't signed in.- The account
connectlast signed in as (recorded in the pointer file). - The only cached account, if there's exactly one.
- Otherwise it refuses and lists the candidates — it will not pick for you.
To switch accounts, call connect again and sign in as the other user; the pointer follows.
Note: the cache path contains
<tenant>, so changingGRAPH_TENANT_ID(e.g. a GUID →organizations) points at a different directory and everyone signs in once more.
Sign in
Preferred — in-conversation (no terminal): the AI client calls the connect tool, which
returns a verification URL + code; the user opens it, signs in, and auth_status confirms.
Nothing to run in a shell. This is the flow to use inside a plugin.
Alternative — CLI (for scripts/CI):
GRAPH_CLIENT_ID=… GRAPH_TENANT_ID=… npx -y @bold-tech-npm/graph-mcp loginEither way, tokens cache per-tenant, so multiple clients coexist on one machine.
Use from an MCP client / plugin
{
"mcpServers": {
"graph": {
"command": "npx",
"args": ["-y", "@bold-tech-npm/graph-mcp"],
"env": {
"GRAPH_CLIENT_ID": "…",
"GRAPH_TENANT_ID": "…",
"GRAPH_SCOPES": "User.Read,Mail.ReadWrite"
}
}
}
}Tools
| Tool | Scope | Description |
|---|---|---|
| connect | — | Start/confirm sign-in; returns a verification URL + code (or "connected") |
| auth_status | — | Sign-in status: connected / pending (URL + code) / not_connected |
| whoami | User.Read | The signed-in user (proves the auth chain) |
| inbox_list | Mail.Read | Inbox messages with unread/date filters and thread collapse |
| create_draft | Mail.ReadWrite | Create an email draft (never sends) |
| create_reply_draft | Mail.ReadWrite | Reply-draft into an existing thread (never sends) |
Draft tools require Mail.ReadWrite in GRAPH_SCOPES; with a read-only token they return a clear
permission error rather than failing silently.
