hypermail-mcp 0.5.0 → 0.6.1

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 CHANGED
@@ -3,6 +3,14 @@
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.6.1** — Docker deployment (standalone Dockerfile with HEALTHCHECK),
7
+ > email notification bug fixes (ID-based dedup, pagination cap, dynamic
8
+ > re-scan), Node 22 base image, dropped docker-compose.
9
+ >
10
+ > **v0.6.0** — Email watch notifications (polling-based), `signaturePath`
11
+ > support in `set_account_settings` for loading signatures from files,
12
+ > and a `check_notifications` tool for draining pending alerts.
13
+ >
6
14
  > **v0.5.0** — Replaced optional `isHtml` boolean with required `format`
7
15
  > parameter (`"html"` | `"markdown"`) on `send_email`, `draft_email`, and
8
16
  > `edit_draft`. Markdown bodies are converted to HTML via `marked` so
@@ -68,16 +76,152 @@ hypermail-mcp --http --port 3000 --host 0.0.0.0
68
76
  When hosted you **must** set `HYPERMAIL_MCP_KEY` so the account file is
69
77
  reproducibly decryptable.
70
78
 
79
+ ### Docker
80
+
81
+ ```bash
82
+ # Build
83
+ docker build -t hypermail-mcp .
84
+
85
+ # Run
86
+ docker run -d \
87
+ --name hypermail-mcp \
88
+ -p 3000:3000 \
89
+ -e HYPERMAIL_MCP_KEY=<32-byte-key> \
90
+ -e MS_CLIENT_ID=<your-client-id> \
91
+ -e MS_TENANT_ID=<your-tenant-id> \
92
+ -v hypermail-data:/data \
93
+ hypermail-mcp
94
+ ```
95
+
96
+ The image runs the server in HTTP mode on port 3000 with a 30-second
97
+ HEALTHCHECK against `/mcp`. Data is persisted via a Docker volume at `/data`.
98
+ Pass `HYPERMAIL_AGENTS_CONFIG` and mount a config file for agent multi-tenancy.
99
+
100
+ ### Development (HTTP mode + email watch)
101
+
102
+ To test the email watch feature locally:
103
+
104
+ ```bash
105
+ # Terminal 1: auto-rebuild TypeScript on save
106
+ pnpm dev
107
+
108
+ # Terminal 2: start HTTP server with dev config (10s poll, separate data dir)
109
+ pnpm dev:http
110
+ ```
111
+
112
+ The server listens on `http://127.0.0.1:3000/mcp`. Pi connects via the
113
+ `.pi/mcp.json` config (read by `pi-mcp-adapter`). Tools appear as
114
+ `hypermail_http_*` (e.g. `hypermail_http_check_notifications`).
115
+
116
+ The dev config (`hypermail-config.http.json`) uses a separate data dir
117
+ (`~/.hypermail-mcp-dev`) and a 10-second poll interval for fast feedback.
118
+
119
+ ## Modes: stdio vs HTTP
120
+
121
+ The server runs in one of two modes — the choice affects session management,
122
+ security, and which features are available.
123
+
124
+ | | stdio (default) | HTTP (`--http`) |
125
+ | --- | --- | --- |
126
+ | **Transport** | stdin/stdout | HTTP (Streamable HTTP MCP) |
127
+ | **Lifecycle** | Per-invocation (lazy) — spawned on demand by the MCP host | Long-lived server process |
128
+ | **Session model** | One `McpServer` instance for all invocations | One `McpServer` per MCP session (multi-tenant) |
129
+ | **Key management** | Auto-generated, stored in OS keychain or `master.key` file | Requires `HYPERMAIL_MCP_KEY` env var (32-byte key for AES-256-GCM) |
130
+ | **Email watch** | ❌ Not available | ✅ Polls inbox every N seconds for new mail |
131
+ | **`check_notifications`** | ❌ Not registered | ✅ Drains pending new-mail alerts |
132
+ | **Agent multi-tenancy** | ❌ Unrestricted access | ✅ Per-agent API keys, account allowlists, provisioning control (via `agents.yaml`) |
133
+ | **Pi tool naming** | `hyper_*` | `hypermail_http_*` |
134
+
135
+ **When to use HTTP mode:**
136
+ - You need email watch / push notifications
137
+ - You want to expose the server to multiple agents with different permissions
138
+ - You're hosting the server as a service (Docker, cloud)
139
+
140
+ **When to use stdio mode:**
141
+ - Single-user local development with a desktop MCP client (Claude, Pi)
142
+ - You don't need email watch or multi-agent access control
143
+
144
+ ## Agent multi-tenancy
145
+
146
+ In HTTP mode, the server can be shared across multiple agents with
147
+ different permissions. Agent identity and authorization are defined in an
148
+ `agents.yaml` file.
149
+
150
+ ### agents.yaml
151
+
152
+ ```yaml
153
+ agents:
154
+ - id: my-assistant
155
+ api_key: hm_sk_<64-hex-chars>
156
+ name: My Email Assistant
157
+ accounts: # which email addresses this agent can access
158
+ - alice@example.com
159
+ - bob@example.com
160
+ provisioning: false # can this agent add/remove accounts?
161
+
162
+ - id: admin-agent
163
+ api_key: hm_sk_<64-hex-chars>
164
+ name: Admin Agent
165
+ accounts: [] # empty = all accounts
166
+ provisioning: true
167
+
168
+ # Optional: pre-declare email accounts with provider hints
169
+ email_accounts:
170
+ alice@example.com:
171
+ provider: outlook
172
+ ```
173
+
174
+ **Agent ID:** lowercase letters, digits, hyphens, underscores. No spaces.
175
+
176
+ **API key format:** `hm_sk_` prefix + 64 hex characters. Generate with:
177
+
178
+ ```bash
179
+ hypermail-mcp generate-key
180
+ # => hm_sk_a1b2c3d4...
181
+ ```
182
+
183
+ The API key is hashed (SHA-256) before storage — the plaintext is never
184
+ written to disk. Agents authenticate by passing the key in the
185
+ `Authorization: Bearer hm_sk_...` header.
186
+
187
+ **accounts:** An allowlist of email addresses the agent can operate on.
188
+ If empty or omitted, the agent can access all configured accounts.
189
+
190
+ **provisioning:** When `true`, the agent can call `add_account` and
191
+ `remove_account`. Defaults to `false`.
192
+
193
+ ### Configuration
194
+
195
+ Point the server at your agents.yaml:
196
+
197
+ ```bash
198
+ # Via CLI flag
199
+ hypermail-mcp --http --agents-config ./agents.yaml
200
+
201
+ # Via env var
202
+ export HYPERMAIL_AGENTS_CONFIG=/etc/hypermail/agents.yaml
203
+ ```
204
+
205
+ The server watches `agents.yaml` for changes and reloads automatically
206
+ (live reload — no restart needed). Agents removed from the file lose
207
+ access on their next request.
208
+
209
+ In **stdio mode**, agent multi-tenancy is not available — the server runs
210
+ with unrestricted access (the local user _is_ the agent).
211
+
71
212
  ## Configuration
72
213
 
73
214
  | Env var | Purpose | Default |
74
215
  | --- | --- | --- |
75
216
  | `HYPERMAIL_MCP_DATA_DIR` | Where to keep the encrypted accounts blob | `~/.hypermail-mcp` |
76
217
  | `HYPERMAIL_MCP_KEY` | 32-byte AES-256-GCM key (hex, base64, or any passphrase — derived via SHA-256). Required for hosted deployments. | auto-generated, stored via OS keychain (`keytar`) or a local `master.key` file |
218
+ | `HYPERMAIL_AGENTS_CONFIG` | Path to `agents.yaml` for HTTP multi-tenant mode (see Agent multi-tenancy above). | — (multi-tenancy disabled) |
77
219
  | `MS_CLIENT_ID` | Azure Entra public client (application) id used for device-code login | placeholder — **set your own for production** |
78
220
  | `MS_TENANT_ID` | Tenant for the authority URL | `common` |
79
221
 
80
- CLI flags: `--http`, `--port`, `--host`, `--data-dir`, `--read-only`, `--help`.
222
+ CLI flags: `--http`, `--port`, `--host`, `--data-dir`, `--agents-config`, `--read-only`, `--help`.
223
+
224
+ Subcommands: `hypermail-mcp generate-key` — generate an `hm_sk_` API key for agents.yaml.
81
225
 
82
226
  ### Config file (`hypermail-config.json`)
83
227
 
@@ -117,7 +261,7 @@ account store.
117
261
  | `add_account` | `provider`, `email?`, `config?` | Starts device-code (Outlook). Returns `{handle, verification:{userCode, verificationUri, expiresAt}}`. |
118
262
  | `complete_add_account` | `provider`, `handle` | Returns `pending` / `ready` / `expired` / `error`. |
119
263
  | `get_account_settings` | `account` | Get signature (HTML) and style preferences for an account. |
120
- | `set_account_settings` | `account`, `signature?`, `style?` | Set signature HTML and font preferences. Disabled under `--read-only`. |
264
+ | `set_account_settings` | `account`, `signature?`, `signaturePath?`, `style?` | Set signature HTML (inline or via file path) and font preferences. Disabled under `--read-only`. |
121
265
  | `remove_account` | `email` | Deletes tokens for the account. |
122
266
  | `list_emails` | `account`, `folder?`, `limit?`, `unreadOnly?`, `skip?` | Defaults: folder=`inbox`, limit=25. Supports pagination via `skip` — response includes `hasMore`. |
123
267
  | `search_emails` | `account`, `query`, `limit?` | KQL on Outlook. |
@@ -130,13 +274,50 @@ account store.
130
274
  | `draft_email` | `account`, `to[]`, `cc?`, `bcc?`, `subject`, `body`, `format`, `include_signature?`, `inReplyTo?`, `replyAll?`, `forwardMessageId?` | Save as draft instead of sending. `format` (`"html"` or `"markdown"`) controls body format — Markdown is converted to HTML via `marked`. Returns the draft message ID and HTML body (`draftHtml`). Disabled under `--read-only`. |
131
275
  | `edit_draft` | `account`, `id`, `to?`, `cc?`, `bcc?`, `subject?`, `body?`, `format?`, `include_signature?` | Edit an existing draft by ID. Only provided fields are updated. `format` only meaningful when `body` is provided. Returns the updated draft ID and HTML body (`draftHtml`). Disabled under `--read-only`. |
132
276
  | `send_draft` | `account`, `id` | Send an existing draft email by ID. Use with draft IDs returned by `draft_email` or `edit_draft`. Disabled under `--read-only`. |
133
- | `add_attachment_to_draft` | `account`, `id`, `path` | Attach a local file to an existing draft email. Disabled under `--read-only`. |
277
+ | `add_attachment_to_draft` | `account`, `id`, `name`, `contentBytes`, `contentType?` | Attach a base64-encoded file to an existing draft email by ID. Disabled under `--read-only`. |
134
278
  | `list_folders` | `account`, `parentFolderId?` | List available mail folders. Returns top-level folders by default, or children of `parentFolderId`. |
135
279
  | `create_folder` | `account`, `displayName`, `parentFolderId?` | Create a new mail folder under root (default) or the given parent. Disabled under `--read-only`. |
136
280
  | `delete_folder` | `account`, `folderId` | Delete a mail folder by ID. Disabled under `--read-only`. |
137
281
  | `rename_folder` | `account`, `folderId`, `newName` | Rename an existing mail folder. Disabled under `--read-only`. |
138
282
  | `mark_read` | `account`, `id` | Mark a message as read. Disabled under `--read-only`. |
139
283
  | `mark_unread` | `account`, `id` | Mark a message as unread. Disabled under `--read-only`. |
284
+ | `check_notifications` | — | Returns pending email-watch notifications (new-email alerts, auth failures). Drains the buffer on read. Only registered in HTTP mode. |
285
+
286
+ ## Email Watch
287
+
288
+ When running in **HTTP mode** (`--http`), the server polls all configured
289
+ accounts every N seconds for new inbox mail. Detected emails and auth failures
290
+ are delivered through two channels:
291
+
292
+ - **Push** — `notifications/message` sent over the MCP stream. Compatible
293
+ clients (e.g. Mastra) receive these in real time.
294
+ - **Poll** — `check_notifications` tool drains an in-memory buffer. Works with
295
+ **any** MCP client, even those that don't maintain an SSE listener.
296
+
297
+ **Configuration** (in `hypermail-config.json`):
298
+
299
+ ```jsonc
300
+ {
301
+ "watch": {
302
+ "enabled": true, // default true
303
+ "pollIntervalSeconds": 60 // default 60 (min 10, max 3600)
304
+ }
305
+ }
306
+ ```
307
+
308
+ **Behavior:**
309
+
310
+ - Only the **inbox** folder is watched. All stored accounts are polled by default.
311
+ - On first poll per account, the server records the newest email as a baseline
312
+ (no notifications). Only emails arriving after baseline trigger alerts.
313
+ - Baselines (`lastSeenAt`) persist in the account store — they survive server
314
+ restarts.
315
+ - Each poll paginates through the inbox (25 items per page) to catch email
316
+ bursts without missing messages.
317
+ - Auth failures (e.g. expired OAuth tokens) generate immediate notifications.
318
+
319
+ **Not supported in stdio mode.** The watcher requires a long-lived server
320
+ process. In stdio mode the `check_notifications` tool is not registered.
140
321
 
141
322
  ### Add-account flow (Outlook)
142
323
 
@@ -163,16 +344,21 @@ account store.
163
344
 
164
345
  - Threading / conversations.
165
346
  - Calendar integration.
166
- - Webhook / push notifications for new mail.
167
347
 
168
348
  ## Project layout
169
349
 
170
350
  ```
171
351
  src/
172
352
  cli.ts # arg parsing + entry
173
- server.ts # MCP server, stdio + HTTP transports
174
- version.ts
175
- store/account-store.ts # encrypted multi-account store (AES-256-GCM)
353
+ server.ts # MCP server, stdio + HTTP transports, session management
354
+ version.ts # version constant
355
+ config.ts # hypermail-config.json schema + resolution
356
+ config/
357
+ agents-config.ts # agents.yaml schema, validation, live-reload watcher
358
+ store/
359
+ account-store.ts # encrypted multi-account store (AES-256-GCM)
360
+ agent-store.ts # agent identity + credentials store (HTTP multi-tenant)
361
+ crypto.ts # AES-256-GCM encrypt/decrypt, key resolution, atomic writes
176
362
  providers/
177
363
  types.ts # EmailProvider interface + shared DTOs
178
364
  registry.ts # routes account email → provider
@@ -185,8 +371,20 @@ src/
185
371
  auth.ts # Google OAuth device-code flow
186
372
  client.ts # Gmail API (googleapis)
187
373
  index.ts # GmailProvider implementation
188
- shared/ # Shared utilities across providers
189
- tools/index.ts # MCP tool registrations
374
+ shared/ # shared utilities across providers
375
+ watcher/
376
+ manager.ts # inbox poller + notification buffer
377
+ index.ts # watcher public API
378
+ tools/
379
+ index.ts # MCP tool registrations
380
+ agent-context.ts # agent authorization guards (checkAccountAccess, checkProvisioning)
381
+ accounts.ts # list/add/remove/complete-add account tools
382
+ browse.ts # list/search/read email tools
383
+ compose.ts # send/draft/edit/send-draft/add-attachment tools
384
+ folders.ts # list/create/delete/rename folder tools
385
+ notifications.ts # check_notifications tool (HTTP only)
386
+ organize.ts # archive/trash/move/mark-read/mark-unread tools
387
+ shared.ts # shared tool helpers
190
388
  ```
191
389
 
192
390
  ## License