hypermail-mcp 0.6.2 → 0.7.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/README.md CHANGED
@@ -3,6 +3,17 @@
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.0** — Email watch mode: background poll loop detects new inbox
7
+ > messages and POSTs them to a configurable webhook URL (e.g. Mastra). Opt-in —
8
+ > disabled by default, enabled via `HYPERMAIL_WATCH_ENABLED=true` or config.
9
+ > Works in both stdio and HTTP transport modes.
10
+ >
11
+ > **v0.6.3** — Unify stdio and HTTP modes into a single feature set. Removed
12
+ > email watch (inbox polling, SSE push, notification buffer), agent
13
+ > multi-tenancy (`agents.yaml`, `x-api-key` auth, per-agent allowlists), and
14
+ > the `check_notifications` tool. Dropped `js-yaml` dependency. Dockerfile
15
+ > simplified to a single `install → build → prune` step.
16
+ >
6
17
  > **v0.6.2** — Version source-of-truth fix: `version.ts` now imports directly
7
18
  > from `package.json` instead of hardcoding, preventing version drift between
8
19
  > the two files.
@@ -11,10 +22,6 @@ inboxes through a single, unified tool surface.
11
22
  > email notification bug fixes (ID-based dedup, pagination cap, dynamic
12
23
  > re-scan), Node 22 base image, dropped docker-compose.
13
24
  >
14
- > **v0.6.0** — Email watch notifications (polling-based), `signaturePath`
15
- > support in `set_account_settings` for loading signatures from files,
16
- > and a `check_notifications` tool for draining pending alerts.
17
- >
18
25
  > **v0.5.0** — Replaced optional `isHtml` boolean with required `format`
19
26
  > parameter (`"html"` | `"markdown"`) on `send_email`, `draft_email`, and
20
27
  > `edit_draft`. Markdown bodies are converted to HTML via `marked` so
@@ -99,135 +106,37 @@ docker run -d \
99
106
 
100
107
  The image runs the server in HTTP mode on port 3000 with a 30-second
101
108
  HEALTHCHECK against `/mcp`. Data is persisted via a Docker volume at `/data`.
102
- Pass `HYPERMAIL_AGENTS_CONFIG` and mount a config file for agent multi-tenancy.
103
109
 
104
- ### Development (HTTP mode + email watch)
110
+ ### Development
105
111
 
106
- To test the email watch feature locally:
112
+ To test the HTTP server locally:
107
113
 
108
114
  ```bash
109
115
  # Terminal 1: auto-rebuild TypeScript on save
110
116
  pnpm dev
111
117
 
112
- # Terminal 2: start HTTP server with dev config (10s poll, separate data dir)
118
+ # Terminal 2: start HTTP server with dev config
113
119
  pnpm dev:http
114
120
  ```
115
121
 
116
122
  The server listens on `http://127.0.0.1:3000/mcp`. Pi connects via the
117
123
  `.pi/mcp.json` config (read by `pi-mcp-adapter`). Tools appear as
118
- `hypermail_http_*` (e.g. `hypermail_http_check_notifications`).
119
-
120
- The dev config (`hypermail-config.http.json`) uses a separate data dir
121
- (`~/.hypermail-mcp-dev`) and a 10-second poll interval for fast feedback.
122
-
123
- ## Modes: stdio vs HTTP
124
-
125
- The server runs in one of two modes — the choice affects session management,
126
- security, and which features are available.
127
-
128
- | | stdio (default) | HTTP (`--http`) |
129
- | --- | --- | --- |
130
- | **Transport** | stdin/stdout | HTTP (Streamable HTTP MCP) |
131
- | **Lifecycle** | Per-invocation (lazy) — spawned on demand by the MCP host | Long-lived server process |
132
- | **Session model** | One `McpServer` instance for all invocations | One `McpServer` per MCP session (multi-tenant) |
133
- | **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) |
134
- | **Email watch** | ❌ Not available | ✅ Polls inbox every N seconds for new mail |
135
- | **`check_notifications`** | ❌ Not registered | ✅ Drains pending new-mail alerts |
136
- | **Agent multi-tenancy** | ❌ Unrestricted access | ✅ Per-agent API keys, account allowlists, provisioning control (via `agents.yaml`) |
137
- | **Pi tool naming** | `hyper_*` | `hypermail_http_*` |
138
-
139
- **When to use HTTP mode:**
140
- - You need email watch / push notifications
141
- - You want to expose the server to multiple agents with different permissions
142
- - You're hosting the server as a service (Docker, cloud)
143
-
144
- **When to use stdio mode:**
145
- - Single-user local development with a desktop MCP client (Claude, Pi)
146
- - You don't need email watch or multi-agent access control
147
-
148
- ## Agent multi-tenancy
149
-
150
- In HTTP mode, the server can be shared across multiple agents with
151
- different permissions. Agent identity and authorization are defined in an
152
- `agents.yaml` file.
153
-
154
- ### agents.yaml
155
-
156
- ```yaml
157
- agents:
158
- - id: my-assistant
159
- api_key: hm_sk_<64-hex-chars>
160
- name: My Email Assistant
161
- accounts: # which email addresses this agent can access
162
- - alice@example.com
163
- - bob@example.com
164
- provisioning: false # can this agent add/remove accounts?
165
-
166
- - id: admin-agent
167
- api_key: hm_sk_<64-hex-chars>
168
- name: Admin Agent
169
- accounts: [] # empty = all accounts
170
- provisioning: true
171
-
172
- # Optional: pre-declare email accounts with provider hints
173
- email_accounts:
174
- alice@example.com:
175
- provider: outlook
176
- ```
177
-
178
- **Agent ID:** lowercase letters, digits, hyphens, underscores. No spaces.
179
-
180
- **API key format:** `hm_sk_` prefix + 64 hex characters. Generate with:
181
-
182
- ```bash
183
- hypermail-mcp generate-key
184
- # => hm_sk_a1b2c3d4...
185
- ```
186
-
187
- The API key is hashed (SHA-256) before storage — the plaintext is never
188
- written to disk. Agents authenticate by passing the key in the
189
- `Authorization: Bearer hm_sk_...` header.
190
-
191
- **accounts:** An allowlist of email addresses the agent can operate on.
192
- If empty or omitted, the agent can access all configured accounts.
193
-
194
- **provisioning:** When `true`, the agent can call `add_account` and
195
- `remove_account`. Defaults to `false`.
196
-
197
- ### Configuration
124
+ `hypermail_http_*`.
198
125
 
199
- Point the server at your agents.yaml:
200
-
201
- ```bash
202
- # Via CLI flag
203
- hypermail-mcp --http --agents-config ./agents.yaml
204
-
205
- # Via env var
206
- export HYPERMAIL_AGENTS_CONFIG=/etc/hypermail/agents.yaml
207
- ```
208
-
209
- The server watches `agents.yaml` for changes and reloads automatically
210
- (live reload — no restart needed). Agents removed from the file lose
211
- access on their next request.
212
-
213
- In **stdio mode**, agent multi-tenancy is not available — the server runs
214
- with unrestricted access (the local user _is_ the agent).
215
-
216
- ## Configuration
126
+ ## Add-account flow (Outlook)
217
127
 
218
128
  | Env var | Purpose | Default |
219
129
  | --- | --- | --- |
220
130
  | `HYPERMAIL_MCP_DATA_DIR` | Where to keep the encrypted accounts blob | `~/.hypermail-mcp` |
221
- | `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 |
222
- | `HYPERMAIL_AGENTS_CONFIG` | Path to `agents.yaml` for HTTP multi-tenant mode (see Agent multi-tenancy above). | — (multi-tenancy disabled) |
131
+ | `HYPERMAIL_MCP_KEY` | 32-byte AES-256-GCM key (hex, base64, or any passphrase — derived via SHA-256). Required for hosted deployments. Auto-generated for stdio. | auto-generated, stored via OS keychain (`keytar`) or a local `master.key` file |
223
132
  | `MS_CLIENT_ID` | Azure Entra public client (application) id used for device-code login | placeholder — **set your own for production** |
224
133
  | `MS_TENANT_ID` | Tenant for the authority URL | `common` |
225
134
 
226
- CLI flags: `--http`, `--port`, `--host`, `--data-dir`, `--agents-config`, `--read-only`, `--help`.
135
+ CLI flags: `--http`, `--port`, `--host`, `--data-dir`, `--read-only`, `--help`.
227
136
 
228
- Subcommands: `hypermail-mcp generate-key` — generate an `hm_sk_` API key for agents.yaml.
137
+ Subcommands: `hypermail-mcp generate-key` — generate an `hm_sk_` API key.
229
138
 
230
- ### Config file (`hypermail-config.json`)
139
+ ### Configuration
231
140
 
232
141
  Instead of (or in addition to) CLI flags and env vars, you can configure the
233
142
  server with a `hypermail-config.json` file next to the server binary. The server
@@ -245,6 +154,14 @@ looks for it in the same directory as `cli.js`.
245
154
  },
246
155
  "providers": {
247
156
  "outlook": { "clientId": "...", "tenantId": "..." }
157
+ },
158
+ "watch": {
159
+ "enabled": true,
160
+ "pollIntervalSeconds": 10,
161
+ "webhook": {
162
+ "url": "http://your-agent:3000/api/email-webhook",
163
+ "retry": { "maxAttempts": 5, "baseDelayMs": 1000 }
164
+ }
248
165
  }
249
166
  }
250
167
  ```
@@ -285,45 +202,51 @@ account store.
285
202
  | `rename_folder` | `account`, `folderId`, `newName` | Rename an existing mail folder. Disabled under `--read-only`. |
286
203
  | `mark_read` | `account`, `id` | Mark a message as read. Disabled under `--read-only`. |
287
204
  | `mark_unread` | `account`, `id` | Mark a message as unread. Disabled under `--read-only`. |
288
- | `check_notifications` | — | Returns pending email-watch notifications (new-email alerts, auth failures). Drains the buffer on read. Only registered in HTTP mode. |
289
205
 
290
206
  ## Email Watch
291
207
 
292
- When running in **HTTP mode** (`--http`), the server polls all configured
293
- accounts every N seconds for new inbox mail. Detected emails and auth failures
294
- are delivered through two channels:
295
-
296
- - **Push** — `notifications/message` sent over the MCP stream. Compatible
297
- clients (e.g. Mastra) receive these in real time.
298
- - **Poll** — `check_notifications` tool drains an in-memory buffer. Works with
299
- **any** MCP client, even those that don't maintain an SSE listener.
300
-
301
- **Configuration** (in `hypermail-config.json`):
208
+ When enabled, hypermail-mcp runs a background poll loop that scans inboxes for
209
+ new messages and POSTs each one to a configurable webhook URL. Intended for
210
+ push-based email triage downstream agents (e.g. Mastra) receive full email
211
+ content without polling.
302
212
 
303
213
  ```jsonc
304
214
  {
305
215
  "watch": {
306
- "enabled": true, // default true
307
- "pollIntervalSeconds": 60 // default 60 (min 10, max 3600)
216
+ "enabled": true,
217
+ "pollIntervalSeconds": 10,
218
+ "webhook": {
219
+ "url": "http://localhost:3000/api/email-webhook",
220
+ "retry": { "maxAttempts": 5, "baseDelayMs": 1000 }
221
+ }
308
222
  }
309
223
  }
310
224
  ```
311
225
 
312
- **Behavior:**
313
-
314
- - Only the **inbox** folder is watched. All stored accounts are polled by default.
315
- - On first poll per account, the server records the newest email as a baseline
316
- (no notifications). Only emails arriving after baseline trigger alerts.
317
- - Baselines (`lastSeenAt`) persist in the account store they survive server
318
- restarts.
319
- - Each poll paginates through the inbox (25 items per page) to catch email
320
- bursts without missing messages.
321
- - Auth failures (e.g. expired OAuth tokens) generate immediate notifications.
322
-
323
- **Not supported in stdio mode.** The watcher requires a long-lived server
324
- process. In stdio mode the `check_notifications` tool is not registered.
226
+ | Setting | Default | Notes |
227
+ | --- | --- | --- |
228
+ | `watch.enabled` | `false` | Toggle via config or `HYPERMAIL_WATCH_ENABLED=true` env var |
229
+ | `watch.pollIntervalSeconds` | `10` | Min 10s, max 3600s |
230
+ | `watch.webhook.url` | | Endpoint that receives `POST` with `EmailFull` JSON |
231
+ | `watch.webhook.retry.maxAttempts` | `5` | Max delivery attempts (1–10) |
232
+ | `watch.webhook.retry.baseDelayMs` | `1000` | Base backoff delay (× 2^attempt) |
325
233
 
326
- ### Add-account flow (Outlook)
234
+ **Behavior:**
235
+ - Polls **all accounts** in the store, **inbox only**.
236
+ - Detects new emails via `lastSeenIds` (capped at 200) stored in the encrypted
237
+ account file — no duplicate emits across restarts.
238
+ - One `POST` per email (full body: subject, sender, text, HTML, attachments
239
+ metadata, thread ID via `EmailFull`).
240
+ - Delivery uses exponential backoff (`baseDelay × 2^attempt`). Retries on
241
+ non-2xx responses and connection errors. Logs and moves on after
242
+ `maxAttempts` exhausted — never blocks the poll loop.
243
+ - Works in both **stdio** and **HTTP** transport modes — the poll interval
244
+ fires normally alongside MCP message handling.
245
+
246
+ **Rate limits:** Polling every 10s on a single inbox = 6 req/min = 0.6% of
247
+ Microsoft Graph's 10,000 req/10min per-user limit. Safe for personal inboxes.
248
+
249
+ ## Add-account flow (Outlook)
327
250
 
328
251
  1. Agent calls `add_account({ provider: "outlook" })`.
329
252
  2. Server returns:
@@ -357,11 +280,8 @@ src/
357
280
  server.ts # MCP server, stdio + HTTP transports, session management
358
281
  version.ts # version constant
359
282
  config.ts # hypermail-config.json schema + resolution
360
- config/
361
- agents-config.ts # agents.yaml schema, validation, live-reload watcher
362
283
  store/
363
284
  account-store.ts # encrypted multi-account store (AES-256-GCM)
364
- agent-store.ts # agent identity + credentials store (HTTP multi-tenant)
365
285
  crypto.ts # AES-256-GCM encrypt/decrypt, key resolution, atomic writes
366
286
  providers/
367
287
  types.ts # EmailProvider interface + shared DTOs
@@ -377,16 +297,15 @@ src/
377
297
  index.ts # GmailProvider implementation
378
298
  shared/ # shared utilities across providers
379
299
  watcher/
380
- manager.ts # inbox poller + notification buffer
381
- index.ts # watcher public API
300
+ manager.ts # WatcherManager — inbox poll loop + dedup
301
+ webhook.ts # HTTP POST with exponential backoff retry
302
+ index.ts # barrel export
382
303
  tools/
383
304
  index.ts # MCP tool registrations
384
- agent-context.ts # agent authorization guards (checkAccountAccess, checkProvisioning)
385
305
  accounts.ts # list/add/remove/complete-add account tools
386
306
  browse.ts # list/search/read email tools
387
307
  compose.ts # send/draft/edit/send-draft/add-attachment tools
388
308
  folders.ts # list/create/delete/rename folder tools
389
- notifications.ts # check_notifications tool (HTTP only)
390
309
  organize.ts # archive/trash/move/mark-read/mark-unread tools
391
310
  shared.ts # shared tool helpers
392
311
  ```