outlook-file-attach-mcp
v2.0.0
Published
MCP server that attaches local files to Outlook draft emails for the signed-in user via Microsoft Graph (delegated auth, shared MSAL token cache).
Maintainers
Readme
Outlook File Attach MCP Server (v2)
A lightweight MCP server that attaches local files to Outlook draft emails on behalf of the signed-in user via the Microsoft Graph API.
Why this exists
MCP-based email tools like @softeria/ms-365-mcp-server require file content to be passed as base64 through tool parameters. For files larger than ~50KB, this exceeds what an LLM can output in a single tool call. This server solves the problem by accepting a local file path instead — it reads the file from disk, encodes it, and uploads it directly to Graph.
What changed in v2
v2 is a clean rewrite that switches from application credentials (one shared client secret across users) to delegated authentication (each user's own OAuth token).
| | v1 | v2 |
|---|---|---|
| Auth model | Application (client_credentials) | Delegated (PKCE + device code) |
| Client secret | Required | Not used |
| Mailbox scope | Any mailbox the app's ApplicationAccessPolicy allows | The signed-in user's own mailbox |
| userEmail tool parameter | Required | Removed — implicit |
| Endpoint | /users/{userEmail}/messages/... | /me/messages/... |
| Token cache | None (re-auth every call) | Persistent file cache, shareable with @softeria |
The result: no shared secret to distribute, no ApplicationAccessPolicy, no security group, no per-rep blast radius beyond the rep's own mailbox.
Requirements
- Node.js 18+
- An Azure AD app registration with:
- Authentication → "Allow public client flows" set to Yes
- A Mobile and desktop applications platform with redirect URI
http://localhost - Delegated Microsoft Graph permissions:
Mail.ReadWrite,Mail.Send(admin consent recommended) - No client secret required
The same Azure AD app can serve both this server and @softeria/ms-365-mcp-server.
Configuration
Three environment variables:
| Variable | Required | Description |
|---|---|---|
| MS365_MCP_CLIENT_ID | Yes | Application (client) ID of the Azure AD app |
| MS365_MCP_TENANT_ID | Yes | Directory (tenant) ID |
| MS365_MCP_TOKEN_CACHE_PATH | No | Path to the persisted token cache file. Defaults to ${LOCALAPPDATA}\ms-365-mcp-server\tokens.json on Windows, ~/.config/ms-365-mcp-server/tokens.json elsewhere. Supports ${VAR} expansion. |
The default cache path matches @softeria/ms-365-mcp-server's convention. When both servers are configured with the same MS365_MCP_TOKEN_CACHE_PATH (or both rely on the default), they share a single login session.
MCP host config example
{
"ms365": {
"command": "npx",
"args": ["-y", "@softeria/ms-365-mcp-server", "--preset", "mail"],
"env": {
"MS365_MCP_CLIENT_ID": "your-client-id",
"MS365_MCP_TENANT_ID": "your-tenant-id"
}
},
"outlook-file-attach": {
"command": "npx",
"args": ["-y", "outlook-file-attach-mcp"],
"env": {
"MS365_MCP_CLIENT_ID": "your-client-id",
"MS365_MCP_TENANT_ID": "your-tenant-id"
}
}
}No client secret in either entry. Both MCPs target the same Azure AD app and share a single token cache.
First-time sign-in
Before the MCP server can act, a token must exist in the cache. The simplest path is to use @softeria/ms-365-mcp-server's --login mode, which populates the shared cache and serves both servers:
# Set env vars for this one-shot session
export MS365_MCP_CLIENT_ID=your-client-id
export MS365_MCP_TENANT_ID=your-tenant-id
npx -y @softeria/ms-365-mcp-server --loginOr use this server's own --login mode:
npx -y outlook-file-attach-mcp --loginEither prints a Microsoft device code to the terminal. Open the URL it gives, enter the code, sign in, return to the terminal — login success is reported and the cache file is written. After that, both MCP servers acquire tokens silently.
Tool
The server provides one tool: attach-file-to-email
| Parameter | Type | Required | Description |
|---|---|---|---|
| messageId | string | Yes | Outlook message ID of the draft to attach to |
| filePath | string | Yes | Absolute path to the local file |
| fileName | string | No | Custom filename for the attachment (defaults to original) |
The mailbox is implicitly the signed-in user's. There is no userEmail parameter.
How it works
- The LLM calls
attach-file-to-emailwith a draft message ID and a local file path. - The server acquires an access token silently using the cached refresh token (no user interaction).
- It reads the file from disk, base64-encodes it, builds a fileAttachment payload.
- It POSTs to
https://graph.microsoft.com/v1.0/me/messages/{messageId}/attachments. - The attachment appears on the user's draft, ready to send.
If silent token acquisition fails (no cache, refresh token expired, account removed), the tool returns an actionable error pointing the user at --login.
Limitations
- 4 MB per attachment — Microsoft Graph's single-request upload limit. Larger files require the resumable upload session API; not currently implemented.
- Drafts only — The tool attaches to existing draft messages identified by
messageId. It does not create the draft itself; pair it with@softeria/ms-365-mcp-server'screate-draft-emailtool for that. - Single account — If the token cache holds multiple accounts, this server uses the first one it finds. Multi-account selection is not yet supported.
