hypermail-mcp 0.4.3 → 0.5.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,11 @@
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.5.0** — Replaced optional `isHtml` boolean with required `format`
7
+ > parameter (`"html"` | `"markdown"`) on `send_email`, `draft_email`, and
8
+ > `edit_draft`. Markdown bodies are converted to HTML via `marked` so
9
+ > recipients always see clean HTML.
10
+ >
6
11
  > **v0.4.3** — Upgraded Zod to v4.4.3. Fixed MCP SDK v1.29.0 compatibility
7
12
  > by wrapping all tool schemas in `z.object()` and replacing discriminated
8
13
  > union output schemas that caused `validateToolOutput` crashes.
@@ -121,9 +126,9 @@ account store.
121
126
  | `archive_email` | `account`, `id` | Move a message to the Archive folder. Disabled under `--read-only`. |
122
127
  | `trash_email` | `account`, `id` | Move a message to Deleted Items (trash). Disabled under `--read-only`. |
123
128
  | `move_email` | `account`, `id`, `destination` | Move to any folder by well-known name (`inbox`, `drafts`, etc.) or custom folder ID. Disabled under `--read-only`. |
124
- | `send_email` | `account`, `to[]`, `cc?`, `bcc?`, `subject`, `body`, `isHtml?`, `include_signature?`, `inReplyTo?`, `replyAll?`, `forwardMessageId?` | Send an email. Appends signature when `include_signature` is true. `inReplyTo` sends as threaded reply; `forwardMessageId` sends as forward. Disabled under `--read-only`. |
125
- | `draft_email` | `account`, `to[]`, `cc?`, `bcc?`, `subject`, `body`, `isHtml?`, `include_signature?`, `inReplyTo?`, `replyAll?`, `forwardMessageId?` | Save as draft instead of sending. Returns the draft message ID and HTML body (`draftHtml`). Disabled under `--read-only`. |
126
- | `edit_draft` | `account`, `id`, `to?`, `cc?`, `bcc?`, `subject?`, `body?`, `isHtml?`, `include_signature?` | Edit an existing draft by ID. Only provided fields are updated. Returns the updated draft ID and HTML body (`draftHtml`). Disabled under `--read-only`. |
129
+ | `send_email` | `account`, `to[]`, `cc?`, `bcc?`, `subject`, `body`, `format`, `include_signature?`, `inReplyTo?`, `replyAll?`, `forwardMessageId?` | 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. Disabled under `--read-only`. |
130
+ | `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
+ | `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`. |
127
132
  | `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`. |
128
133
  | `add_attachment_to_draft` | `account`, `id`, `path` | Attach a local file to an existing draft email. Disabled under `--read-only`. |
129
134
  | `list_folders` | `account`, `parentFolderId?` | List available mail folders. Returns top-level folders by default, or children of `parentFolderId`. |
package/dist/cli.js CHANGED
@@ -2459,6 +2459,14 @@ function buildRegistry(opts) {
2459
2459
 
2460
2460
  // src/tools/shared.ts
2461
2461
  import { z } from "zod";
2462
+
2463
+ // src/markdown-to-html.ts
2464
+ import { marked } from "marked";
2465
+ function markdownToHtml(md) {
2466
+ return marked.parse(md, { async: false });
2467
+ }
2468
+
2469
+ // src/tools/shared.ts
2462
2470
  function ok(data, structuredContent) {
2463
2471
  const result = {
2464
2472
  content: [{ type: "text", text: JSON.stringify(data, null, 2) }]
@@ -2535,21 +2543,15 @@ var folderInfoOutputSchema = z.object({
2535
2543
  unreadItemCount: z.number()
2536
2544
  });
2537
2545
  function composeBody(input) {
2538
- const { body, isHtml = false, signature, style, includeSignature } = input;
2546
+ const { body, format, signature, style, includeSignature } = input;
2547
+ const htmlBody = format === "markdown" ? markdownToHtml(body) : body;
2539
2548
  const hasSignature = includeSignature && !!signature;
2540
2549
  const hasStyle = !!(style && (style.fontFamily || style.fontSize || style.fontColor));
2541
2550
  if (!hasSignature && !hasStyle) {
2542
- return { body, isHtml };
2551
+ return { body: htmlBody, isHtml: true };
2543
2552
  }
2544
2553
  const styleAttr = hasStyle ? buildStyleAttr(style) : "";
2545
- if (isHtml) {
2546
- let result2 = hasStyle ? `<div style="${styleAttr}">${body}</div>` : body;
2547
- if (hasSignature) result2 += `
2548
- <div class="signature">${signature}</div>`;
2549
- return { body: result2, isHtml: true };
2550
- }
2551
- const escaped = escapeHtml(body);
2552
- let result = `<div style="${styleAttr}">${escaped}</div>`;
2554
+ let result = hasStyle ? `<div style="${styleAttr}">${htmlBody}</div>` : htmlBody;
2553
2555
  if (hasSignature) result += `
2554
2556
  <div class="signature">${signature}</div>`;
2555
2557
  return { body: result, isHtml: true };
@@ -2561,9 +2563,6 @@ function buildStyleAttr(style) {
2561
2563
  if (style.fontColor) parts.push(`color: ${style.fontColor}`);
2562
2564
  return parts.join("; ");
2563
2565
  }
2564
- function escapeHtml(text) {
2565
- return text.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/\n/g, "<br>");
2566
- }
2567
2566
  function shouldRegister(name, tools) {
2568
2567
  if (tools.enabledTools) {
2569
2568
  return tools.enabledTools.has(name);
@@ -3258,7 +3257,9 @@ function registerComposeTools(server, ctx) {
3258
3257
  bcc: z6.array(emailAddrSchema).optional(),
3259
3258
  subject: z6.string(),
3260
3259
  body: z6.string(),
3261
- isHtml: z6.boolean().optional(),
3260
+ format: z6.enum(["html", "markdown"]).describe(
3261
+ "Body format. 'html' sends the body as-is (must be valid HTML). 'markdown' converts the body from Markdown to HTML for clean rendering on the recipient side."
3262
+ ),
3262
3263
  include_signature: z6.boolean().describe(
3263
3264
  "Whether to append the account's saved HTML signature to the email. If true, don't include a signature in the body param to avoid double signature. Returns an error if true but no signature is configured for this account."
3264
3265
  ),
@@ -3282,7 +3283,7 @@ function registerComposeTools(server, ctx) {
3282
3283
  }
3283
3284
  const composed = composeBody({
3284
3285
  body: args.body,
3285
- isHtml: args.isHtml,
3286
+ format: args.format,
3286
3287
  signature: account.signature,
3287
3288
  style: account.style,
3288
3289
  includeSignature: args.include_signature
@@ -3362,7 +3363,9 @@ function registerComposeTools(server, ctx) {
3362
3363
  bcc: z6.array(emailAddrSchema).optional(),
3363
3364
  subject: z6.string().optional(),
3364
3365
  body: z6.string().optional(),
3365
- isHtml: z6.boolean().optional(),
3366
+ format: z6.enum(["html", "markdown"]).optional().describe(
3367
+ "Body format. Only meaningful when `body` is also provided. 'html' sends the body as-is (must be valid HTML). 'markdown' converts the body from Markdown to HTML for clean rendering on the recipient side."
3368
+ ),
3366
3369
  include_signature: z6.boolean().optional().describe(
3367
3370
  "Whether to re-apply the account's saved HTML signature to the body. If true, don't include a signature in the body param. Only meaningful when `body` is also provided. Returns an error if true but no signature is configured for this account."
3368
3371
  )
@@ -3394,7 +3397,7 @@ function registerComposeTools(server, ctx) {
3394
3397
  if (a.body !== void 0) {
3395
3398
  const composed = composeBody({
3396
3399
  body: a.body,
3397
- isHtml: a.isHtml,
3400
+ format: a.format ?? "html",
3398
3401
  signature: account.signature,
3399
3402
  style: account.style,
3400
3403
  includeSignature: !!a.include_signature