Skip to main content
Glama
idea404

email-mcp-plus

by idea404
README.md
# email-mcp-plus

Additive MCP server for Microsoft Graph email that fixes the broken `$search` limitation in the existing [@marlinjai/email-mcp](https://www.npmjs.com/package/@marlinjai/email-mcp).

## Problem

The existing `email-mcp` uses Microsoft Graph's `$search` parameter, which is **incompatible with `$orderBy`**. This means:
- Filtered searches (by sender, subject, date) fail with `"$orderBy is not supported with $search"`
- You can only paginate chronologically through thousands of emails

## Solution

`email-mcp-plus` uses Graph's `$filter` parameter instead, which **works with `$orderBy`**. It shares the same MSAL token cache as `email-mcp`, so:

- No separate app registration needed
- No separate OAuth flow
- Just authenticate once with `email-mcp`, and `email-mcp-plus` reuses the refresh token

## Setup

### 1. Install

```bash
cd ~/Projects/email-mcp-plus
uv sync
```

### 2. Authenticate with email-mcp first

Make sure `@marlinjai/email-mcp` is configured and you've completed the OAuth flow at least once. This populates `~/.email-mcp/msal-cache.json`.

### 3. Add to opencode.json

```json
{
  "mcp": {
    "emailmcpplus": {
      "type": "local",
      "command": ["uv", "run", "--directory", "/Users/dennis/Projects/email-mcp-plus", "python", "-m", "email_mcp_plus.server"],
      "enabled": true
    }
  }
}
```

Or run standalone:

```bash
uv run --directory ~/Projects/email-mcp-plus python -m email_mcp_plus.server
```

If the shared MSAL cache contains more than one Microsoft account, select the
mailbox explicitly in the MCP environment:

```json
"environment": {
  "EMAIL_MCP_ACCOUNT": "you@example.com"
}
```

The value can be the account's email address or MSAL home account ID. The server
refuses to guess when multiple cached accounts exist, preventing message IDs
from one mailbox from being used against another.

## Tools

| Tool | Description |
|---|---|
| `search_emails` | Filter by `query` (free-text KQL, e.g. `from:klm.com`, `subject:flight`), sender, subject, body, date range, folder using `$filter`/`$search` |
| `list_folders` | List all mail folders with IDs and item counts |
| `get_sent_items` | Search sent items by query/subject/date |
| `get_email_body` | Get full email body by message ID |
| `list_attachments` | List attachments for a message |
| `get_attachment` | Get attachment content (base64) |
| `send_email` | Send email with optional attachments (local `path` or inline `contentBase64`) |
| `create_draft` | Create a draft in the Drafts folder (never sends) with optional attachments — same params as `send_email` |
| `update_draft` | Edit a draft: change to/subject/body/cc/bcc, add attachments, remove attachments by id (from `list_attachments`) |
| `delete_draft` | Delete a draft (refuses to delete non-draft messages) |

Unknown parameters are rejected with an error listing the valid ones, so a
model that guesses a wrong argument name gets a loud, self-correcting failure
instead of silently unfiltered results.

## How it works

```
email-mcp (existing)     →  OAuth wizard → populates ~/.email-mcp/msal-cache.json
email-mcp-plus (this)    →  reads msal-cache.json → acquires silent token → calls Graph API
```

The refresh token in the MSAL cache is long-lived (90 days of inactivity). `email-mcp-plus` uses `msal.acquire_token_silent()` to get fresh access tokens without any browser interaction.

## Requirements

- Python 3.11+
- [uv](https://docs.astral.sh/uv/) for dependency management
- `@marlinjai/email-mcp` configured and authenticated at least once

## License

MIT

TDQS

A3.6/5.0

Scored across 6 tools

Disambiguation4/5

Tools are mostly distinct, but search_emails and get_sent_items both perform searches with overlapping capabilities (subject/date queries). The descriptions clarify that get_sent_items is specific to the sent folder, but an agent could still confuse which to use. Other tools have clearly separate roles.

Naming Consistency4/5

Tool names generally follow a verb_noun pattern (search_emails, list_folders, get_email_body, list_attachments, get_attachment). However, get_sent_items is a search operation but uses 'get' instead of 'search', deviating from the pattern established by search_emails. Overall, naming is readable and predictable.

Tool Count5/5

With 6 tools, the server is well-scoped for email search and retrieval. Each tool covers a needed function without unnecessary overlap or bloat, making the count appropriate.

Completeness4/5

The server covers searching, folder listing, body retrieval, and attachment handling, which is solid for a read-oriented email tool. Missing operations like sending or moving emails are not necessarily required for the apparent scope. A minor gap is the lack of a direct 'list emails in a folder' tool, but search can work around it.

Maintenance

ActivitySlowing
ResponsivenessNo issues