Jmap MCP agent

A Model Context Protocol (MCP) server that provides tools for interacting with JMAP (JSON Meta Application Protocol) email servers.

by wyattjoh·MIT license·★ 176 Stars on the repo·GitHub ↗

Files of Jmap MCP

wyattjoh/main1 file
README.md
Show the full text369 lines

JMAP MCP Server

JSR JSR Score JMAP Client JSR Scope

A Deno workspace containing a functional JMAP email client and a Model Context Protocol (MCP) server built on top of it. The client package uses jmap-jam for JMAP protocol access; the MCP package adapts those typed operations into tools.

Features

Email Management Tools
  • Search Emails: Search emails with text queries, sender/recipient filters, date ranges, and keywords. All filters are AND'd together.
  • Get Emails: Retrieve specific emails by ID with configurable property selection
  • Get Threads: Retrieve email threads (conversation chains)
  • Mark Emails: Mark emails as read/unread, flagged/unflagged
  • Move Emails: Move emails to one mailbox
  • Patch Email Mailboxes: Add or remove selected mailbox memberships while preserving unspecified memberships
  • Delete Emails: Delete emails permanently
Mailbox Management
  • Get Mailboxes: List all mailboxes/folders with hierarchy support. Use this to find mailbox IDs needed by other tools.
Incremental Sync
  • Get Email Changes: Get IDs of emails created, updated, or destroyed since a previous state (state-based delta tracking)
  • Get Search Updates: Get additions/removals within a previous search query since its last queryState
Email Composition
  • Send Email: Compose and send new emails with support for plain text and HTML
  • Reply to Email: Reply to existing emails with automatic header handling and reply-all support
Key Capabilities
  • Full JMAP RFC 8620/8621 compliance via jmap-jam
  • Comprehensive input validation with Zod schemas
  • Pagination support for all list operations
  • State-based incremental sync for efficient polling
  • Rich error handling and connection management
  • Capability-based tool registration (read-only, submission)
  • TypeScript support with strong typing
Reusable JMAP Client

Standalone applications can import @wyattjoh/jmap without creating an MCP server or transport:

import { connectJmap, searchEmails } from "jsr:@wyattjoh/jmap";

const connection = await connectJmap({
  sessionUrl: Deno.env.get("JMAP_SESSION_URL")!,
  bearerToken: Deno.env.get("JMAP_BEARER_TOKEN")!,
  accountId: Deno.env.get("JMAP_ACCOUNT_ID"),
});

const unread = await searchEmails(connection, {
  filter: { inMailbox: "inbox-id", notKeyword: "$seen" },
  limit: 50,
  position: 0,
});

See packages/jmap/README.md for the client package.

Installation

Install via the plugin marketplace:

/plugin marketplace add wyattjoh/claude-code-marketplace
/plugin install jmap-mcp@wyattjoh-marketplace

Then configure the required environment variables in your MCP server settings.

Prerequisites
  • Deno v2 or later
  • A JMAP-compliant email server (e.g., Cyrus IMAP, Stalwart Mail Server, FastMail)
  • Valid JMAP authentication credentials
Setup

Add the following to your agent of choice:

{
  "mcpServers": {
    "jmap": {
      "type": "stdio",
      "command": "deno",
      "args": [
        "run",
        "--allow-net=api.fastmail.com",
        "--allow-env=JMAP_SESSION_URL,JMAP_BEARER_TOKEN,JMAP_ACCOUNT_ID",
        "jsr:@wyattjoh/[email protected]"
      ],
      "env": {
        "JMAP_SESSION_URL": "https://api.fastmail.com/jmap/session",
        "JMAP_BEARER_TOKEN": "YOUR_API_TOKEN"
      }
    }
  }
}

Replace api.fastmail.com in --allow-net with your JMAP server's hostname if not using FastMail.

Usage

Environment Variables
Variable Required Description
JMAP_SESSION_URL Yes JMAP server session URL (usually ends with /.well-known/jmap)
JMAP_BEARER_TOKEN Yes Bearer token for authentication
JMAP_ACCOUNT_ID No Account ID (auto-detected if not provided)
Available Tools
get_mailboxes

List mailboxes/folders with their IDs, names, and metadata. Call this first to get mailbox IDs needed by search_emails (inMailbox) and move_emails (mailboxId). Common names: Inbox, Drafts, Sent, Trash, Archive, Spam/Junk.

Parameters:

  • parentId (optional): Filter by parent mailbox ID
  • limit (optional): Max results (1-200, default: 100)
  • position (optional): Starting position for pagination
search_emails

Search emails with filters. All filters are AND'd together. Returns only email IDs — use get_emails to fetch content. Results include queryState for incremental sync via get_search_updates.

Parameters:

  • query (optional): Text search across all fields
  • body (optional): Search in message body only
  • from (optional): Filter by sender email address
  • to (optional): Filter by recipient email address
  • subject (optional): Filter by subject text
  • inMailbox (optional): Mailbox ID to search within (get from get_mailboxes)
  • hasKeyword (optional): Filter by keyword (e.g., $seen, $flagged)
  • notKeyword (optional): Exclude by keyword (e.g., $seen, $draft)
  • allInThreadHaveKeyword (optional): All emails in thread must have keyword
  • someInThreadHaveKeyword (optional): At least one email in thread must have keyword
  • before (optional): Only emails before date (ISO 8601 datetime)
  • after (optional): Only emails after date (ISO 8601 datetime)
  • limit (optional): Max results (1-100, default: 50)
  • position (optional): Starting position for pagination (default: 0)
get_emails

Retrieve specific emails by their IDs. Use properties to request only what you need — fetching all properties returns large payloads.

Parameters:

  • ids: Array of email IDs (1-50 IDs)
  • properties (optional): Specific properties to return. Recommended sets:
    • Summary: ["id", "subject", "from", "to", "receivedAt", "preview"]
    • Full read: ["id", "subject", "from", "to", "cc", "receivedAt", "bodyValues", "textBody", "htmlBody"]
    • Note: To get body content, include bodyValues AND textBody/htmlBody
get_threads

Get email threads by their IDs. Thread IDs come from get_emails responses (threadId property). Returns email IDs per thread — use get_emails on those IDs to fetch content.

Parameters:

  • ids: Array of thread IDs (1-20 IDs)
get_email_changes

Get IDs of emails created, updated, or destroyed since a previous state. Use the state string from a get_emails response.

Parameters:

  • sinceState: State string from a previous get_emails response
  • maxChanges (optional): Max changes to return (1-500)
  • fetchEmails (optional): Auto-fetch full email details for changed IDs (default: false)
  • properties (optional): Properties to fetch when fetchEmails is true
get_search_updates

Get changes within a previous search query since its queryState. Must use the same filter parameters as the original search_emails call.

Parameters:

  • sinceQueryState: queryState from a previous search_emails response
  • All filter parameters from search_emails (must match original query)
  • maxChanges (optional): Max changes to return (1-500)
mark_emails

Mark emails as read/unread or flagged/unflagged.

Parameters:

  • ids: Array of email IDs (1-100 IDs)
  • seen (optional): Mark as read (true) or unread (false)
  • flagged (optional): Mark as flagged (true) or unflagged (false)
move_emails

Move emails to a different mailbox. Use get_mailboxes to find the target mailbox ID.

Parameters:

  • ids: Array of email IDs (1-100 IDs)
  • mailboxId: Target mailbox ID (get from get_mailboxes)
patch_email_mailboxes

Add or remove selected mailbox memberships without replacing unspecified memberships. This supports label-like workflows and compound categorization.

Parameters:

  • ids: Array of email IDs (1-100 IDs)
  • addMailboxIds (optional): Mailbox IDs to add
  • removeMailboxIds (optional): Mailbox IDs to remove

At least one mailbox must be added or removed. A mailbox cannot appear in both arrays.

delete_emails

Delete emails permanently (cannot be undone). Prefer moving to Trash via move_emails for recoverable deletion.

Parameters:

  • ids: Array of email IDs (1-100 IDs)
send_email

Send a new email. Requires either textBody or htmlBody (or both).

Parameters:

  • to: Array of recipients (name optional, email required)
  • cc (optional): Array of CC recipients
  • bcc (optional): Array of BCC recipients
  • subject: Email subject
  • textBody (optional): Plain text body
  • htmlBody (optional): HTML body
  • identityId (optional): JMAP identity ID to send from (uses server default if omitted)
reply_to_email

Reply to an existing email. Automatically sets To/CC, Re: subject prefix, and threading headers (In-Reply-To, References).

Parameters:

  • emailId: ID of email to reply to
  • replyAll (optional): Include all original recipients (default: false)
  • subject (optional): Custom reply subject (defaults to Re: <original>)
  • textBody (optional): Plain text body
  • htmlBody (optional): HTML body
  • identityId (optional): JMAP identity ID to send from (uses server default if omitted)

JMAP Server Compatibility

This server should work with any JMAP-compliant email server, including:

Development

Running in Development
deno task watch     # Run with file watching
deno task start     # Run without watching
Testing
deno task test      # Run all tests
deno task check     # Type-check both packages
deno task lint      # Lint workspace packages
deno task fmt       # Format code
deno task publish:check # Validate both JSR packages

Architecture

The repository is a Deno workspace with two independently publishable packages:

  • packages/jmap / @wyattjoh/jmap: Functional connection, retrieval, incremental sync, mutation, and submission operations
  • packages/jmap-mcp / @wyattjoh/jmap-mcp: MCP schemas, tool adapters, capability registration, and stdio entrypoint

The MCP adapters call the client package directly. JMAP behavior belongs in the client package; transport validation and user-facing tool contracts belong in the MCP package.

Security

  • All input is validated using Zod schemas
  • Environment variables are used for sensitive configuration
  • No secrets are logged or exposed in responses
  • Follows JMAP security best practices

Contributing

  1. Fork the repository
  2. Create a feature branch
  3. Make changes following the functional programming style
  4. Test your changes thoroughly
  5. Submit a pull request

License

MIT License - see LICENSE file for details.

1# JMAP MCP Server
2 
3[![JSR](https://jsr.io/badges/@wyattjoh/jmap-mcp)](https://jsr.io/@wyattjoh/jmap-mcp)
4[![JSR Score](https://jsr.io/badges/@wyattjoh/jmap-mcp/score)](https://jsr.io/@wyattjoh/jmap-mcp)
5[![JMAP Client](https://jsr.io/badges/@wyattjoh/jmap)](https://jsr.io/@wyattjoh/jmap)
6[![JSR Scope](https://jsr.io/badges/@wyattjoh)](https://jsr.io/@wyattjoh)
7 
8A Deno workspace containing a functional JMAP email client and a Model Context
9Protocol (MCP) server built on top of it. The client package uses
10[jmap-jam](https://github.com/htunnicliff/jmap-jam) for JMAP protocol access;
11the MCP package adapts those typed operations into tools.
12 
13## Features
14 
15### Email Management Tools
16 
17- **Search Emails**: Search emails with text queries, sender/recipient filters,
18 date ranges, and keywords. All filters are AND'd together.
19- **Get Emails**: Retrieve specific emails by ID with configurable property
20 selection
21- **Get Threads**: Retrieve email threads (conversation chains)
22- **Mark Emails**: Mark emails as read/unread, flagged/unflagged
23- **Move Emails**: Move emails to one mailbox
24- **Patch Email Mailboxes**: Add or remove selected mailbox memberships while
25 preserving unspecified memberships
26- **Delete Emails**: Delete emails permanently
27 
28### Mailbox Management
29 
30- **Get Mailboxes**: List all mailboxes/folders with hierarchy support. Use this
31 to find mailbox IDs needed by other tools.
32 
33### Incremental Sync
34 
35- **Get Email Changes**: Get IDs of emails created, updated, or destroyed since
36 a previous state (state-based delta tracking)
37- **Get Search Updates**: Get additions/removals within a previous search query
38 since its last queryState
39 
40### Email Composition
41 
42- **Send Email**: Compose and send new emails with support for plain text and
43 HTML
44- **Reply to Email**: Reply to existing emails with automatic header handling
45 and reply-all support
46 
47### Key Capabilities
48 
49- Full JMAP RFC 8620/8621 compliance via jmap-jam
50- Comprehensive input validation with Zod schemas
51- Pagination support for all list operations
52- State-based incremental sync for efficient polling
53- Rich error handling and connection management
54- Capability-based tool registration (read-only, submission)
55- TypeScript support with strong typing
56 
57### Reusable JMAP Client
58 
59Standalone applications can import `@wyattjoh/jmap` without creating an MCP
60server or transport:
61 
62```ts
63import { connectJmap, searchEmails } from "jsr:@wyattjoh/jmap";
64 
65const connection = await connectJmap({
66 sessionUrl: Deno.env.get("JMAP_SESSION_URL")!,
67 bearerToken: Deno.env.get("JMAP_BEARER_TOKEN")!,
68 accountId: Deno.env.get("JMAP_ACCOUNT_ID"),
69});
70 
71const unread = await searchEmails(connection, {
72 filter: { inMailbox: "inbox-id", notKeyword: "$seen" },
73 limit: 50,
74 position: 0,
75});
76```
77 
78See [`packages/jmap/README.md`](packages/jmap/README.md) for the client package.
79 
80## Installation
81 
82### Claude Code Plugin (Recommended)
83 
84Install via the plugin marketplace:
85 
86```shell
87/plugin marketplace add wyattjoh/claude-code-marketplace
88/plugin install jmap-mcp@wyattjoh-marketplace
89```
90 
91Then configure the required environment variables in your MCP server settings.
92 
93### Prerequisites
94 
95- [Deno](https://deno.land/) v2 or later
96- A JMAP-compliant email server (e.g., Cyrus IMAP, Stalwart Mail Server,
97 FastMail)
98- Valid JMAP authentication credentials
99 
100### Setup
101 
102Add the following to your agent of choice:
103 
104<!-- x-release-please-start-version -->
105 
106```json
107{
108 "mcpServers": {
109 "jmap": {
110 "type": "stdio",
111 "command": "deno",
112 "args": [
113 "run",
114 "--allow-net=api.fastmail.com",
115 "--allow-env=JMAP_SESSION_URL,JMAP_BEARER_TOKEN,JMAP_ACCOUNT_ID",
116 "jsr:@wyattjoh/[email protected]"
117 ],
118 "env": {
119 "JMAP_SESSION_URL": "https://api.fastmail.com/jmap/session",
120 "JMAP_BEARER_TOKEN": "YOUR_API_TOKEN"
121 }
122 }
123 }
124}
125```
126 
127<!-- x-release-please-end -->
128 
129> Replace `api.fastmail.com` in `--allow-net` with your JMAP server's hostname
130> if not using FastMail.
131 
132## Usage
133 
134### Environment Variables
135 
136| Variable | Required | Description |
137| ------------------- | -------- | --------------------------------------------------------------- |
138| `JMAP_SESSION_URL` | Yes | JMAP server session URL (usually ends with `/.well-known/jmap`) |
139| `JMAP_BEARER_TOKEN` | Yes | Bearer token for authentication |
140| `JMAP_ACCOUNT_ID` | No | Account ID (auto-detected if not provided) |
141 
142### Available Tools
143 
144#### `get_mailboxes`
145 
146List mailboxes/folders with their IDs, names, and metadata. **Call this first**
147to get mailbox IDs needed by `search_emails` (`inMailbox`) and `move_emails`
148(`mailboxId`). Common names: Inbox, Drafts, Sent, Trash, Archive, Spam/Junk.
149 
150**Parameters:**
151 
152- `parentId` (optional): Filter by parent mailbox ID
153- `limit` (optional): Max results (1-200, default: 100)
154- `position` (optional): Starting position for pagination
155 
156#### `search_emails`
157 
158Search emails with filters. **All filters are AND'd together.** Returns only
159email IDs — use `get_emails` to fetch content. Results include `queryState` for
160incremental sync via `get_search_updates`.
161 
162**Parameters:**
163 
164- `query` (optional): Text search across all fields
165- `body` (optional): Search in message body only
166- `from` (optional): Filter by sender email address
167- `to` (optional): Filter by recipient email address
168- `subject` (optional): Filter by subject text
169- `inMailbox` (optional): Mailbox ID to search within (get from `get_mailboxes`)
170- `hasKeyword` (optional): Filter by keyword (e.g., `$seen`, `$flagged`)
171- `notKeyword` (optional): Exclude by keyword (e.g., `$seen`, `$draft`)
172- `allInThreadHaveKeyword` (optional): All emails in thread must have keyword
173- `someInThreadHaveKeyword` (optional): At least one email in thread must have
174 keyword
175- `before` (optional): Only emails before date (ISO 8601 datetime)
176- `after` (optional): Only emails after date (ISO 8601 datetime)
177- `limit` (optional): Max results (1-100, default: 50)
178- `position` (optional): Starting position for pagination (default: 0)
179 
180#### `get_emails`
181 
182Retrieve specific emails by their IDs. Use `properties` to request only what you
183need — fetching all properties returns large payloads.
184 
185**Parameters:**
186 
187- `ids`: Array of email IDs (1-50 IDs)
188- `properties` (optional): Specific properties to return. Recommended sets:
189 - Summary: `["id", "subject", "from", "to", "receivedAt", "preview"]`
190 - Full read:
191 `["id", "subject", "from", "to", "cc", "receivedAt", "bodyValues", "textBody", "htmlBody"]`
192 - **Note:** To get body content, include `bodyValues` AND
193 `textBody`/`htmlBody`
194 
195#### `get_threads`
196 
197Get email threads by their IDs. Thread IDs come from `get_emails` responses
198(`threadId` property). Returns email IDs per thread — use `get_emails` on those
199IDs to fetch content.
200 
201**Parameters:**
202 
203- `ids`: Array of thread IDs (1-20 IDs)
204 
205#### `get_email_changes`
206 
207Get IDs of emails created, updated, or destroyed since a previous state. Use the
208`state` string from a `get_emails` response.
209 
210**Parameters:**
211 
212- `sinceState`: State string from a previous `get_emails` response
213- `maxChanges` (optional): Max changes to return (1-500)
214- `fetchEmails` (optional): Auto-fetch full email details for changed IDs
215 (default: false)
216- `properties` (optional): Properties to fetch when `fetchEmails` is true
217 
218#### `get_search_updates`
219 
220Get changes within a previous search query since its `queryState`. Must use the
221same filter parameters as the original `search_emails` call.
222 
223**Parameters:**
224 
225- `sinceQueryState`: `queryState` from a previous `search_emails` response
226- All filter parameters from `search_emails` (must match original query)
227- `maxChanges` (optional): Max changes to return (1-500)
228 
229#### `mark_emails`
230 
231Mark emails as read/unread or flagged/unflagged.
232 
233**Parameters:**
234 
235- `ids`: Array of email IDs (1-100 IDs)
236- `seen` (optional): Mark as read (`true`) or unread (`false`)
237- `flagged` (optional): Mark as flagged (`true`) or unflagged (`false`)
238 
239#### `move_emails`
240 
241Move emails to a different mailbox. Use `get_mailboxes` to find the target
242mailbox ID.
243 
244**Parameters:**
245 
246- `ids`: Array of email IDs (1-100 IDs)
247- `mailboxId`: Target mailbox ID (get from `get_mailboxes`)
248 
249#### `patch_email_mailboxes`
250 
251Add or remove selected mailbox memberships without replacing unspecified
252memberships. This supports label-like workflows and compound categorization.
253 
254**Parameters:**
255 
256- `ids`: Array of email IDs (1-100 IDs)
257- `addMailboxIds` (optional): Mailbox IDs to add
258- `removeMailboxIds` (optional): Mailbox IDs to remove
259 
260At least one mailbox must be added or removed. A mailbox cannot appear in both
261arrays.
262 
263#### `delete_emails`
264 
265Delete emails permanently (cannot be undone). Prefer moving to Trash via
266`move_emails` for recoverable deletion.
267 
268**Parameters:**
269 
270- `ids`: Array of email IDs (1-100 IDs)
271 
272#### `send_email`
273 
274Send a new email. Requires either `textBody` or `htmlBody` (or both).
275 
276**Parameters:**
277 
278- `to`: Array of recipients (`name` optional, `email` required)
279- `cc` (optional): Array of CC recipients
280- `bcc` (optional): Array of BCC recipients
281- `subject`: Email subject
282- `textBody` (optional): Plain text body
283- `htmlBody` (optional): HTML body
284- `identityId` (optional): JMAP identity ID to send from (uses server default if
285 omitted)
286 
287#### `reply_to_email`
288 
289Reply to an existing email. Automatically sets To/CC, Re: subject prefix, and
290threading headers (In-Reply-To, References).
291 
292**Parameters:**
293 
294- `emailId`: ID of email to reply to
295- `replyAll` (optional): Include all original recipients (default: false)
296- `subject` (optional): Custom reply subject (defaults to `Re: <original>`)
297- `textBody` (optional): Plain text body
298- `htmlBody` (optional): HTML body
299- `identityId` (optional): JMAP identity ID to send from (uses server default if
300 omitted)
301 
302## JMAP Server Compatibility
303 
304This server should work with any JMAP-compliant email server, including:
305 
306- [Cyrus IMAP](https://www.cyrusimap.org/) 3.0+
307- [Stalwart Mail Server](https://stalw.art/)
308- [FastMail](https://www.fastmail.com/) (commercial)
309- [Apache James](https://james.apache.org/) (with JMAP support)
310 
311## Development
312 
313### Running in Development
314 
315```bash
316deno task watch # Run with file watching
317deno task start # Run without watching
318```
319 
320### Testing
321 
322```bash
323deno task test # Run all tests
324deno task check # Type-check both packages
325deno task lint # Lint workspace packages
326deno task fmt # Format code
327deno task publish:check # Validate both JSR packages
328```
329 
330## Architecture
331 
332The repository is a Deno workspace with two independently publishable packages:
333 
334- **`packages/jmap` / `@wyattjoh/jmap`**: Functional connection, retrieval,
335 incremental sync, mutation, and submission operations
336- **`packages/jmap-mcp` / `@wyattjoh/jmap-mcp`**: MCP schemas, tool adapters,
337 capability registration, and stdio entrypoint
338 
339The MCP adapters call the client package directly. JMAP behavior belongs in the
340client package; transport validation and user-facing tool contracts belong in
341the MCP package.
342 
343## Security
344 
345- All input is validated using Zod schemas
346- Environment variables are used for sensitive configuration
347- No secrets are logged or exposed in responses
348- Follows JMAP security best practices
349 
350## Contributing
351 
3521. Fork the repository
3532. Create a feature branch
3543. Make changes following the functional programming style
3554. Test your changes thoroughly
3565. Submit a pull request
357 
358## License
359 
360MIT License - see [LICENSE](LICENSE) file for details.
361 
362## Related Projects
363 
364- [jmap-jam](https://github.com/htunnicliff/jmap-jam) - JMAP client library
365- [Model Context Protocol](https://modelcontextprotocol.io/) - MCP specification
366- [JMAP RFC 8620](https://datatracker.ietf.org/doc/html/rfc8620) - JMAP core
367 protocol
368- [JMAP RFC 8621](https://datatracker.ietf.org/doc/html/rfc8621) - JMAP for Mail
369 

Discussion

Alternatives