hypermail-mcp 0.7.6 → 0.7.7
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/README.md +174 -138
- package/dist/cli.js +622 -424
- package/dist/cli.js.map +1 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -3,17 +3,24 @@
|
|
|
3
3
|
A **Model Context Protocol** server that lets an agent operate any of the user's
|
|
4
4
|
inboxes through a single, unified tool surface.
|
|
5
5
|
|
|
6
|
-
> **v0.7.
|
|
7
|
-
>
|
|
8
|
-
>
|
|
9
|
-
> `
|
|
6
|
+
> **v0.7.7** — Env-only configuration. Runtime config now comes from flat
|
|
7
|
+
> `HYPERMAIL_*` environment variables plus selected CLI overrides. Config files
|
|
8
|
+
> and legacy provider env names are no longer read. Hosted Gmail OAuth callbacks
|
|
9
|
+
> are supported via `HYPERMAIL_GMAIL_REDIRECT_URI`; local loopback and manual
|
|
10
|
+
> completion still work.
|
|
11
|
+
>
|
|
12
|
+
> **v0.7.6** — Gmail setup uses OAuth authorization URLs instead of Google's
|
|
13
|
+
> rejected device-code flow for Gmail API scopes. `complete_add_account` accepts
|
|
14
|
+
> a final redirected URL or raw `code`/`state`, and provider credentials use
|
|
15
|
+
> dedicated `HYPERMAIL_GMAIL_*` / `HYPERMAIL_OUTLOOK_*` env vars.
|
|
10
16
|
>
|
|
11
17
|
> **v0.7.5** — Attachments via file path on `send_email`/`draft_email`
|
|
12
18
|
> (`attachments` param). `edit_draft` gains `new_attachments` and
|
|
13
19
|
> `remove_attachments` — `add_attachment_to_draft` is removed (23 tools now).
|
|
14
20
|
> Draft editing uses multi-strategy thread boundary detection for more reliable
|
|
15
|
-
> quoted-thread preservation. Watcher now supports
|
|
16
|
-
> alongside webhook delivery.
|
|
21
|
+
> quoted-thread preservation. Watcher now supports shell-command notification
|
|
22
|
+
> alongside webhook delivery. Published CLI installs the MCP SDK dependency so
|
|
23
|
+
> global/npx runs do not fail on a missing SDK module.
|
|
17
24
|
>
|
|
18
25
|
> **v0.7.4** — `inReplyTo` is now a required parameter on `send_email` and
|
|
19
26
|
> `draft_email` (was optional). Set it to `false` for a new email, or pass a
|
|
@@ -31,7 +38,7 @@ inboxes through a single, unified tool surface.
|
|
|
31
38
|
>
|
|
32
39
|
> **v0.7.0** — Email watch mode: background poll loop detects new inbox
|
|
33
40
|
> messages and POSTs them to a configurable webhook URL (e.g. Mastra). Opt-in —
|
|
34
|
-
> disabled by default, enabled via `HYPERMAIL_WATCH_ENABLED=true
|
|
41
|
+
> disabled by default, enabled via `HYPERMAIL_WATCH_ENABLED=true`.
|
|
35
42
|
> Works in both stdio and HTTP transport modes.
|
|
36
43
|
>
|
|
37
44
|
> **v0.6.3** — Unify stdio and HTTP modes into a single feature set. Removed
|
|
@@ -58,13 +65,14 @@ inboxes through a single, unified tool surface.
|
|
|
58
65
|
> union output schemas that caused `validateToolOutput` crashes.
|
|
59
66
|
|
|
60
67
|
The agent doesn't care whether an address is a work Outlook account, a personal
|
|
61
|
-
Microsoft account,
|
|
68
|
+
Microsoft account, a personal IMAP mailbox, or Gmail — it just calls
|
|
62
69
|
`list_emails`, `search_emails`, `read_email`, `send_email` and passes the email
|
|
63
70
|
address as the `account` argument. The server routes to the right backend.
|
|
64
71
|
|
|
65
72
|
**v1 status:** Outlook / Microsoft 365 (personal + work) fully supported via
|
|
66
73
|
Microsoft Graph. IMAP (any IMAP server) supported via `imapflow` + `nodemailer`.
|
|
67
|
-
Gmail supported via Google OAuth
|
|
74
|
+
Gmail supported via Google OAuth authorization-code flow with local loopback or
|
|
75
|
+
hosted callbacks plus remote-safe manual completion.
|
|
68
76
|
|
|
69
77
|
## Why
|
|
70
78
|
|
|
@@ -110,8 +118,8 @@ hypermail-mcp --http --port 3000 --host 0.0.0.0
|
|
|
110
118
|
# endpoint: http://<host>:3000/mcp (Streamable HTTP transport, session-aware)
|
|
111
119
|
```
|
|
112
120
|
|
|
113
|
-
When hosted
|
|
114
|
-
|
|
121
|
+
When hosted, set `HYPERMAIL_KEY` so the account file is reproducibly
|
|
122
|
+
decryptable across restarts and redeploys.
|
|
115
123
|
|
|
116
124
|
### Docker
|
|
117
125
|
|
|
@@ -120,18 +128,20 @@ reproducibly decryptable.
|
|
|
120
128
|
docker build -t hypermail-mcp .
|
|
121
129
|
|
|
122
130
|
# Run
|
|
131
|
+
# Pass secret values from your shell or deployment environment; do not commit them.
|
|
123
132
|
docker run -d \
|
|
124
133
|
--name hypermail-mcp \
|
|
125
134
|
-p 3000:3000 \
|
|
126
|
-
-e
|
|
127
|
-
-e
|
|
128
|
-
-e
|
|
129
|
-
-v hypermail-data:/
|
|
135
|
+
-e HYPERMAIL_KEY \
|
|
136
|
+
-e HYPERMAIL_OUTLOOK_CLIENT_ID \
|
|
137
|
+
-e HYPERMAIL_OUTLOOK_TENANT_ID \
|
|
138
|
+
-v hypermail-data:/var/lib/mcp \
|
|
130
139
|
hypermail-mcp
|
|
131
140
|
```
|
|
132
141
|
|
|
133
142
|
The image runs the server in HTTP mode on port 3000 with a 30-second
|
|
134
|
-
HEALTHCHECK against `/mcp`. Data is persisted via a Docker volume at
|
|
143
|
+
HEALTHCHECK against `/mcp`. Data is persisted via a Docker volume at
|
|
144
|
+
`/var/lib/mcp`.
|
|
135
145
|
|
|
136
146
|
### Development
|
|
137
147
|
|
|
@@ -141,92 +151,81 @@ To test the HTTP server locally:
|
|
|
141
151
|
# Terminal 1: auto-rebuild TypeScript on save
|
|
142
152
|
pnpm dev
|
|
143
153
|
|
|
144
|
-
# Terminal 2: start HTTP server with
|
|
154
|
+
# Terminal 2: start HTTP server with env/CLI config
|
|
145
155
|
pnpm dev:http
|
|
146
156
|
```
|
|
147
157
|
|
|
148
|
-
The server listens on `http://127.0.0.1:3000/mcp`.
|
|
149
|
-
`.pi/mcp.json` config (read by `pi-mcp-adapter`). Tools appear as
|
|
150
|
-
`hypermail_http_*`.
|
|
158
|
+
The server listens on `http://127.0.0.1:3000/mcp`.
|
|
151
159
|
|
|
152
|
-
##
|
|
160
|
+
## Runtime and provider configuration
|
|
153
161
|
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
| `HYPERMAIL_PROVIDERS_OUTLOOK_TENANT_ID` | Tenant for the authority URL | `common` |
|
|
162
|
+
Hypermail uses flat `HYPERMAIL_*` environment variables as the source of truth.
|
|
163
|
+
There is no runtime config file. CLI flags only override transport, host, port,
|
|
164
|
+
and data directory for a single invocation.
|
|
165
|
+
|
|
166
|
+
CLI flags: `--http`, `--port`, `--host`, `--data-dir`, `--help`.
|
|
160
167
|
|
|
161
|
-
|
|
168
|
+
Subcommands: `hypermail-mcp generate-key` — generate a base64 32-byte key for
|
|
169
|
+
`HYPERMAIL_KEY`.
|
|
162
170
|
|
|
163
|
-
|
|
171
|
+
### Local CLI / env example
|
|
164
172
|
|
|
165
|
-
|
|
173
|
+
```bash
|
|
174
|
+
export HYPERMAIL_KEY="$(hypermail-mcp generate-key)"
|
|
175
|
+
export HYPERMAIL_DATA_DIR="$HOME/.local/share/hypermail-mcp"
|
|
176
|
+
export HYPERMAIL_OUTLOOK_CLIENT_ID="<your-client-id>"
|
|
177
|
+
hypermail-mcp
|
|
178
|
+
```
|
|
166
179
|
|
|
167
|
-
|
|
168
|
-
server with a `hypermail-config.json` file next to the server binary. The server
|
|
169
|
-
looks for it in the same directory as `cli.js`.
|
|
180
|
+
### Generic MCP client JSON example
|
|
170
181
|
|
|
171
182
|
```jsonc
|
|
172
183
|
{
|
|
173
|
-
"
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
"outlook": { "clientId": "...", "tenantId": "..." }
|
|
183
|
-
},
|
|
184
|
-
"watch": {
|
|
185
|
-
"enabled": true,
|
|
186
|
-
"pollIntervalSeconds": 10,
|
|
187
|
-
"webhook": {
|
|
188
|
-
"url": "http://your-agent:3000/api/email-webhook",
|
|
189
|
-
"retry": { "maxAttempts": 5, "baseDelayMs": 1000 }
|
|
184
|
+
"mcpServers": {
|
|
185
|
+
"hypermail": {
|
|
186
|
+
"command": "npx",
|
|
187
|
+
"args": ["-y", "hypermail-mcp"],
|
|
188
|
+
"env": {
|
|
189
|
+
"HYPERMAIL_KEY": "${HYPERMAIL_KEY}",
|
|
190
|
+
"HYPERMAIL_DATA_DIR": "${HYPERMAIL_DATA_DIR}",
|
|
191
|
+
"HYPERMAIL_OUTLOOK_CLIENT_ID": "${HYPERMAIL_OUTLOOK_CLIENT_ID}"
|
|
192
|
+
}
|
|
190
193
|
}
|
|
191
194
|
}
|
|
192
195
|
}
|
|
193
196
|
```
|
|
194
197
|
|
|
195
|
-
|
|
196
|
-
minimal agent-facing surfaces — e.g. a read-only assistant that can only list
|
|
197
|
-
and read emails.
|
|
198
|
-
|
|
199
|
-
## Environment Variables
|
|
198
|
+
### Environment Variables
|
|
200
199
|
|
|
201
|
-
|
|
202
|
-
a dotted-path naming convention (`HYPERMAIL_HTTP_PORT`,
|
|
203
|
-
`HYPERMAIL_PROVIDERS_OUTLOOK_CLIENT_ID`, etc.). Legacy provider env vars such
|
|
204
|
-
as `MS_CLIENT_ID`, `MS_TENANT_ID`, `GOOGLE_CLIENT_ID`, and
|
|
205
|
-
`GOOGLE_CLIENT_SECRET` are ignored; use the `HYPERMAIL_PROVIDERS_*` names
|
|
206
|
-
below.
|
|
207
|
-
|
|
208
|
-
| Env var | Config path | Type |
|
|
200
|
+
| Env var | Purpose | Default / behavior |
|
|
209
201
|
| --- | --- | --- |
|
|
210
|
-
| `
|
|
211
|
-
| `
|
|
212
|
-
| `
|
|
213
|
-
| `
|
|
214
|
-
| `
|
|
215
|
-
| `
|
|
216
|
-
| `
|
|
217
|
-
| `
|
|
218
|
-
| `
|
|
219
|
-
| `
|
|
220
|
-
| `
|
|
221
|
-
| `
|
|
222
|
-
| `
|
|
223
|
-
| `
|
|
224
|
-
| `
|
|
225
|
-
| `
|
|
226
|
-
| `
|
|
227
|
-
| `
|
|
228
|
-
|
|
229
|
-
|
|
202
|
+
| `HYPERMAIL_DATA_DIR` | Account/token store location | `${XDG_DATA_HOME:-~/.local/share}/hypermail-mcp` |
|
|
203
|
+
| `HYPERMAIL_KEY` | 32-byte AES-256-GCM key as hex/base64, or any passphrase derived via SHA-256 | If unset, generates and persists a local key and prints a startup warning |
|
|
204
|
+
| `HYPERMAIL_TRANSPORT` | Runtime transport: `stdio` or `http` | `stdio`; `--http` overrides to `http` |
|
|
205
|
+
| `HYPERMAIL_HTTP_PORT` | HTTP bind port | `3000`; invalid HTTP-mode values warn and fall back |
|
|
206
|
+
| `HYPERMAIL_HTTP_HOST` | HTTP bind host | `127.0.0.1`; invalid HTTP-mode values warn and fall back |
|
|
207
|
+
| `HYPERMAIL_OUTLOOK_CLIENT_ID` | Optional custom Azure/Entra public client ID | Built-in public client |
|
|
208
|
+
| `HYPERMAIL_OUTLOOK_TENANT_ID` | Optional Outlook tenant/authority selector | `common` |
|
|
209
|
+
| `HYPERMAIL_GMAIL_CLIENT_ID` | Google OAuth client ID | Required when adding a Gmail account |
|
|
210
|
+
| `HYPERMAIL_GMAIL_CLIENT_SECRET` | Google OAuth client secret, when issued by the client type | unset |
|
|
211
|
+
| `HYPERMAIL_GMAIL_REDIRECT_URI` | Hosted Gmail OAuth callback URI | Local loopback callback when unset |
|
|
212
|
+
| `HYPERMAIL_TOOLS_ENABLED` | Comma-separated tool allowlist | Empty/unset means no filtering |
|
|
213
|
+
| `HYPERMAIL_TOOLS_DISABLED` | Comma-separated tool blocklist | Empty/unset means no filtering |
|
|
214
|
+
| `HYPERMAIL_WATCH_ENABLED` | Enable inbox polling: `true` or `false` | `false` |
|
|
215
|
+
| `HYPERMAIL_WATCH_POLL_SECONDS` | Watcher polling cadence | `10` |
|
|
216
|
+
| `HYPERMAIL_WATCH_WEBHOOK_URL` | Webhook delivery target | Required if watch is enabled and no notify command is set |
|
|
217
|
+
| `HYPERMAIL_WATCH_WEBHOOK_RETRY_ATTEMPTS` | Webhook retry attempts | `5` |
|
|
218
|
+
| `HYPERMAIL_WATCH_WEBHOOK_RETRY_DELAY_MS` | Webhook exponential-backoff base delay | `1000` |
|
|
219
|
+
| `HYPERMAIL_WATCH_NOTIFY_COMMAND` | Shell command run with `EmailFull` JSON on stdin | Required if watch is enabled and no webhook is set |
|
|
220
|
+
| `HYPERMAIL_WATCH_NOTIFY_TIMEOUT_MS` | Notify-command execution timeout | `30000` |
|
|
221
|
+
| `HYPERMAIL_WATCH_NOTIFY_RETRY_ATTEMPTS` | Notify-command retry attempts | `5` |
|
|
222
|
+
| `HYPERMAIL_WATCH_NOTIFY_RETRY_DELAY_MS` | Notify-command exponential-backoff base delay | `1000` |
|
|
223
|
+
|
|
224
|
+
**Priority order:** selected CLI flags > `HYPERMAIL_*` env vars > hardcoded defaults.
|
|
225
|
+
|
|
226
|
+
Per-tool filtering (`HYPERMAIL_TOOLS_ENABLED` / `HYPERMAIL_TOOLS_DISABLED`) lets
|
|
227
|
+
operators ship minimal agent-facing surfaces. If both non-empty lists are set,
|
|
228
|
+
or either list contains an unknown tool name, startup fails.
|
|
230
229
|
|
|
231
230
|
## Tools
|
|
232
231
|
|
|
@@ -237,65 +236,48 @@ account store.
|
|
|
237
236
|
| Tool | Inputs | Notes |
|
|
238
237
|
| --- | --- | --- |
|
|
239
238
|
| `list_accounts` | — | Returns registered emails + provider, no secrets. |
|
|
240
|
-
| `add_account` | `provider`, `email?`, `config?` | Starts device
|
|
241
|
-
| `complete_add_account` | `provider`, `handle` | Returns `pending` / `ready` / `expired` / `error`. |
|
|
239
|
+
| `add_account` | `provider`, `email?`, `config?` | Starts the provider add flow. Outlook returns a device code; Gmail returns an OAuth URL. Returns `{handle, verification:{type, userCode, verificationUri, expiresAt, message}}`. |
|
|
240
|
+
| `complete_add_account` | `provider`, `handle`, `authorizationResponse?`, `code?`, `state?` | Returns `pending` / `ready` / `expired` / `error`. Gmail accepts a pasted final redirected URL or raw code/state for remote-safe completion. |
|
|
242
241
|
| `get_account_settings` | `account` | Get signature (HTML) and style preferences for an account. |
|
|
243
|
-
| `set_account_settings` | `account`, `signature?`, `signaturePath?`, `style?` | Set signature HTML (inline or via file path) and font preferences.
|
|
242
|
+
| `set_account_settings` | `account`, `signature?`, `signaturePath?`, `style?` | Set signature HTML (inline or via file path) and font preferences. |
|
|
244
243
|
| `remove_account` | `email` | Deletes tokens for the account. |
|
|
245
244
|
| `list_emails` | `account`, `folder?`, `limit?`, `unreadOnly?`, `skip?` | Defaults: folder=`inbox`, limit=25. Supports pagination via `skip` — response includes `hasMore`. |
|
|
246
245
|
| `search_emails` | `account`, `query`, `limit?` | KQL on Outlook. |
|
|
247
246
|
| `read_email` | `account`, `id`, `format?` | Returns full body + recipients + attachment metadata. `format`: `markdown` (default), `html`, or `text`. |
|
|
248
247
|
| `read_attachment` | `account`, `messageId`, `attachmentId` | Download an attachment to a temporary file and return its path. |
|
|
249
|
-
| `archive_email` | `account`, `id` | Move a message to the Archive folder.
|
|
250
|
-
| `trash_email` | `account`, `id` | Move a message to Deleted Items (trash).
|
|
251
|
-
| `move_email` | `account`, `id`, `destination` | Move to any folder by well-known name (`inbox`, `drafts`, etc.) or custom folder ID.
|
|
252
|
-
| `send_email` | `account`, `to[]`, `cc?`, `bcc?`, `subject`, `body`, `format`, `include_signature`, `inReplyTo`, `replyAll?`, `forwardMessageId?`, `attachments?` | Send an email. `format` (`"html"` or `"markdown"`) controls body format — Markdown is converted to HTML via `marked`. Appends signature when `include_signature` is true. `inReplyTo` sends as threaded reply; `forwardMessageId` sends as forward. `inReplyTo` is required — set to `false` for new emails. `attachments` is an optional array of `{filePath, name?}` — files are read from disk and encoded automatically.
|
|
253
|
-
| `draft_email` | `account`, `to[]`, `cc?`, `bcc?`, `subject`, `body`, `format`, `include_signature`, `inReplyTo`, `replyAll?`, `forwardMessageId?`, `attachments?` | Save as draft instead of sending. Same params as `send_email` including `attachments`. Returns the draft message ID and HTML body (`draftHtml`). `inReplyTo` is required — set to `false` for new emails.
|
|
254
|
-
| `edit_draft` | `account`, `id`, `to?`, `cc?`, `bcc?`, `subject?`, `body?`, `format?`, `include_signature?`, `new_attachments?`, `remove_attachments?` | Edit an existing draft by ID. Only provided fields are updated. `new_attachments` adds files (`{filePath, name?}[]`); `remove_attachments` removes by attachment ID (`string[]`). Returns the updated draft ID, HTML body (`draftHtml`), and attachment metadata.
|
|
255
|
-
| `send_draft` | `account`, `id` | Send an existing draft email by ID. Use with draft IDs returned by `draft_email` or `edit_draft`.
|
|
248
|
+
| `archive_email` | `account`, `id` | Move a message to the Archive folder. |
|
|
249
|
+
| `trash_email` | `account`, `id` | Move a message to Deleted Items (trash). |
|
|
250
|
+
| `move_email` | `account`, `id`, `destination` | Move to any folder by well-known name (`inbox`, `drafts`, etc.) or custom folder ID. |
|
|
251
|
+
| `send_email` | `account`, `to[]`, `cc?`, `bcc?`, `subject`, `body`, `format`, `include_signature`, `inReplyTo`, `replyAll?`, `forwardMessageId?`, `attachments?` | Send an email. `format` (`"html"` or `"markdown"`) controls body format — Markdown is converted to HTML via `marked`. Appends signature when `include_signature` is true. `inReplyTo` sends as threaded reply; `forwardMessageId` sends as forward. `inReplyTo` is required — set to `false` for new emails. `attachments` is an optional array of `{filePath, name?}` — files are read from disk and encoded automatically. |
|
|
252
|
+
| `draft_email` | `account`, `to[]`, `cc?`, `bcc?`, `subject`, `body`, `format`, `include_signature`, `inReplyTo`, `replyAll?`, `forwardMessageId?`, `attachments?` | Save as draft instead of sending. Same params as `send_email` including `attachments`. Returns the draft message ID and HTML body (`draftHtml`). `inReplyTo` is required — set to `false` for new emails. |
|
|
253
|
+
| `edit_draft` | `account`, `id`, `to?`, `cc?`, `bcc?`, `subject?`, `body?`, `format?`, `include_signature?`, `new_attachments?`, `remove_attachments?` | Edit an existing draft by ID. Only provided fields are updated. `new_attachments` adds files (`{filePath, name?}[]`); `remove_attachments` removes by attachment ID (`string[]`). Returns the updated draft ID, HTML body (`draftHtml`), and attachment metadata. |
|
|
254
|
+
| `send_draft` | `account`, `id` | Send an existing draft email by ID. Use with draft IDs returned by `draft_email` or `edit_draft`. |
|
|
256
255
|
| `list_folders` | `account`, `parentFolderId?` | List available mail folders. Returns top-level folders by default, or children of `parentFolderId`. |
|
|
257
|
-
| `create_folder` | `account`, `displayName`, `parentFolderId?` | Create a new mail folder under root (default) or the given parent.
|
|
258
|
-
| `delete_folder` | `account`, `folderId` | Delete a mail folder by ID.
|
|
259
|
-
| `rename_folder` | `account`, `folderId`, `newName` | Rename an existing mail folder.
|
|
260
|
-
| `mark_read` | `account`, `id` | Mark a message as read.
|
|
261
|
-
| `mark_unread` | `account`, `id` | Mark a message as unread.
|
|
256
|
+
| `create_folder` | `account`, `displayName`, `parentFolderId?` | Create a new mail folder under root (default) or the given parent. |
|
|
257
|
+
| `delete_folder` | `account`, `folderId` | Delete a mail folder by ID. |
|
|
258
|
+
| `rename_folder` | `account`, `folderId`, `newName` | Rename an existing mail folder. |
|
|
259
|
+
| `mark_read` | `account`, `id` | Mark a message as read. |
|
|
260
|
+
| `mark_unread` | `account`, `id` | Mark a message as unread. |
|
|
262
261
|
|
|
263
262
|
## Email Watch
|
|
264
263
|
|
|
265
264
|
When enabled, hypermail-mcp runs a background poll loop that scans inboxes for
|
|
266
|
-
new messages and delivers each one via webhook POST and/or
|
|
267
|
-
Intended for push-based email triage — downstream agents
|
|
268
|
-
|
|
265
|
+
new messages and delivers each one via webhook POST and/or shell command.
|
|
266
|
+
Intended for push-based email triage — downstream agents receive full email
|
|
267
|
+
content without polling.
|
|
269
268
|
|
|
270
|
-
```
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
"url": "http://localhost:3000/api/email-webhook",
|
|
277
|
-
"retry": { "maxAttempts": 5, "baseDelayMs": 1000 }
|
|
278
|
-
},
|
|
279
|
-
"script": {
|
|
280
|
-
"command": "node /path/to/email-handler.js",
|
|
281
|
-
"timeoutMs": 30000,
|
|
282
|
-
"retry": { "maxAttempts": 3, "baseDelayMs": 1000 }
|
|
283
|
-
}
|
|
284
|
-
}
|
|
285
|
-
}
|
|
269
|
+
```bash
|
|
270
|
+
HYPERMAIL_WATCH_ENABLED=true \
|
|
271
|
+
HYPERMAIL_WATCH_POLL_SECONDS=10 \
|
|
272
|
+
HYPERMAIL_WATCH_WEBHOOK_URL=http://localhost:3000/api/email-webhook \
|
|
273
|
+
HYPERMAIL_WATCH_NOTIFY_COMMAND='node /path/to/email-handler.js' \
|
|
274
|
+
hypermail-mcp
|
|
286
275
|
```
|
|
287
276
|
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
| `watch.webhook.url` | — | Endpoint that receives `POST` with `EmailFull` JSON |
|
|
293
|
-
| `watch.webhook.retry.maxAttempts` | `5` | Max delivery attempts (1–10) |
|
|
294
|
-
| `watch.webhook.retry.baseDelayMs` | `1000` | Base backoff delay (× 2^attempt) |
|
|
295
|
-
| `watch.script.command` | — | Shell command spawned with email JSON on stdin |
|
|
296
|
-
| `watch.script.timeoutMs` | `30000` | Max script runtime before SIGKILL |
|
|
297
|
-
| `watch.script.retry.maxAttempts` | `3` | Max script delivery attempts |
|
|
298
|
-
| `watch.script.retry.baseDelayMs` | `1000` | Base backoff delay (× 2^attempt) |
|
|
277
|
+
**Validation:** If `HYPERMAIL_WATCH_ENABLED=true`, startup requires at least one
|
|
278
|
+
of `HYPERMAIL_WATCH_WEBHOOK_URL` or `HYPERMAIL_WATCH_NOTIFY_COMMAND`. Webhook
|
|
279
|
+
URLs are syntax-validated, and notify commands must be non-empty. Startup does
|
|
280
|
+
not test network reachability or execute the command.
|
|
299
281
|
|
|
300
282
|
**Behavior:**
|
|
301
283
|
- Polls **all accounts** in the store, **inbox only**.
|
|
@@ -303,21 +285,22 @@ full email content without polling.
|
|
|
303
285
|
account file — no duplicate emits across restarts.
|
|
304
286
|
- Two delivery modes (can be used together):
|
|
305
287
|
- **Webhook:** One `POST` per email (full body as `EmailFull` JSON).
|
|
306
|
-
- **
|
|
307
|
-
|
|
308
|
-
`body`, `isRead`, `sentDateTime`, `attachments`, plus an `account` field.
|
|
288
|
+
- **Notify command:** Executes `HYPERMAIL_WATCH_NOTIFY_COMMAND` through the
|
|
289
|
+
platform shell with the `EmailFull` JSON piped to stdin.
|
|
309
290
|
- Both modes use exponential backoff (`baseDelay × 2^attempt`). Retries on
|
|
310
|
-
failures (non-2xx for webhook, non-zero exit for
|
|
291
|
+
failures (non-2xx for webhook, non-zero exit for command). Logs and moves on
|
|
311
292
|
after `maxAttempts` exhausted — never blocks the poll loop.
|
|
312
|
-
-
|
|
313
|
-
|
|
293
|
+
- Command delivery is fire-and-forget — the poll loop continues while delivery
|
|
294
|
+
runs in the background.
|
|
314
295
|
- Works in both **stdio** and **HTTP** transport modes — the poll interval
|
|
315
296
|
fires normally alongside MCP message handling.
|
|
316
297
|
|
|
317
298
|
**Rate limits:** Polling every 10s on a single inbox = 6 req/min = 0.6% of
|
|
318
299
|
Microsoft Graph's 10,000 req/10min per-user limit. Safe for personal inboxes.
|
|
319
300
|
|
|
320
|
-
## Add-account
|
|
301
|
+
## Add-account flows
|
|
302
|
+
|
|
303
|
+
### Outlook
|
|
321
304
|
|
|
322
305
|
1. Agent calls `add_account({ provider: "outlook" })`.
|
|
323
306
|
2. Server returns:
|
|
@@ -326,6 +309,7 @@ Microsoft Graph's 10,000 req/10min per-user limit. Safe for personal inboxes.
|
|
|
326
309
|
"status": "pending",
|
|
327
310
|
"handle": "…uuid…",
|
|
328
311
|
"verification": {
|
|
312
|
+
"type": "device_code",
|
|
329
313
|
"userCode": "ABCD-EFGH",
|
|
330
314
|
"verificationUri": "https://microsoft.com/devicelogin",
|
|
331
315
|
"expiresAt": "2025-…",
|
|
@@ -338,6 +322,57 @@ Microsoft Graph's 10,000 req/10min per-user limit. Safe for personal inboxes.
|
|
|
338
322
|
it returns `{ "status": "ready", "account": {...} }`.
|
|
339
323
|
5. From then on, any tool can be called with `account: "<that-email>"`.
|
|
340
324
|
|
|
325
|
+
### Gmail
|
|
326
|
+
|
|
327
|
+
Gmail uses Google OAuth 2.0, matching the official Gmail MCP model. Google's
|
|
328
|
+
device-code endpoint rejects Gmail API scopes, so Hypermail uses an authorization
|
|
329
|
+
URL with a real callback. Service accounts are only suitable for Google
|
|
330
|
+
Workspace domain-wide delegation; they don't grant server-to-server access to
|
|
331
|
+
consumer `@gmail.com` inboxes.
|
|
332
|
+
|
|
333
|
+
For local stdio/Desktop OAuth clients, Hypermail starts a temporary
|
|
334
|
+
`127.0.0.1` loopback callback server automatically. For hosted HTTP deployments,
|
|
335
|
+
set `HYPERMAIL_GMAIL_REDIRECT_URI` and register the exact URI in Google Auth
|
|
336
|
+
Platform, for example:
|
|
337
|
+
|
|
338
|
+
```bash
|
|
339
|
+
HYPERMAIL_TRANSPORT=http
|
|
340
|
+
HYPERMAIL_GMAIL_REDIRECT_URI=https://mail.example.com/oauth/gmail/callback
|
|
341
|
+
```
|
|
342
|
+
|
|
343
|
+
1. Configure `HYPERMAIL_GMAIL_CLIENT_ID` and, when issued by your Google client
|
|
344
|
+
type, `HYPERMAIL_GMAIL_CLIENT_SECRET`. Use a Desktop client for local
|
|
345
|
+
loopback, or a Web client for hosted HTTP callbacks.
|
|
346
|
+
2. Agent calls `add_account({ provider: "gmail" })`.
|
|
347
|
+
3. Server returns an OAuth URL:
|
|
348
|
+
```json
|
|
349
|
+
{
|
|
350
|
+
"status": "pending",
|
|
351
|
+
"handle": "…uuid…",
|
|
352
|
+
"verification": {
|
|
353
|
+
"type": "oauth_url",
|
|
354
|
+
"userCode": "",
|
|
355
|
+
"verificationUri": "https://accounts.google.com/o/oauth2/v2/auth?...",
|
|
356
|
+
"expiresAt": "2025-…",
|
|
357
|
+
"message": "Open this URL in a browser to authorize Gmail access..."
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
```
|
|
361
|
+
4. The user opens `verificationUri` and grants access. If the configured
|
|
362
|
+
callback is reachable, the browser shows a small success page and the agent
|
|
363
|
+
can poll `complete_add_account({ provider: "gmail", handle })` until ready.
|
|
364
|
+
5. If the browser cannot reach the callback, the manual fallback still works:
|
|
365
|
+
copy the final redirected URL from the browser address bar and call:
|
|
366
|
+
```json
|
|
367
|
+
{
|
|
368
|
+
"provider": "gmail",
|
|
369
|
+
"handle": "…uuid…",
|
|
370
|
+
"authorizationResponse": "http://127.0.0.1:54321/oauth2callback?code=...&state=..."
|
|
371
|
+
}
|
|
372
|
+
```
|
|
373
|
+
6. `complete_add_account` validates state, exchanges the code for tokens, stores
|
|
374
|
+
the account, and returns `{ "status": "ready", "account": {...} }`.
|
|
375
|
+
|
|
341
376
|
## Roadmap
|
|
342
377
|
|
|
343
378
|
- Threading / conversations.
|
|
@@ -350,7 +385,7 @@ src/
|
|
|
350
385
|
cli.ts # arg parsing + entry
|
|
351
386
|
server.ts # MCP server, stdio + HTTP transports, session management
|
|
352
387
|
version.ts # version constant
|
|
353
|
-
config.ts #
|
|
388
|
+
config.ts # env-only config types + resolution
|
|
354
389
|
store/
|
|
355
390
|
account-store.ts # encrypted multi-account store (AES-256-GCM)
|
|
356
391
|
crypto.ts # AES-256-GCM encrypt/decrypt, key resolution, atomic writes
|
|
@@ -363,19 +398,20 @@ src/
|
|
|
363
398
|
index.ts # OutlookProvider implementation
|
|
364
399
|
imap/index.ts # IMAP provider (imapflow + nodemailer)
|
|
365
400
|
gmail/
|
|
366
|
-
auth.ts # Google OAuth
|
|
401
|
+
auth.ts # Google OAuth authorization-code flow
|
|
367
402
|
client.ts # Gmail API (googleapis)
|
|
368
403
|
index.ts # GmailProvider implementation
|
|
369
404
|
shared/ # shared utilities across providers
|
|
370
405
|
watcher/
|
|
371
406
|
manager.ts # WatcherManager — inbox poll loop + dedup
|
|
372
407
|
webhook.ts # HTTP POST with exponential backoff retry
|
|
408
|
+
script.ts # shell-command delivery with retry/timeout
|
|
373
409
|
index.ts # barrel export
|
|
374
410
|
tools/
|
|
375
411
|
index.ts # MCP tool registrations
|
|
376
412
|
accounts.ts # list/add/remove/complete-add account tools
|
|
377
413
|
browse.ts # list/search/read email tools
|
|
378
|
-
compose.ts # send/draft/edit/send-draft
|
|
414
|
+
compose.ts # send/draft/edit/send-draft tools
|
|
379
415
|
folders.ts # list/create/delete/rename folder tools
|
|
380
416
|
organize.ts # archive/trash/move/mark-read/mark-unread tools
|
|
381
417
|
shared.ts # shared tool helpers
|