hypermail-mcp 0.7.1 → 0.7.3
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 +11 -2
- package/dist/cli.js +74 -12
- package/dist/cli.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -3,7 +3,16 @@
|
|
|
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.
|
|
6
|
+
> **v0.7.3** — `edit_draft` now preserves the quoted thread history when editing
|
|
7
|
+
> Outlook reply/forward drafts. Previously, editing a draft body would overwrite
|
|
8
|
+
> the entire content — including the quoted thread. Now only the answer part
|
|
9
|
+
> (above the spacer delimiter) is replaced.
|
|
10
|
+
>
|
|
11
|
+
> **v0.7.2** — `add_attachment_to_draft` now accepts `filePath` (absolute path to
|
|
12
|
+
a local file) as an alternative to `contentBytes`. The file is read from disk and
|
|
13
|
+
base64-encoded automatically, the MIME type is inferred from the extension, and
|
|
14
|
+
`name` defaults to the file's basename.
|
|
15
|
+
>> **v0.7.1** — Every config field is now settable via a dedicated
|
|
7
16
|
> `HYPERMAIL_*` env var. Legacy env vars (`MS_CLIENT_ID`, `MS_TENANT_ID`,
|
|
8
17
|
> `GOOGLE_CLIENT_ID`, `GOOGLE_CLIENT_SECRET`) still work as fallbacks. See
|
|
9
18
|
> [Environment Variables](#environment-variables) for the full reference.
|
|
@@ -227,7 +236,7 @@ account store.
|
|
|
227
236
|
| `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`. |
|
|
228
237
|
| `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`. |
|
|
229
238
|
| `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`. |
|
|
230
|
-
| `add_attachment_to_draft` | `account`, `id`, `name
|
|
239
|
+
| `add_attachment_to_draft` | `account`, `id`, `name?`, `contentBytes?`, `filePath?`, `contentType?` | Attach a file to an existing draft by ID. Provide `contentBytes` (base64) or `filePath` (absolute path — auto-encodes). Disabled under `--read-only`. |
|
|
231
240
|
| `list_folders` | `account`, `parentFolderId?` | List available mail folders. Returns top-level folders by default, or children of `parentFolderId`. |
|
|
232
241
|
| `create_folder` | `account`, `displayName`, `parentFolderId?` | Create a new mail folder under root (default) or the given parent. Disabled under `--read-only`. |
|
|
233
242
|
| `delete_folder` | `account`, `folderId` | Delete a mail folder by ID. Disabled under `--read-only`. |
|
package/dist/cli.js
CHANGED
|
@@ -3301,6 +3301,8 @@ function registerOrganizeTools(server, ctx) {
|
|
|
3301
3301
|
|
|
3302
3302
|
// src/tools/compose.ts
|
|
3303
3303
|
import { z as z6 } from "zod";
|
|
3304
|
+
import { readFileSync } from "fs";
|
|
3305
|
+
import { basename, extname } from "path";
|
|
3304
3306
|
function registerComposeTools(server, ctx) {
|
|
3305
3307
|
const { store, registry, tools } = ctx;
|
|
3306
3308
|
const sendEmailSchema = z6.object({
|
|
@@ -3458,6 +3460,18 @@ function registerComposeTools(server, ctx) {
|
|
|
3458
3460
|
bodyPayload = composed.body;
|
|
3459
3461
|
isHtmlPayload = composed.isHtml;
|
|
3460
3462
|
}
|
|
3463
|
+
if (bodyPayload !== void 0) {
|
|
3464
|
+
const spacer = '<div style="line-height:12px"><br></div>';
|
|
3465
|
+
try {
|
|
3466
|
+
const existing = await provider.readEmail(account, a.id);
|
|
3467
|
+
const existingHtml = existing.bodyHtml ?? "";
|
|
3468
|
+
const spacerIdx = existingHtml.indexOf(spacer);
|
|
3469
|
+
if (spacerIdx !== -1) {
|
|
3470
|
+
bodyPayload = bodyPayload + existingHtml.slice(spacerIdx);
|
|
3471
|
+
}
|
|
3472
|
+
} catch {
|
|
3473
|
+
}
|
|
3474
|
+
}
|
|
3461
3475
|
const res = await provider.updateDraft(account, a.id, {
|
|
3462
3476
|
to: a.to,
|
|
3463
3477
|
cc: a.cc,
|
|
@@ -3506,6 +3520,31 @@ function registerComposeTools(server, ctx) {
|
|
|
3506
3520
|
}
|
|
3507
3521
|
);
|
|
3508
3522
|
}
|
|
3523
|
+
const MIME_TYPES = {
|
|
3524
|
+
".pdf": "application/pdf",
|
|
3525
|
+
".png": "image/png",
|
|
3526
|
+
".jpg": "image/jpeg",
|
|
3527
|
+
".jpeg": "image/jpeg",
|
|
3528
|
+
".gif": "image/gif",
|
|
3529
|
+
".svg": "image/svg+xml",
|
|
3530
|
+
".webp": "image/webp",
|
|
3531
|
+
".txt": "text/plain",
|
|
3532
|
+
".html": "text/html",
|
|
3533
|
+
".css": "text/css",
|
|
3534
|
+
".csv": "text/csv",
|
|
3535
|
+
".json": "application/json",
|
|
3536
|
+
".xml": "application/xml",
|
|
3537
|
+
".zip": "application/zip",
|
|
3538
|
+
".gz": "application/gzip",
|
|
3539
|
+
".tar": "application/x-tar",
|
|
3540
|
+
".doc": "application/msword",
|
|
3541
|
+
".docx": "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
|
|
3542
|
+
".xls": "application/vnd.ms-excel",
|
|
3543
|
+
".xlsx": "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
|
|
3544
|
+
".pptx": "application/vnd.openxmlformats-officedocument.presentationml.presentation",
|
|
3545
|
+
".mp3": "audio/mpeg",
|
|
3546
|
+
".mp4": "video/mp4"
|
|
3547
|
+
};
|
|
3509
3548
|
const addAttachmentOutputSchema = {
|
|
3510
3549
|
attached: z6.literal(true),
|
|
3511
3550
|
id: z6.string(),
|
|
@@ -3519,25 +3558,48 @@ function registerComposeTools(server, ctx) {
|
|
|
3519
3558
|
server.registerTool(
|
|
3520
3559
|
"add_attachment_to_draft",
|
|
3521
3560
|
{
|
|
3522
|
-
description: "Add a file attachment to an existing draft email by ID. `contentBytes`
|
|
3523
|
-
inputSchema: {
|
|
3561
|
+
description: "Add a file attachment to an existing draft email by ID. Provide exactly one of `contentBytes` (base64-encoded content) or `filePath` (absolute path to a local file). When using `filePath`, the file is read from disk and base64-encoded automatically, and `name` defaults to the file's basename if not provided. `contentType` is the MIME type (e.g. 'application/pdf'); when using `filePath` it is inferred from the extension if omitted. Disabled in --read-only mode.",
|
|
3562
|
+
inputSchema: z6.object({
|
|
3524
3563
|
account: z6.string().email(),
|
|
3525
3564
|
id: z6.string().min(1).describe("Draft message ID"),
|
|
3526
|
-
name: z6.string().min(1).describe(
|
|
3527
|
-
|
|
3528
|
-
|
|
3529
|
-
|
|
3565
|
+
name: z6.string().min(1).optional().describe(
|
|
3566
|
+
"Attachment filename (e.g. 'report.pdf'). Defaults to the file's basename when `filePath` is used."
|
|
3567
|
+
),
|
|
3568
|
+
contentBytes: z6.string().min(1).optional().describe("Base64-encoded file content. Mutually exclusive with `filePath`."),
|
|
3569
|
+
filePath: z6.string().min(1).optional().describe(
|
|
3570
|
+
"Absolute path to a local file. The file is read and base64-encoded automatically. Mutually exclusive with `contentBytes`."
|
|
3571
|
+
),
|
|
3572
|
+
contentType: z6.string().optional().describe("MIME type (e.g. 'application/pdf'). Inferred from `filePath` extension if omitted.")
|
|
3573
|
+
}).refine(
|
|
3574
|
+
(v) => Boolean(v.contentBytes) !== Boolean(v.filePath),
|
|
3575
|
+
{ message: "Provide exactly one of `contentBytes` or `filePath`." }
|
|
3576
|
+
),
|
|
3530
3577
|
outputSchema: addAttachmentOutputSchema
|
|
3531
3578
|
},
|
|
3532
3579
|
async (args) => {
|
|
3533
3580
|
try {
|
|
3534
3581
|
const { provider, account } = registry.resolveByEmail(args.account);
|
|
3582
|
+
let contentBytes;
|
|
3583
|
+
let name;
|
|
3584
|
+
let contentType = args.contentType;
|
|
3585
|
+
if (args.filePath) {
|
|
3586
|
+
const fileData = readFileSync(args.filePath);
|
|
3587
|
+
contentBytes = fileData.toString("base64");
|
|
3588
|
+
name = args.name ?? basename(args.filePath);
|
|
3589
|
+
if (!contentType) {
|
|
3590
|
+
const ext = extname(args.filePath).toLowerCase();
|
|
3591
|
+
contentType = MIME_TYPES[ext] ?? "application/octet-stream";
|
|
3592
|
+
}
|
|
3593
|
+
} else {
|
|
3594
|
+
contentBytes = args.contentBytes;
|
|
3595
|
+
name = args.name;
|
|
3596
|
+
}
|
|
3535
3597
|
const res = await provider.addAttachmentToDraft(
|
|
3536
3598
|
account,
|
|
3537
3599
|
args.id,
|
|
3538
|
-
|
|
3539
|
-
|
|
3540
|
-
|
|
3600
|
+
name,
|
|
3601
|
+
contentBytes,
|
|
3602
|
+
contentType
|
|
3541
3603
|
);
|
|
3542
3604
|
const data = {
|
|
3543
3605
|
attached: true,
|
|
@@ -3566,7 +3628,7 @@ function registerTools(server, opts) {
|
|
|
3566
3628
|
// package.json
|
|
3567
3629
|
var package_default = {
|
|
3568
3630
|
name: "hypermail-mcp",
|
|
3569
|
-
version: "0.7.
|
|
3631
|
+
version: "0.7.3",
|
|
3570
3632
|
description: "Unified email MCP server \u2014 operate any inbox (Outlook now, IMAP/Gmail later) by passing an email address.",
|
|
3571
3633
|
type: "module",
|
|
3572
3634
|
bin: {
|
|
@@ -3747,7 +3809,7 @@ var WatcherManager = class {
|
|
|
3747
3809
|
};
|
|
3748
3810
|
|
|
3749
3811
|
// src/config.ts
|
|
3750
|
-
import { readFileSync } from "fs";
|
|
3812
|
+
import { readFileSync as readFileSync2 } from "fs";
|
|
3751
3813
|
import { z as z7 } from "zod";
|
|
3752
3814
|
var httpConfigSchema = z7.object({
|
|
3753
3815
|
enabled: z7.boolean().default(false),
|
|
@@ -3886,7 +3948,7 @@ function loadConfig(configPath, cliOverrides = {}) {
|
|
|
3886
3948
|
let raw = {};
|
|
3887
3949
|
if (configPath) {
|
|
3888
3950
|
try {
|
|
3889
|
-
raw = JSON.parse(
|
|
3951
|
+
raw = JSON.parse(readFileSync2(configPath, "utf-8"));
|
|
3890
3952
|
} catch (err) {
|
|
3891
3953
|
const detail = err instanceof SyntaxError ? "Invalid JSON" : err instanceof Error ? err.message : String(err);
|
|
3892
3954
|
throw new Error(`Failed to read config file "${configPath}": ${detail}`);
|