mailcue-mcp 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +120 -0
- package/dist/index.js +465 -0
- package/dist/index.js.map +1 -0
- package/package.json +61 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Olib AI
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
# mailcue-mcp
|
|
2
|
+
|
|
3
|
+
<p align="center">
|
|
4
|
+
<img src="https://raw.githubusercontent.com/Olib-AI/mailcue/main/logo.svg" alt="MailCue" width="280" />
|
|
5
|
+
</p>
|
|
6
|
+
|
|
7
|
+
A [Model Context Protocol](https://modelcontextprotocol.io) server for
|
|
8
|
+
[MailCue](https://github.com/Olib-AI/mailcue) — the open-source email testing and
|
|
9
|
+
production server. It gives an AI agent **its own mailbox**: the agent can read,
|
|
10
|
+
search, triage, send, reply to, and delete email directly, managing an inbox the
|
|
11
|
+
way a person uses a mail client.
|
|
12
|
+
|
|
13
|
+
Built on the official [`mailcue`](https://www.npmjs.com/package/mailcue) Node SDK
|
|
14
|
+
and the MCP TypeScript SDK. Talks to any MailCue server over its REST API.
|
|
15
|
+
|
|
16
|
+
## Why
|
|
17
|
+
|
|
18
|
+
MailCue already exposes a full mail stack (Postfix + Dovecot + IMAP/SMTP) behind
|
|
19
|
+
one API. This server turns that API into MCP tools so an agent can own and run a
|
|
20
|
+
mailbox end-to-end — answering mail, keeping threads tidy, clearing the inbox —
|
|
21
|
+
better and faster than a human babysitting it.
|
|
22
|
+
|
|
23
|
+
## Install / run
|
|
24
|
+
|
|
25
|
+
Requires Node.js 18+ and a running MailCue server with an API key.
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
npx mailcue-mcp
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
The server speaks MCP over **stdio**, so you normally launch it from an MCP
|
|
32
|
+
client's config rather than by hand.
|
|
33
|
+
|
|
34
|
+
## Configuration
|
|
35
|
+
|
|
36
|
+
All configuration is via environment variables:
|
|
37
|
+
|
|
38
|
+
| Variable | Required | Default | Description |
|
|
39
|
+
| ---------------------- | -------- | ------------------------ | ----------- |
|
|
40
|
+
| `MAILCUE_API_KEY` | yes\* | — | MailCue `X-API-Key` (`mc_...`). |
|
|
41
|
+
| `MAILCUE_BEARER_TOKEN` | yes\* | — | JWT alternative to the API key. |
|
|
42
|
+
| `MAILCUE_BASE_URL` | no | `http://localhost:8088` | Your MailCue server URL. |
|
|
43
|
+
| `MAILCUE_MAILBOX` | no | — | Lock the agent to a single mailbox (see below). |
|
|
44
|
+
|
|
45
|
+
\* Provide **either** `MAILCUE_API_KEY` (preferred) or `MAILCUE_BEARER_TOKEN`.
|
|
46
|
+
|
|
47
|
+
### Single-mailbox lock
|
|
48
|
+
|
|
49
|
+
When `MAILCUE_MAILBOX` is set, the server runs in **locked** mode:
|
|
50
|
+
|
|
51
|
+
- Every tool operates only on that mailbox. The `mailbox` argument is removed
|
|
52
|
+
from all tool schemas, so the agent **cannot read or touch any other mailbox**.
|
|
53
|
+
- `send_email` and `reply_email` always send **from** the locked address.
|
|
54
|
+
- The `list_mailboxes` discovery tool is not registered.
|
|
55
|
+
|
|
56
|
+
This is the recommended way to give one agent one inbox it fully owns. Leave
|
|
57
|
+
`MAILCUE_MAILBOX` unset for an operator/admin agent that works across mailboxes;
|
|
58
|
+
in that mode every tool takes a required `mailbox` argument and `list_mailboxes`
|
|
59
|
+
is available for discovery.
|
|
60
|
+
|
|
61
|
+
## Using with an MCP client
|
|
62
|
+
|
|
63
|
+
### Claude Desktop / Claude Code (`mcp.json`)
|
|
64
|
+
|
|
65
|
+
```json
|
|
66
|
+
{
|
|
67
|
+
"mcpServers": {
|
|
68
|
+
"mailcue": {
|
|
69
|
+
"command": "npx",
|
|
70
|
+
"args": ["-y", "mailcue-mcp"],
|
|
71
|
+
"env": {
|
|
72
|
+
"MAILCUE_BASE_URL": "http://localhost:8088",
|
|
73
|
+
"MAILCUE_API_KEY": "mc_your_api_key",
|
|
74
|
+
"MAILCUE_MAILBOX": "agent@mailcue.local"
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
Drop `MAILCUE_MAILBOX` to let the agent work across every mailbox on the server.
|
|
82
|
+
|
|
83
|
+
## Tools
|
|
84
|
+
|
|
85
|
+
| Tool | Purpose | Locked-mode note |
|
|
86
|
+
| ---------------- | ------- | ---------------- |
|
|
87
|
+
| `list_emails` | List a folder's emails (newest first); returns uids. | no `mailbox` arg |
|
|
88
|
+
| `search_emails` | Full-text search a folder. | no `mailbox` arg |
|
|
89
|
+
| `get_email` | Fetch one full email by uid (body, headers, attachments). | no `mailbox` arg |
|
|
90
|
+
| `send_email` | Send a new email. | sends from the locked address; no `from` arg |
|
|
91
|
+
| `reply_email` | Reply by uid; threading + `Re:` subject set automatically. | replies from the locked address |
|
|
92
|
+
| `delete_email` | Permanently delete an email by uid. | no `mailbox` arg |
|
|
93
|
+
| `mailbox_stats` | Per-folder total / unread counts. | no `mailbox` arg |
|
|
94
|
+
| `list_mailboxes` | Discover available mailboxes. | **not available when locked** |
|
|
95
|
+
|
|
96
|
+
uids are scoped to a `(mailbox, folder)` pair — read and act using the same
|
|
97
|
+
folder you listed from. HTML bodies are converted to readable text and long
|
|
98
|
+
bodies are truncated; re-fetch with `get_email` for the full message.
|
|
99
|
+
|
|
100
|
+
### Built-in agent instructions
|
|
101
|
+
|
|
102
|
+
The server ships MCP `instructions` (surfaced to the model at initialization)
|
|
103
|
+
that explain the mailbox scope, when to use each tool, and safe-handling rules
|
|
104
|
+
(e.g. read before replying or deleting, prefer `reply_email` to keep threads
|
|
105
|
+
intact, deletes are irreversible). The text adapts to locked vs. multi-mailbox
|
|
106
|
+
mode, so the agent is oriented without any extra prompting on your side.
|
|
107
|
+
|
|
108
|
+
## Development
|
|
109
|
+
|
|
110
|
+
```bash
|
|
111
|
+
npm install
|
|
112
|
+
npm run typecheck
|
|
113
|
+
npm run lint
|
|
114
|
+
npm run build # bundles to dist/index.js (stdio entrypoint)
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
## License
|
|
118
|
+
|
|
119
|
+
MIT — see `LICENSE`. Part of [MailCue](https://github.com/Olib-AI/mailcue) by
|
|
120
|
+
[Olib AI](https://www.olib.ai).
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,465 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/index.ts
|
|
4
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
5
|
+
|
|
6
|
+
// src/config.ts
|
|
7
|
+
var DEFAULT_BASE_URL = "http://localhost:8088";
|
|
8
|
+
var ConfigError = class extends Error {
|
|
9
|
+
};
|
|
10
|
+
function clean(value) {
|
|
11
|
+
if (value === void 0) return void 0;
|
|
12
|
+
const trimmed = value.trim();
|
|
13
|
+
return trimmed === "" ? void 0 : trimmed;
|
|
14
|
+
}
|
|
15
|
+
function loadConfig(env = process.env) {
|
|
16
|
+
const apiKey = clean(env["MAILCUE_API_KEY"]);
|
|
17
|
+
const bearerToken = clean(env["MAILCUE_BEARER_TOKEN"]);
|
|
18
|
+
if (!apiKey && !bearerToken) {
|
|
19
|
+
throw new ConfigError(
|
|
20
|
+
"Missing credentials: set MAILCUE_API_KEY (preferred) or MAILCUE_BEARER_TOKEN."
|
|
21
|
+
);
|
|
22
|
+
}
|
|
23
|
+
const config = {
|
|
24
|
+
baseUrl: clean(env["MAILCUE_BASE_URL"]) ?? DEFAULT_BASE_URL
|
|
25
|
+
};
|
|
26
|
+
if (apiKey) config.apiKey = apiKey;
|
|
27
|
+
if (bearerToken) config.bearerToken = bearerToken;
|
|
28
|
+
const mailbox = clean(env["MAILCUE_MAILBOX"]);
|
|
29
|
+
if (mailbox) config.mailbox = mailbox;
|
|
30
|
+
return config;
|
|
31
|
+
}
|
|
32
|
+
function isLocked(config) {
|
|
33
|
+
return typeof config.mailbox === "string";
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// src/server.ts
|
|
37
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
38
|
+
import { Mailcue, MailcueError } from "mailcue";
|
|
39
|
+
import { z } from "zod";
|
|
40
|
+
|
|
41
|
+
// src/format.ts
|
|
42
|
+
var MAX_BODY_CHARS = 8e3;
|
|
43
|
+
function htmlToText(html) {
|
|
44
|
+
return html.replace(/<style[\s\S]*?<\/style>/gi, "").replace(/<script[\s\S]*?<\/script>/gi, "").replace(/<\/(p|div|tr|li|h[1-6])>/gi, "\n").replace(/<br\s*\/?>/gi, "\n").replace(/<[^>]+>/g, "").replace(/ /g, " ").replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, '"').replace(/'/g, "'").replace(/[ \t]+\n/g, "\n").replace(/\n{3,}/g, "\n\n").trim();
|
|
45
|
+
}
|
|
46
|
+
function truncate(text2) {
|
|
47
|
+
if (text2.length <= MAX_BODY_CHARS) return text2;
|
|
48
|
+
return `${text2.slice(0, MAX_BODY_CHARS)}
|
|
49
|
+
|
|
50
|
+
[...truncated ${text2.length - MAX_BODY_CHARS} chars. Use get_email with the uid for the full message.]`;
|
|
51
|
+
}
|
|
52
|
+
function summaryView(e) {
|
|
53
|
+
return {
|
|
54
|
+
uid: e.uid,
|
|
55
|
+
from: e.fromAddress,
|
|
56
|
+
to: e.toAddresses,
|
|
57
|
+
subject: e.subject,
|
|
58
|
+
date: e.date,
|
|
59
|
+
unread: !e.isRead,
|
|
60
|
+
hasAttachments: e.hasAttachments,
|
|
61
|
+
preview: e.preview
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
function formatList(res, folder) {
|
|
65
|
+
const view = {
|
|
66
|
+
folder,
|
|
67
|
+
total: res.total,
|
|
68
|
+
page: res.page,
|
|
69
|
+
pageSize: res.pageSize,
|
|
70
|
+
hasMore: res.hasMore,
|
|
71
|
+
returned: res.emails.length,
|
|
72
|
+
emails: res.emails.map(summaryView)
|
|
73
|
+
};
|
|
74
|
+
return JSON.stringify(view, null, 2);
|
|
75
|
+
}
|
|
76
|
+
function formatEmail(e) {
|
|
77
|
+
const bestBody = e.textBody ?? (e.htmlBody ? htmlToText(e.htmlBody) : "");
|
|
78
|
+
const view = {
|
|
79
|
+
uid: e.uid,
|
|
80
|
+
mailbox: e.mailbox,
|
|
81
|
+
messageId: e.messageId,
|
|
82
|
+
from: e.fromAddress,
|
|
83
|
+
to: e.toAddresses,
|
|
84
|
+
cc: e.ccAddresses,
|
|
85
|
+
subject: e.subject,
|
|
86
|
+
date: e.date,
|
|
87
|
+
unread: !e.isRead,
|
|
88
|
+
isSigned: e.isSigned,
|
|
89
|
+
isEncrypted: e.isEncrypted,
|
|
90
|
+
attachments: e.attachments.map((a) => ({
|
|
91
|
+
partId: a.partId,
|
|
92
|
+
filename: a.filename,
|
|
93
|
+
contentType: a.contentType,
|
|
94
|
+
size: a.size
|
|
95
|
+
})),
|
|
96
|
+
body: truncate(bestBody)
|
|
97
|
+
};
|
|
98
|
+
return JSON.stringify(view, null, 2);
|
|
99
|
+
}
|
|
100
|
+
function formatStats(s) {
|
|
101
|
+
return JSON.stringify(
|
|
102
|
+
{
|
|
103
|
+
address: s.address,
|
|
104
|
+
totalEmails: s.totalEmails,
|
|
105
|
+
unreadEmails: s.unreadEmails,
|
|
106
|
+
totalSizeBytes: s.totalSizeBytes,
|
|
107
|
+
folders: s.folders.map((f) => ({
|
|
108
|
+
name: f.name,
|
|
109
|
+
messages: f.messageCount,
|
|
110
|
+
unseen: f.unseenCount
|
|
111
|
+
}))
|
|
112
|
+
},
|
|
113
|
+
null,
|
|
114
|
+
2
|
|
115
|
+
);
|
|
116
|
+
}
|
|
117
|
+
function formatMailboxes(res) {
|
|
118
|
+
return JSON.stringify(
|
|
119
|
+
{
|
|
120
|
+
total: res.total,
|
|
121
|
+
mailboxes: res.mailboxes.map((m) => ({
|
|
122
|
+
address: m.address,
|
|
123
|
+
displayName: m.displayName,
|
|
124
|
+
emailCount: m.emailCount,
|
|
125
|
+
unreadCount: m.unreadCount
|
|
126
|
+
}))
|
|
127
|
+
},
|
|
128
|
+
null,
|
|
129
|
+
2
|
|
130
|
+
);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// src/instructions.ts
|
|
134
|
+
function buildInstructions(config) {
|
|
135
|
+
const locked = isLocked(config);
|
|
136
|
+
const scope = locked ? `You are operating a single mailbox: ${config.mailbox}. This is YOUR inbox.
|
|
137
|
+
You cannot see or touch any other mailbox. Tools do not take a "mailbox"
|
|
138
|
+
argument \u2014 every action runs against ${config.mailbox} automatically, and
|
|
139
|
+
mail you send goes out from ${config.mailbox}.` : `This server is in multi-mailbox mode. Every tool takes a required
|
|
140
|
+
"mailbox" argument naming the address to act on. Call list_mailboxes first to
|
|
141
|
+
discover which addresses exist before reading or sending.`;
|
|
142
|
+
return `MailCue MCP \u2014 an email mailbox you operate directly.
|
|
143
|
+
|
|
144
|
+
MailCue is a full email server (Postfix + Dovecot + IMAP/SMTP behind a REST
|
|
145
|
+
API). Through this server you can read, search, triage, send, reply to, and
|
|
146
|
+
delete real email, the same way a person uses an inbox client.
|
|
147
|
+
|
|
148
|
+
# Scope
|
|
149
|
+
${scope}
|
|
150
|
+
|
|
151
|
+
# Tools
|
|
152
|
+
- list_emails Browse a folder (default INBOX), newest first. Returns
|
|
153
|
+
summaries with a "uid" for each message.
|
|
154
|
+
- get_email Fetch one full message by uid: bodies, headers, attachment
|
|
155
|
+
list. Read this before replying or acting on a message.
|
|
156
|
+
- search_emails Full-text search across a folder (sender, subject, body).
|
|
157
|
+
- send_email Send a new message. Provide "text" for plain or "html" for
|
|
158
|
+
rich; you may pass both.
|
|
159
|
+
- reply_email Reply to a message by uid. Threading headers (In-Reply-To,
|
|
160
|
+
References), the "Re:" subject, and the recipient are filled
|
|
161
|
+
in for you. Just pass the uid and your reply body.
|
|
162
|
+
- delete_email Permanently delete a message by uid. There is no undo.${locked ? "" : "\n- list_mailboxes Discover the mailboxes you can act on."}
|
|
163
|
+
- mailbox_stats Per-folder counts (total / unread) for a mailbox.
|
|
164
|
+
|
|
165
|
+
# How to work
|
|
166
|
+
1. To answer "what's in my inbox" or "any new mail", call list_emails (or
|
|
167
|
+
mailbox_stats for just counts). uids come from these listings.
|
|
168
|
+
2. uids are scoped to a (mailbox, folder) pair. Use the same folder you listed
|
|
169
|
+
from when calling get_email / reply_email / delete_email.
|
|
170
|
+
3. Always get_email before you reply or delete, so you act on the real content
|
|
171
|
+
rather than the short preview.
|
|
172
|
+
4. Prefer reply_email over send_email when responding to an existing thread \u2014
|
|
173
|
+
it preserves threading so the conversation stays intact.
|
|
174
|
+
5. delete_email is irreversible. Only delete when explicitly asked, or when the
|
|
175
|
+
task clearly calls for it.
|
|
176
|
+
6. Bodies may be truncated in tool output; re-fetch with get_email for the full
|
|
177
|
+
text when you need it.`;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// src/server.ts
|
|
181
|
+
var SERVER_VERSION = "0.1.0";
|
|
182
|
+
var ToolError = class extends Error {
|
|
183
|
+
};
|
|
184
|
+
function text(value) {
|
|
185
|
+
return { content: [{ type: "text", text: value }] };
|
|
186
|
+
}
|
|
187
|
+
function toolError(message) {
|
|
188
|
+
return { content: [{ type: "text", text: message }], isError: true };
|
|
189
|
+
}
|
|
190
|
+
function describeError(err) {
|
|
191
|
+
if (err instanceof ToolError) return err.message;
|
|
192
|
+
if (err instanceof MailcueError) {
|
|
193
|
+
const status = err.status ? ` (HTTP ${err.status})` : "";
|
|
194
|
+
return `MailCue request failed${status}: ${err.message}`;
|
|
195
|
+
}
|
|
196
|
+
if (err instanceof Error) return err.message;
|
|
197
|
+
return String(err);
|
|
198
|
+
}
|
|
199
|
+
var FOLDER = z.string().optional().describe("IMAP folder to act in. Defaults to INBOX.");
|
|
200
|
+
function buildServer(config) {
|
|
201
|
+
const locked = isLocked(config);
|
|
202
|
+
const client = new Mailcue({
|
|
203
|
+
baseUrl: config.baseUrl,
|
|
204
|
+
...config.apiKey ? { apiKey: config.apiKey } : {},
|
|
205
|
+
...config.bearerToken ? { bearerToken: config.bearerToken } : {}
|
|
206
|
+
});
|
|
207
|
+
const server = new McpServer(
|
|
208
|
+
{ name: "mailcue", version: SERVER_VERSION },
|
|
209
|
+
{ instructions: buildInstructions(config) }
|
|
210
|
+
);
|
|
211
|
+
const mailboxArg = locked ? {} : {
|
|
212
|
+
mailbox: z.string().describe("Mailbox address to act on, e.g. user@example.com.")
|
|
213
|
+
};
|
|
214
|
+
const resolveMailbox = (provided) => {
|
|
215
|
+
if (config.mailbox) return config.mailbox;
|
|
216
|
+
const value = provided?.trim();
|
|
217
|
+
if (!value) {
|
|
218
|
+
throw new ToolError(
|
|
219
|
+
'A "mailbox" argument is required. Call list_mailboxes to see available addresses.'
|
|
220
|
+
);
|
|
221
|
+
}
|
|
222
|
+
return value;
|
|
223
|
+
};
|
|
224
|
+
const run = (handler) => {
|
|
225
|
+
return async (args) => {
|
|
226
|
+
try {
|
|
227
|
+
return await handler(args);
|
|
228
|
+
} catch (err) {
|
|
229
|
+
return toolError(describeError(err));
|
|
230
|
+
}
|
|
231
|
+
};
|
|
232
|
+
};
|
|
233
|
+
server.registerTool(
|
|
234
|
+
"list_emails",
|
|
235
|
+
{
|
|
236
|
+
title: "List emails",
|
|
237
|
+
description: "List emails in a mailbox folder, newest first. Returns summaries, each with a uid you can pass to get_email, reply_email, or delete_email.",
|
|
238
|
+
inputSchema: {
|
|
239
|
+
...mailboxArg,
|
|
240
|
+
folder: FOLDER,
|
|
241
|
+
page: z.number().int().min(1).optional().describe("1-based page number. Default 1."),
|
|
242
|
+
pageSize: z.number().int().min(1).max(200).optional().describe("Results per page. Default 50.")
|
|
243
|
+
},
|
|
244
|
+
annotations: { readOnlyHint: true, openWorldHint: true }
|
|
245
|
+
},
|
|
246
|
+
run(async (args) => {
|
|
247
|
+
const a = args;
|
|
248
|
+
const mailbox = resolveMailbox(a.mailbox);
|
|
249
|
+
const folder = a.folder ?? "INBOX";
|
|
250
|
+
const res = await client.emails.list({
|
|
251
|
+
mailbox,
|
|
252
|
+
folder,
|
|
253
|
+
...a.page !== void 0 ? { page: a.page } : {},
|
|
254
|
+
...a.pageSize !== void 0 ? { pageSize: a.pageSize } : {}
|
|
255
|
+
});
|
|
256
|
+
return text(formatList(res, folder));
|
|
257
|
+
})
|
|
258
|
+
);
|
|
259
|
+
server.registerTool(
|
|
260
|
+
"search_emails",
|
|
261
|
+
{
|
|
262
|
+
title: "Search emails",
|
|
263
|
+
description: "Full-text search a mailbox folder by sender, subject, or body content. Returns matching summaries with uids.",
|
|
264
|
+
inputSchema: {
|
|
265
|
+
...mailboxArg,
|
|
266
|
+
query: z.string().min(1).describe("Text to search for."),
|
|
267
|
+
folder: FOLDER,
|
|
268
|
+
page: z.number().int().min(1).optional().describe("1-based page number. Default 1."),
|
|
269
|
+
pageSize: z.number().int().min(1).max(200).optional().describe("Results per page. Default 50.")
|
|
270
|
+
},
|
|
271
|
+
annotations: { readOnlyHint: true, openWorldHint: true }
|
|
272
|
+
},
|
|
273
|
+
run(async (args) => {
|
|
274
|
+
const a = args;
|
|
275
|
+
const mailbox = resolveMailbox(a.mailbox);
|
|
276
|
+
const folder = a.folder ?? "INBOX";
|
|
277
|
+
const res = await client.emails.list({
|
|
278
|
+
mailbox,
|
|
279
|
+
folder,
|
|
280
|
+
search: a.query,
|
|
281
|
+
...a.page !== void 0 ? { page: a.page } : {},
|
|
282
|
+
...a.pageSize !== void 0 ? { pageSize: a.pageSize } : {}
|
|
283
|
+
});
|
|
284
|
+
return text(formatList(res, folder));
|
|
285
|
+
})
|
|
286
|
+
);
|
|
287
|
+
server.registerTool(
|
|
288
|
+
"get_email",
|
|
289
|
+
{
|
|
290
|
+
title: "Get email",
|
|
291
|
+
description: "Fetch one full email by uid: full body, headers, and attachment metadata. Read this before replying to or deleting a message.",
|
|
292
|
+
inputSchema: {
|
|
293
|
+
...mailboxArg,
|
|
294
|
+
uid: z.string().describe("The email uid, as returned by list_emails or search_emails."),
|
|
295
|
+
folder: FOLDER
|
|
296
|
+
},
|
|
297
|
+
annotations: { readOnlyHint: true, openWorldHint: true }
|
|
298
|
+
},
|
|
299
|
+
run(async (args) => {
|
|
300
|
+
const a = args;
|
|
301
|
+
const mailbox = resolveMailbox(a.mailbox);
|
|
302
|
+
const folder = a.folder ?? "INBOX";
|
|
303
|
+
const email = await client.emails.get(a.uid, { mailbox, folder });
|
|
304
|
+
return text(formatEmail(email));
|
|
305
|
+
})
|
|
306
|
+
);
|
|
307
|
+
server.registerTool(
|
|
308
|
+
"send_email",
|
|
309
|
+
{
|
|
310
|
+
title: "Send email",
|
|
311
|
+
description: locked ? `Send a new email from ${config.mailbox}. Provide "text" for plain or "html" for rich (at least one).` : 'Send a new email. Provide "text" for plain or "html" for rich (at least one).',
|
|
312
|
+
inputSchema: {
|
|
313
|
+
...locked ? {} : {
|
|
314
|
+
from: z.string().describe("Sender mailbox address. You must own this mailbox.")
|
|
315
|
+
},
|
|
316
|
+
to: z.array(z.string()).min(1).describe("One or more recipient addresses."),
|
|
317
|
+
cc: z.array(z.string()).optional().describe("Optional CC recipients."),
|
|
318
|
+
subject: z.string().describe("Email subject line."),
|
|
319
|
+
text: z.string().optional().describe("Plain-text body."),
|
|
320
|
+
html: z.string().optional().describe("HTML body."),
|
|
321
|
+
replyTo: z.string().optional().describe("Optional Reply-To address.")
|
|
322
|
+
},
|
|
323
|
+
annotations: { readOnlyHint: false, openWorldHint: true }
|
|
324
|
+
},
|
|
325
|
+
run(async (args) => {
|
|
326
|
+
const a = args;
|
|
327
|
+
if (a.text === void 0 && a.html === void 0) {
|
|
328
|
+
throw new ToolError('Provide at least one of "text" or "html".');
|
|
329
|
+
}
|
|
330
|
+
const from = config.mailbox ?? resolveMailbox(a.from);
|
|
331
|
+
const params = { from, to: a.to, subject: a.subject };
|
|
332
|
+
if (a.cc !== void 0) params.cc = a.cc;
|
|
333
|
+
if (a.text !== void 0) params.text = a.text;
|
|
334
|
+
if (a.html !== void 0) params.html = a.html;
|
|
335
|
+
if (a.replyTo !== void 0) params.replyTo = a.replyTo;
|
|
336
|
+
const res = await client.emails.send(params);
|
|
337
|
+
return text(
|
|
338
|
+
JSON.stringify({ status: "sent", messageId: res.messageId, message: res.message }, null, 2)
|
|
339
|
+
);
|
|
340
|
+
})
|
|
341
|
+
);
|
|
342
|
+
server.registerTool(
|
|
343
|
+
"reply_email",
|
|
344
|
+
{
|
|
345
|
+
title: "Reply to email",
|
|
346
|
+
description: 'Reply to an email by uid. The recipient, the "Re:" subject, and threading headers (In-Reply-To, References) are set for you. Provide "text" or "html".',
|
|
347
|
+
inputSchema: {
|
|
348
|
+
...mailboxArg,
|
|
349
|
+
uid: z.string().describe("uid of the message to reply to."),
|
|
350
|
+
folder: FOLDER,
|
|
351
|
+
text: z.string().optional().describe("Plain-text reply body."),
|
|
352
|
+
html: z.string().optional().describe("HTML reply body.")
|
|
353
|
+
},
|
|
354
|
+
annotations: { readOnlyHint: false, openWorldHint: true }
|
|
355
|
+
},
|
|
356
|
+
run(async (args) => {
|
|
357
|
+
const a = args;
|
|
358
|
+
if (a.text === void 0 && a.html === void 0) {
|
|
359
|
+
throw new ToolError('Provide at least one of "text" or "html".');
|
|
360
|
+
}
|
|
361
|
+
const mailbox = resolveMailbox(a.mailbox);
|
|
362
|
+
const folder = a.folder ?? "INBOX";
|
|
363
|
+
const original = await client.emails.get(a.uid, { mailbox, folder });
|
|
364
|
+
const subject = /^re:/i.test(original.subject) ? original.subject : `Re: ${original.subject}`;
|
|
365
|
+
const params = {
|
|
366
|
+
from: mailbox,
|
|
367
|
+
to: [original.fromAddress],
|
|
368
|
+
subject
|
|
369
|
+
};
|
|
370
|
+
if (original.messageId) {
|
|
371
|
+
params.inReplyTo = original.messageId;
|
|
372
|
+
params.references = [original.messageId];
|
|
373
|
+
}
|
|
374
|
+
if (a.text !== void 0) params.text = a.text;
|
|
375
|
+
if (a.html !== void 0) params.html = a.html;
|
|
376
|
+
const res = await client.emails.send(params);
|
|
377
|
+
return text(
|
|
378
|
+
JSON.stringify(
|
|
379
|
+
{ status: "sent", to: original.fromAddress, subject, messageId: res.messageId },
|
|
380
|
+
null,
|
|
381
|
+
2
|
|
382
|
+
)
|
|
383
|
+
);
|
|
384
|
+
})
|
|
385
|
+
);
|
|
386
|
+
server.registerTool(
|
|
387
|
+
"delete_email",
|
|
388
|
+
{
|
|
389
|
+
title: "Delete email",
|
|
390
|
+
description: "Permanently delete an email by uid. This cannot be undone. Only delete when explicitly asked or clearly required.",
|
|
391
|
+
inputSchema: {
|
|
392
|
+
...mailboxArg,
|
|
393
|
+
uid: z.string().describe("uid of the message to delete."),
|
|
394
|
+
folder: FOLDER
|
|
395
|
+
},
|
|
396
|
+
annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: true }
|
|
397
|
+
},
|
|
398
|
+
run(async (args) => {
|
|
399
|
+
const a = args;
|
|
400
|
+
const mailbox = resolveMailbox(a.mailbox);
|
|
401
|
+
const folder = a.folder ?? "INBOX";
|
|
402
|
+
await client.emails.delete(a.uid, { mailbox, folder });
|
|
403
|
+
return text(JSON.stringify({ status: "deleted", uid: a.uid, mailbox, folder }, null, 2));
|
|
404
|
+
})
|
|
405
|
+
);
|
|
406
|
+
server.registerTool(
|
|
407
|
+
"mailbox_stats",
|
|
408
|
+
{
|
|
409
|
+
title: "Mailbox stats",
|
|
410
|
+
description: "Per-folder message counts (total and unread) for a mailbox.",
|
|
411
|
+
inputSchema: { ...mailboxArg },
|
|
412
|
+
annotations: { readOnlyHint: true, openWorldHint: true }
|
|
413
|
+
},
|
|
414
|
+
run(async (args) => {
|
|
415
|
+
const a = args;
|
|
416
|
+
const mailbox = resolveMailbox(a.mailbox);
|
|
417
|
+
const stats = await client.mailboxes.stats(mailbox);
|
|
418
|
+
return text(formatStats(stats));
|
|
419
|
+
})
|
|
420
|
+
);
|
|
421
|
+
if (!locked) {
|
|
422
|
+
server.registerTool(
|
|
423
|
+
"list_mailboxes",
|
|
424
|
+
{
|
|
425
|
+
title: "List mailboxes",
|
|
426
|
+
description: 'List the mailboxes available on this server, with their addresses and unread counts. Use this to choose a "mailbox" argument for the other tools.',
|
|
427
|
+
inputSchema: {},
|
|
428
|
+
annotations: { readOnlyHint: true, openWorldHint: true }
|
|
429
|
+
},
|
|
430
|
+
run(async () => {
|
|
431
|
+
const res = await client.mailboxes.list();
|
|
432
|
+
return text(formatMailboxes(res));
|
|
433
|
+
})
|
|
434
|
+
);
|
|
435
|
+
}
|
|
436
|
+
return server;
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
// src/index.ts
|
|
440
|
+
function logStderr(message) {
|
|
441
|
+
process.stderr.write(`${message}
|
|
442
|
+
`);
|
|
443
|
+
}
|
|
444
|
+
async function main() {
|
|
445
|
+
let config;
|
|
446
|
+
try {
|
|
447
|
+
config = loadConfig();
|
|
448
|
+
} catch (err) {
|
|
449
|
+
if (err instanceof ConfigError) {
|
|
450
|
+
logStderr(`mailcue-mcp: ${err.message}`);
|
|
451
|
+
process.exit(1);
|
|
452
|
+
}
|
|
453
|
+
throw err;
|
|
454
|
+
}
|
|
455
|
+
const server = buildServer(config);
|
|
456
|
+
const transport = new StdioServerTransport();
|
|
457
|
+
await server.connect(transport);
|
|
458
|
+
const scope = isLocked(config) ? `locked to ${config.mailbox}` : "multi-mailbox";
|
|
459
|
+
logStderr(`mailcue-mcp ready on stdio \u2014 ${config.baseUrl} (${scope})`);
|
|
460
|
+
}
|
|
461
|
+
main().catch((err) => {
|
|
462
|
+
logStderr(`mailcue-mcp: fatal: ${err instanceof Error ? err.message : String(err)}`);
|
|
463
|
+
process.exit(1);
|
|
464
|
+
});
|
|
465
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/config.ts","../src/server.ts","../src/format.ts","../src/instructions.ts"],"sourcesContent":["import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';\n\nimport { ConfigError, isLocked, loadConfig } from './config.js';\nimport { buildServer } from './server.js';\n\nfunction logStderr(message: string): void {\n process.stderr.write(`${message}\\n`);\n}\n\nasync function main(): Promise<void> {\n let config;\n try {\n config = loadConfig();\n } catch (err) {\n if (err instanceof ConfigError) {\n logStderr(`mailcue-mcp: ${err.message}`);\n process.exit(1);\n }\n throw err;\n }\n\n const server = buildServer(config);\n const transport = new StdioServerTransport();\n await server.connect(transport);\n\n const scope = isLocked(config) ? `locked to ${config.mailbox}` : 'multi-mailbox';\n logStderr(`mailcue-mcp ready on stdio — ${config.baseUrl} (${scope})`);\n}\n\nmain().catch((err) => {\n logStderr(`mailcue-mcp: fatal: ${err instanceof Error ? err.message : String(err)}`);\n process.exit(1);\n});\n","/**\n * Configuration is read entirely from the environment so the server can be\n * dropped into any MCP client config block without code changes.\n *\n * MAILCUE_BASE_URL MailCue server URL (default http://localhost:8088)\n * MAILCUE_API_KEY X-API-Key credential (mc_...)\n * MAILCUE_BEARER_TOKEN JWT alternative to MAILCUE_API_KEY\n * MAILCUE_MAILBOX optional. When set, the agent is locked to this one\n * mailbox: every tool operates on it, the `mailbox`\n * argument disappears, and cross-mailbox tools are not\n * registered at all.\n */\n\nconst DEFAULT_BASE_URL = 'http://localhost:8088';\n\nexport interface McpConfig {\n baseUrl: string;\n apiKey?: string;\n bearerToken?: string;\n /** When set, the server runs in single-mailbox (locked) mode. */\n mailbox?: string;\n}\n\nexport class ConfigError extends Error {}\n\nfunction clean(value: string | undefined): string | undefined {\n if (value === undefined) return undefined;\n const trimmed = value.trim();\n return trimmed === '' ? undefined : trimmed;\n}\n\nexport function loadConfig(env: NodeJS.ProcessEnv = process.env): McpConfig {\n const apiKey = clean(env['MAILCUE_API_KEY']);\n const bearerToken = clean(env['MAILCUE_BEARER_TOKEN']);\n\n if (!apiKey && !bearerToken) {\n throw new ConfigError(\n 'Missing credentials: set MAILCUE_API_KEY (preferred) or MAILCUE_BEARER_TOKEN.',\n );\n }\n\n const config: McpConfig = {\n baseUrl: clean(env['MAILCUE_BASE_URL']) ?? DEFAULT_BASE_URL,\n };\n if (apiKey) config.apiKey = apiKey;\n if (bearerToken) config.bearerToken = bearerToken;\n\n const mailbox = clean(env['MAILCUE_MAILBOX']);\n if (mailbox) config.mailbox = mailbox;\n\n return config;\n}\n\nexport function isLocked(config: McpConfig): config is McpConfig & { mailbox: string } {\n return typeof config.mailbox === 'string';\n}\n","import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\nimport { Mailcue, MailcueError, type SendEmailParams } from 'mailcue';\nimport { z, type ZodRawShape } from 'zod';\n\nimport { isLocked, type McpConfig } from './config.js';\nimport {\n formatEmail,\n formatList,\n formatMailboxes,\n formatStats,\n} from './format.js';\nimport { buildInstructions } from './instructions.js';\n\nexport const SERVER_VERSION = '0.1.0';\n\ntype ToolResult = {\n content: { type: 'text'; text: string }[];\n isError?: boolean;\n};\n\n/** A failure we deliberately surface to the model as tool output, not a crash. */\nclass ToolError extends Error {}\n\nfunction text(value: string): ToolResult {\n return { content: [{ type: 'text', text: value }] };\n}\n\nfunction toolError(message: string): ToolResult {\n return { content: [{ type: 'text', text: message }], isError: true };\n}\n\nfunction describeError(err: unknown): string {\n if (err instanceof ToolError) return err.message;\n if (err instanceof MailcueError) {\n const status = err.status ? ` (HTTP ${err.status})` : '';\n return `MailCue request failed${status}: ${err.message}`;\n }\n if (err instanceof Error) return err.message;\n return String(err);\n}\n\nconst FOLDER = z\n .string()\n .optional()\n .describe('IMAP folder to act in. Defaults to INBOX.');\n\nexport function buildServer(config: McpConfig): McpServer {\n const locked = isLocked(config);\n\n const client = new Mailcue({\n baseUrl: config.baseUrl,\n ...(config.apiKey ? { apiKey: config.apiKey } : {}),\n ...(config.bearerToken ? { bearerToken: config.bearerToken } : {}),\n });\n\n const server = new McpServer(\n { name: 'mailcue', version: SERVER_VERSION },\n { instructions: buildInstructions(config) },\n );\n\n // In locked mode the mailbox is fixed and never exposed as an argument; in\n // multi-mailbox mode every tool requires it.\n const mailboxArg: ZodRawShape = locked\n ? {}\n : {\n mailbox: z\n .string()\n .describe('Mailbox address to act on, e.g. user@example.com.'),\n };\n\n const resolveMailbox = (provided?: string): string => {\n if (config.mailbox) return config.mailbox;\n const value = provided?.trim();\n if (!value) {\n throw new ToolError(\n 'A \"mailbox\" argument is required. Call list_mailboxes to see available addresses.',\n );\n }\n return value;\n };\n\n const run = (handler: (args: Record<string, unknown>) => Promise<ToolResult>) => {\n return async (args: Record<string, unknown>): Promise<ToolResult> => {\n try {\n return await handler(args);\n } catch (err) {\n return toolError(describeError(err));\n }\n };\n };\n\n server.registerTool(\n 'list_emails',\n {\n title: 'List emails',\n description:\n 'List emails in a mailbox folder, newest first. Returns summaries, each with a uid you can pass to get_email, reply_email, or delete_email.',\n inputSchema: {\n ...mailboxArg,\n folder: FOLDER,\n page: z.number().int().min(1).optional().describe('1-based page number. Default 1.'),\n pageSize: z\n .number()\n .int()\n .min(1)\n .max(200)\n .optional()\n .describe('Results per page. Default 50.'),\n },\n annotations: { readOnlyHint: true, openWorldHint: true },\n },\n run(async (args) => {\n const a = args as { mailbox?: string; folder?: string; page?: number; pageSize?: number };\n const mailbox = resolveMailbox(a.mailbox);\n const folder = a.folder ?? 'INBOX';\n const res = await client.emails.list({\n mailbox,\n folder,\n ...(a.page !== undefined ? { page: a.page } : {}),\n ...(a.pageSize !== undefined ? { pageSize: a.pageSize } : {}),\n });\n return text(formatList(res, folder));\n }),\n );\n\n server.registerTool(\n 'search_emails',\n {\n title: 'Search emails',\n description:\n 'Full-text search a mailbox folder by sender, subject, or body content. Returns matching summaries with uids.',\n inputSchema: {\n ...mailboxArg,\n query: z.string().min(1).describe('Text to search for.'),\n folder: FOLDER,\n page: z.number().int().min(1).optional().describe('1-based page number. Default 1.'),\n pageSize: z\n .number()\n .int()\n .min(1)\n .max(200)\n .optional()\n .describe('Results per page. Default 50.'),\n },\n annotations: { readOnlyHint: true, openWorldHint: true },\n },\n run(async (args) => {\n const a = args as {\n mailbox?: string;\n query: string;\n folder?: string;\n page?: number;\n pageSize?: number;\n };\n const mailbox = resolveMailbox(a.mailbox);\n const folder = a.folder ?? 'INBOX';\n const res = await client.emails.list({\n mailbox,\n folder,\n search: a.query,\n ...(a.page !== undefined ? { page: a.page } : {}),\n ...(a.pageSize !== undefined ? { pageSize: a.pageSize } : {}),\n });\n return text(formatList(res, folder));\n }),\n );\n\n server.registerTool(\n 'get_email',\n {\n title: 'Get email',\n description:\n 'Fetch one full email by uid: full body, headers, and attachment metadata. Read this before replying to or deleting a message.',\n inputSchema: {\n ...mailboxArg,\n uid: z.string().describe('The email uid, as returned by list_emails or search_emails.'),\n folder: FOLDER,\n },\n annotations: { readOnlyHint: true, openWorldHint: true },\n },\n run(async (args) => {\n const a = args as { mailbox?: string; uid: string; folder?: string };\n const mailbox = resolveMailbox(a.mailbox);\n const folder = a.folder ?? 'INBOX';\n const email = await client.emails.get(a.uid, { mailbox, folder });\n return text(formatEmail(email));\n }),\n );\n\n server.registerTool(\n 'send_email',\n {\n title: 'Send email',\n description: locked\n ? `Send a new email from ${config.mailbox}. Provide \"text\" for plain or \"html\" for rich (at least one).`\n : 'Send a new email. Provide \"text\" for plain or \"html\" for rich (at least one).',\n inputSchema: {\n ...(locked\n ? {}\n : {\n from: z\n .string()\n .describe('Sender mailbox address. You must own this mailbox.'),\n }),\n to: z.array(z.string()).min(1).describe('One or more recipient addresses.'),\n cc: z.array(z.string()).optional().describe('Optional CC recipients.'),\n subject: z.string().describe('Email subject line.'),\n text: z.string().optional().describe('Plain-text body.'),\n html: z.string().optional().describe('HTML body.'),\n replyTo: z.string().optional().describe('Optional Reply-To address.'),\n },\n annotations: { readOnlyHint: false, openWorldHint: true },\n },\n run(async (args) => {\n const a = args as {\n from?: string;\n to: string[];\n cc?: string[];\n subject: string;\n text?: string;\n html?: string;\n replyTo?: string;\n };\n if (a.text === undefined && a.html === undefined) {\n throw new ToolError('Provide at least one of \"text\" or \"html\".');\n }\n const from = config.mailbox ?? resolveMailbox(a.from);\n const params: SendEmailParams = { from, to: a.to, subject: a.subject };\n if (a.cc !== undefined) params.cc = a.cc;\n if (a.text !== undefined) params.text = a.text;\n if (a.html !== undefined) params.html = a.html;\n if (a.replyTo !== undefined) params.replyTo = a.replyTo;\n const res = await client.emails.send(params);\n return text(\n JSON.stringify({ status: 'sent', messageId: res.messageId, message: res.message }, null, 2),\n );\n }),\n );\n\n server.registerTool(\n 'reply_email',\n {\n title: 'Reply to email',\n description:\n 'Reply to an email by uid. The recipient, the \"Re:\" subject, and threading headers (In-Reply-To, References) are set for you. Provide \"text\" or \"html\".',\n inputSchema: {\n ...mailboxArg,\n uid: z.string().describe('uid of the message to reply to.'),\n folder: FOLDER,\n text: z.string().optional().describe('Plain-text reply body.'),\n html: z.string().optional().describe('HTML reply body.'),\n },\n annotations: { readOnlyHint: false, openWorldHint: true },\n },\n run(async (args) => {\n const a = args as {\n mailbox?: string;\n uid: string;\n folder?: string;\n text?: string;\n html?: string;\n };\n if (a.text === undefined && a.html === undefined) {\n throw new ToolError('Provide at least one of \"text\" or \"html\".');\n }\n const mailbox = resolveMailbox(a.mailbox);\n const folder = a.folder ?? 'INBOX';\n const original = await client.emails.get(a.uid, { mailbox, folder });\n\n const subject = /^re:/i.test(original.subject)\n ? original.subject\n : `Re: ${original.subject}`;\n const params: SendEmailParams = {\n from: mailbox,\n to: [original.fromAddress],\n subject,\n };\n if (original.messageId) {\n params.inReplyTo = original.messageId;\n params.references = [original.messageId];\n }\n if (a.text !== undefined) params.text = a.text;\n if (a.html !== undefined) params.html = a.html;\n\n const res = await client.emails.send(params);\n return text(\n JSON.stringify(\n { status: 'sent', to: original.fromAddress, subject, messageId: res.messageId },\n null,\n 2,\n ),\n );\n }),\n );\n\n server.registerTool(\n 'delete_email',\n {\n title: 'Delete email',\n description:\n 'Permanently delete an email by uid. This cannot be undone. Only delete when explicitly asked or clearly required.',\n inputSchema: {\n ...mailboxArg,\n uid: z.string().describe('uid of the message to delete.'),\n folder: FOLDER,\n },\n annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: true },\n },\n run(async (args) => {\n const a = args as { mailbox?: string; uid: string; folder?: string };\n const mailbox = resolveMailbox(a.mailbox);\n const folder = a.folder ?? 'INBOX';\n await client.emails.delete(a.uid, { mailbox, folder });\n return text(JSON.stringify({ status: 'deleted', uid: a.uid, mailbox, folder }, null, 2));\n }),\n );\n\n server.registerTool(\n 'mailbox_stats',\n {\n title: 'Mailbox stats',\n description: 'Per-folder message counts (total and unread) for a mailbox.',\n inputSchema: { ...mailboxArg },\n annotations: { readOnlyHint: true, openWorldHint: true },\n },\n run(async (args) => {\n const a = args as { mailbox?: string };\n const mailbox = resolveMailbox(a.mailbox);\n const stats = await client.mailboxes.stats(mailbox);\n return text(formatStats(stats));\n }),\n );\n\n // Discovery only makes sense — and is only safe — when not locked to one box.\n if (!locked) {\n server.registerTool(\n 'list_mailboxes',\n {\n title: 'List mailboxes',\n description:\n 'List the mailboxes available on this server, with their addresses and unread counts. Use this to choose a \"mailbox\" argument for the other tools.',\n inputSchema: {},\n annotations: { readOnlyHint: true, openWorldHint: true },\n },\n run(async () => {\n const res = await client.mailboxes.list();\n return text(formatMailboxes(res));\n }),\n );\n }\n\n return server;\n}\n","/**\n * Shape MailCue SDK responses into compact, token-efficient text for the model.\n * Tools return readable JSON rather than dumping raw HTML bodies.\n */\n\nimport type {\n EmailDetail,\n EmailListResponse,\n EmailSummary,\n MailboxListResponse,\n MailboxStats,\n} from 'mailcue';\n\nconst MAX_BODY_CHARS = 8000;\n\n/** Best-effort HTML -> text so the model reads prose, not markup. */\nexport function htmlToText(html: string): string {\n return html\n .replace(/<style[\\s\\S]*?<\\/style>/gi, '')\n .replace(/<script[\\s\\S]*?<\\/script>/gi, '')\n .replace(/<\\/(p|div|tr|li|h[1-6])>/gi, '\\n')\n .replace(/<br\\s*\\/?>/gi, '\\n')\n .replace(/<[^>]+>/g, '')\n .replace(/ /g, ' ')\n .replace(/&/g, '&')\n .replace(/</g, '<')\n .replace(/>/g, '>')\n .replace(/"/g, '\"')\n .replace(/'/g, \"'\")\n .replace(/[ \\t]+\\n/g, '\\n')\n .replace(/\\n{3,}/g, '\\n\\n')\n .trim();\n}\n\nfunction truncate(text: string): string {\n if (text.length <= MAX_BODY_CHARS) return text;\n return `${text.slice(0, MAX_BODY_CHARS)}\\n\\n[...truncated ${text.length - MAX_BODY_CHARS} chars. Use get_email with the uid for the full message.]`;\n}\n\nfunction summaryView(e: EmailSummary): Record<string, unknown> {\n return {\n uid: e.uid,\n from: e.fromAddress,\n to: e.toAddresses,\n subject: e.subject,\n date: e.date,\n unread: !e.isRead,\n hasAttachments: e.hasAttachments,\n preview: e.preview,\n };\n}\n\nexport function formatList(res: EmailListResponse, folder: string): string {\n const view = {\n folder,\n total: res.total,\n page: res.page,\n pageSize: res.pageSize,\n hasMore: res.hasMore,\n returned: res.emails.length,\n emails: res.emails.map(summaryView),\n };\n return JSON.stringify(view, null, 2);\n}\n\nexport function formatEmail(e: EmailDetail): string {\n const bestBody = e.textBody ?? (e.htmlBody ? htmlToText(e.htmlBody) : '');\n const view = {\n uid: e.uid,\n mailbox: e.mailbox,\n messageId: e.messageId,\n from: e.fromAddress,\n to: e.toAddresses,\n cc: e.ccAddresses,\n subject: e.subject,\n date: e.date,\n unread: !e.isRead,\n isSigned: e.isSigned,\n isEncrypted: e.isEncrypted,\n attachments: e.attachments.map((a) => ({\n partId: a.partId,\n filename: a.filename,\n contentType: a.contentType,\n size: a.size,\n })),\n body: truncate(bestBody),\n };\n return JSON.stringify(view, null, 2);\n}\n\nexport function formatStats(s: MailboxStats): string {\n return JSON.stringify(\n {\n address: s.address,\n totalEmails: s.totalEmails,\n unreadEmails: s.unreadEmails,\n totalSizeBytes: s.totalSizeBytes,\n folders: s.folders.map((f) => ({\n name: f.name,\n messages: f.messageCount,\n unseen: f.unseenCount,\n })),\n },\n null,\n 2,\n );\n}\n\nexport function formatMailboxes(res: MailboxListResponse): string {\n return JSON.stringify(\n {\n total: res.total,\n mailboxes: res.mailboxes.map((m) => ({\n address: m.address,\n displayName: m.displayName,\n emailCount: m.emailCount,\n unreadCount: m.unreadCount,\n })),\n },\n null,\n 2,\n );\n}\n","/**\n * Server-level MCP `instructions`. The MCP client surfaces this to the model\n * during initialization, so it is the place to teach an agent how to run its\n * own mailbox well. The text adapts to whether a single mailbox is locked.\n */\n\nimport { isLocked, type McpConfig } from './config.js';\n\nexport function buildInstructions(config: McpConfig): string {\n const locked = isLocked(config);\n\n const scope = locked\n ? `You are operating a single mailbox: ${config.mailbox}. This is YOUR inbox.\nYou cannot see or touch any other mailbox. Tools do not take a \"mailbox\"\nargument — every action runs against ${config.mailbox} automatically, and\nmail you send goes out from ${config.mailbox}.`\n : `This server is in multi-mailbox mode. Every tool takes a required\n\"mailbox\" argument naming the address to act on. Call list_mailboxes first to\ndiscover which addresses exist before reading or sending.`;\n\n return `MailCue MCP — an email mailbox you operate directly.\n\nMailCue is a full email server (Postfix + Dovecot + IMAP/SMTP behind a REST\nAPI). Through this server you can read, search, triage, send, reply to, and\ndelete real email, the same way a person uses an inbox client.\n\n# Scope\n${scope}\n\n# Tools\n- list_emails Browse a folder (default INBOX), newest first. Returns\n summaries with a \"uid\" for each message.\n- get_email Fetch one full message by uid: bodies, headers, attachment\n list. Read this before replying or acting on a message.\n- search_emails Full-text search across a folder (sender, subject, body).\n- send_email Send a new message. Provide \"text\" for plain or \"html\" for\n rich; you may pass both.\n- reply_email Reply to a message by uid. Threading headers (In-Reply-To,\n References), the \"Re:\" subject, and the recipient are filled\n in for you. Just pass the uid and your reply body.\n- delete_email Permanently delete a message by uid. There is no undo.${\n locked ? '' : '\\n- list_mailboxes Discover the mailboxes you can act on.'\n }\n- mailbox_stats Per-folder counts (total / unread) for a mailbox.\n\n# How to work\n1. To answer \"what's in my inbox\" or \"any new mail\", call list_emails (or\n mailbox_stats for just counts). uids come from these listings.\n2. uids are scoped to a (mailbox, folder) pair. Use the same folder you listed\n from when calling get_email / reply_email / delete_email.\n3. Always get_email before you reply or delete, so you act on the real content\n rather than the short preview.\n4. Prefer reply_email over send_email when responding to an existing thread —\n it preserves threading so the conversation stays intact.\n5. delete_email is irreversible. Only delete when explicitly asked, or when the\n task clearly calls for it.\n6. Bodies may be truncated in tool output; re-fetch with get_email for the full\n text when you need it.`;\n}\n"],"mappings":"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","names":["text"]}
|
package/package.json
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "mailcue-mcp",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Model Context Protocol server for MailCue — gives an AI agent its own mailbox",
|
|
5
|
+
"author": "Olib AI <hello@olib.ai>",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"type": "module",
|
|
8
|
+
"bin": {
|
|
9
|
+
"mailcue-mcp": "./dist/index.js"
|
|
10
|
+
},
|
|
11
|
+
"main": "./dist/index.js",
|
|
12
|
+
"files": [
|
|
13
|
+
"dist",
|
|
14
|
+
"README.md",
|
|
15
|
+
"LICENSE"
|
|
16
|
+
],
|
|
17
|
+
"engines": {
|
|
18
|
+
"node": ">=18"
|
|
19
|
+
},
|
|
20
|
+
"keywords": [
|
|
21
|
+
"mailcue",
|
|
22
|
+
"mcp",
|
|
23
|
+
"model-context-protocol",
|
|
24
|
+
"email",
|
|
25
|
+
"agent",
|
|
26
|
+
"ai",
|
|
27
|
+
"imap",
|
|
28
|
+
"smtp",
|
|
29
|
+
"olib"
|
|
30
|
+
],
|
|
31
|
+
"repository": {
|
|
32
|
+
"type": "git",
|
|
33
|
+
"url": "git+https://github.com/Olib-AI/mailcue.git",
|
|
34
|
+
"directory": "sdks/mcp-node"
|
|
35
|
+
},
|
|
36
|
+
"homepage": "https://github.com/Olib-AI/mailcue",
|
|
37
|
+
"bugs": {
|
|
38
|
+
"url": "https://github.com/Olib-AI/mailcue/issues"
|
|
39
|
+
},
|
|
40
|
+
"scripts": {
|
|
41
|
+
"build": "tsup",
|
|
42
|
+
"dev": "tsup --watch",
|
|
43
|
+
"start": "node dist/index.js",
|
|
44
|
+
"lint": "eslint .",
|
|
45
|
+
"typecheck": "tsc --noEmit",
|
|
46
|
+
"prepublishOnly": "npm run build && npm run lint && npm run typecheck"
|
|
47
|
+
},
|
|
48
|
+
"dependencies": {
|
|
49
|
+
"@modelcontextprotocol/sdk": "^1.18.0",
|
|
50
|
+
"mailcue": "^0.1.1",
|
|
51
|
+
"zod": "^3.25.0"
|
|
52
|
+
},
|
|
53
|
+
"devDependencies": {
|
|
54
|
+
"@eslint/js": "^9.18.0",
|
|
55
|
+
"@types/node": "^22.10.0",
|
|
56
|
+
"eslint": "^9.18.0",
|
|
57
|
+
"tsup": "^8.3.5",
|
|
58
|
+
"typescript": "^5.7.3",
|
|
59
|
+
"typescript-eslint": "^8.20.0"
|
|
60
|
+
}
|
|
61
|
+
}
|