hypermail-mcp 0.7.15 → 0.7.17

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,20 @@
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.17** — Hardened Outlook message IDs returned by `list_emails` and
7
+ > `search_emails`: Outlook reads now request Microsoft Graph immutable IDs,
8
+ > `search_emails` probes results and marks stale/not-found messages with
9
+ > `stale: true`, and `read_email` / `read_attachment` keep a legacy mutable-ID
10
+ > fallback for older IDs. Graph errors now include structured diagnostic details
11
+ > when available.
12
+ >
13
+ > **v0.7.16** — Hardened `draft_email` after successful draft creation:
14
+ > if post-save readback fails, the tool now returns the created draft ID with
15
+ > `warning` and `draftReadbackError` instead of losing the draft behind an
16
+ > error. Compose operations also emit sanitized `HYPERMAIL_DEBUG` stage logs,
17
+ > IMAP drafts now use the server-advertised `\\Drafts` mailbox when present,
18
+ > and the CLI supports `--version`.
19
+ >
6
20
  > **v0.7.15** — Hardened IMAP draft creation: drafts now append directly to
7
21
  > the Drafts mailbox, retry without the `\\Draft` flag when an IMAP server
8
22
  > rejects flagged APPEND, and preserve IMAP reply/forward context in drafts the
@@ -205,7 +219,7 @@ Hypermail uses flat `HYPERMAIL_*` environment variables as the source of truth.
205
219
  There is no runtime config file. CLI flags only override transport, host, port,
206
220
  and data directory for a single invocation.
207
221
 
208
- CLI flags: `--http`, `--port`, `--host`, `--data-dir`, `--help`.
222
+ CLI flags: `--http`, `--port`, `--host`, `--data-dir`, `--version`, `--help`.
209
223
 
210
224
  Subcommands: `hypermail-mcp generate-key` — generate a base64 32-byte key for
211
225
  `HYPERMAIL_KEY`.
@@ -284,7 +298,7 @@ account store.
284
298
  | `trash_email` | `account`, `id` | Move a message to Deleted Items (trash). |
285
299
  | `move_email` | `account`, `id`, `destination` | Move to any folder by well-known name (`inbox`, `drafts`, etc.) or custom folder ID. |
286
300
  | `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`; multiline plain text with `format: "html"` is rejected, so use `"markdown"` for paragraphs or add tags like `<p>`/`<br>`. 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. |
287
- | `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. |
301
+ | `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, when readback succeeds, HTML body (`draftHtml`). If the draft is created but readback fails, returns the draft ID with `warning` and `draftReadbackError` so callers can retry `read_email` or continue with `send_draft`. `inReplyTo` is required — set to `false` for new emails. |
288
302
  | `edit_draft` | `account`, `id`, `to?`, `cc?`, `bcc?`, `subject?`, `old_text?`, `new_text?`, `body?`, `format?`, `include_signature?`, `new_attachments?`, `remove_attachments?` | Edit an existing draft by ID. Body edits require exact selected-section replacement: copy `old_text` from the current draft HTML (`draftHtml` or `read_email` with `format: "html"`) and provide `new_text`; the match must occur exactly once, and unselected content such as reply/forward history is preserved. Deprecated `body` is only an alias for `new_text` when `old_text` is also provided; body-only full replacement is rejected. Multiline plain-text replacements with `format: "html"` are rejected; use `"markdown"` for paragraphs or add tags like `<p>`/`<br>`. Body edits are re-read after saving; if the updated body is not observable after retries, the tool returns an error instead of reporting success. `new_attachments` adds files (`{filePath, name?}[]`); `remove_attachments` removes by attachment ID (`string[]`). Returns the updated draft ID, HTML body (`draftHtml`), and attachment metadata. |
289
303
  | `send_draft` | `account`, `id` | Send an existing draft email by ID. Use with draft IDs returned by `draft_email` or `edit_draft`. |
290
304
  | `list_folders` | `account`, `parentFolderId?` | List available mail folders. Returns top-level folders by default, or children of `parentFolderId`. |
package/dist/cli.js CHANGED
@@ -14846,21 +14846,122 @@ import { ResponseType } from "@microsoft/microsoft-graph-client";
14846
14846
  import { writeFileSync } from "fs";
14847
14847
  import { tmpdir } from "os";
14848
14848
  import { join as pathJoin } from "path";
14849
+ var OUTLOOK_IMMUTABLE_ID_PREFER = 'IdType="ImmutableId"';
14850
+ var MESSAGE_SELECT = [
14851
+ "id",
14852
+ "subject",
14853
+ "from",
14854
+ "toRecipients",
14855
+ "receivedDateTime",
14856
+ "bodyPreview",
14857
+ "isRead",
14858
+ "hasAttachments"
14859
+ ].join(",");
14860
+ var FULL_MESSAGE_SELECT = [
14861
+ "id",
14862
+ "subject",
14863
+ "from",
14864
+ "toRecipients",
14865
+ "ccRecipients",
14866
+ "bccRecipients",
14867
+ "receivedDateTime",
14868
+ "bodyPreview",
14869
+ "isRead",
14870
+ "hasAttachments",
14871
+ "body"
14872
+ ].join(",");
14873
+ function graphErrorFields(err) {
14874
+ if (typeof err !== "object" || err === null) {
14875
+ return [String(err)];
14876
+ }
14877
+ const record2 = err;
14878
+ const fields = [];
14879
+ for (const key of ["code", "statusCode", "message"]) {
14880
+ const value = record2[key];
14881
+ if (typeof value === "string" || typeof value === "number") fields.push(value);
14882
+ }
14883
+ if (typeof record2.body === "string") {
14884
+ try {
14885
+ const parsed = JSON.parse(record2.body);
14886
+ if (typeof parsed === "object" && parsed !== null) {
14887
+ const graphError = parsed.error;
14888
+ if (typeof graphError === "object" && graphError !== null) {
14889
+ const graphRecord = graphError;
14890
+ for (const key of ["code", "message"]) {
14891
+ const value = graphRecord[key];
14892
+ if (typeof value === "string" || typeof value === "number") {
14893
+ fields.push(value);
14894
+ }
14895
+ }
14896
+ }
14897
+ }
14898
+ } catch {
14899
+ fields.push(record2.body);
14900
+ }
14901
+ }
14902
+ return fields;
14903
+ }
14904
+ function isStaleMessageIdError(err) {
14905
+ const fields = graphErrorFields(err);
14906
+ if (fields.some((value) => value === 404)) return true;
14907
+ return fields.some((value) => {
14908
+ if (typeof value !== "string") return false;
14909
+ const normalized = value.toLowerCase();
14910
+ return normalized.includes("erroritemnotfound") || normalized.includes("invalidid") || normalized.includes("object was not found in the store") || normalized.includes("not found in the store");
14911
+ });
14912
+ }
14913
+ async function probeMessage(client, id, useImmutableIds) {
14914
+ let req = client.api(`/me/messages/${encodeURIComponent(id)}`).select("id");
14915
+ if (useImmutableIds) req = req.header("Prefer", OUTLOOK_IMMUTABLE_ID_PREFER);
14916
+ await req.get();
14917
+ }
14918
+ async function probeSearchResult(client, id) {
14919
+ try {
14920
+ await probeMessage(client, id, true);
14921
+ return true;
14922
+ } catch (err) {
14923
+ if (!isStaleMessageIdError(err)) throw err;
14924
+ }
14925
+ try {
14926
+ await probeMessage(client, id, false);
14927
+ return true;
14928
+ } catch (err) {
14929
+ if (isStaleMessageIdError(err)) return false;
14930
+ throw err;
14931
+ }
14932
+ }
14933
+ async function getMessage(client, id) {
14934
+ try {
14935
+ const message2 = await client.api(`/me/messages/${encodeURIComponent(id)}`).header("Prefer", OUTLOOK_IMMUTABLE_ID_PREFER).select(FULL_MESSAGE_SELECT).get();
14936
+ return { message: message2, useImmutableIds: true };
14937
+ } catch (err) {
14938
+ if (!isStaleMessageIdError(err)) throw err;
14939
+ }
14940
+ const message = await client.api(`/me/messages/${encodeURIComponent(id)}`).select(FULL_MESSAGE_SELECT).get();
14941
+ return { message, useImmutableIds: false };
14942
+ }
14943
+ async function listAttachments(client, messageId, useImmutableIds) {
14944
+ let req = client.api(`/me/messages/${encodeURIComponent(messageId)}/attachments`).select("id,name,contentType,size");
14945
+ if (useImmutableIds) req = req.header("Prefer", OUTLOOK_IMMUTABLE_ID_PREFER);
14946
+ const attRes = await req.get();
14947
+ return attRes.value;
14948
+ }
14949
+ async function getAttachmentMetadata(client, messageId, attachmentId, useImmutableIds) {
14950
+ let req = client.api(`/me/messages/${encodeURIComponent(messageId)}/attachments/${encodeURIComponent(attachmentId)}`).select("name,contentType");
14951
+ if (useImmutableIds) req = req.header("Prefer", OUTLOOK_IMMUTABLE_ID_PREFER);
14952
+ return await req.get();
14953
+ }
14954
+ async function getAttachmentValue(client, messageId, attachmentId, useImmutableIds) {
14955
+ let req = client.api(`/me/messages/${encodeURIComponent(messageId)}/attachments/${encodeURIComponent(attachmentId)}/$value`).responseType(ResponseType.ARRAYBUFFER);
14956
+ if (useImmutableIds) req = req.header("Prefer", OUTLOOK_IMMUTABLE_ID_PREFER);
14957
+ return await req.get();
14958
+ }
14849
14959
  async function listEmails(client, account, opts) {
14850
14960
  const limit = clampLimit(opts.limit, 25, 100);
14851
14961
  const folder = opts.folder ?? "inbox";
14852
14962
  const filterParts = [];
14853
14963
  if (opts.unreadOnly) filterParts.push("isRead eq false");
14854
- let req = client.api(`/me/mailFolders/${encodeURIComponent(folder)}/messages`).top(limit).skip(opts.skip ?? 0).select([
14855
- "id",
14856
- "subject",
14857
- "from",
14858
- "toRecipients",
14859
- "receivedDateTime",
14860
- "bodyPreview",
14861
- "isRead",
14862
- "hasAttachments"
14863
- ].join(",")).orderby("receivedDateTime DESC");
14964
+ let req = client.api(`/me/mailFolders/${encodeURIComponent(folder)}/messages`).header("Prefer", OUTLOOK_IMMUTABLE_ID_PREFER).top(limit).skip(opts.skip ?? 0).select(MESSAGE_SELECT).orderby("receivedDateTime DESC");
14864
14965
  if (filterParts.length > 0) req = req.filter(filterParts.join(" and "));
14865
14966
  const res = await req.get();
14866
14967
  return {
@@ -14870,41 +14971,22 @@ async function listEmails(client, account, opts) {
14870
14971
  }
14871
14972
  async function searchEmails(client, account, query, opts) {
14872
14973
  const limit = clampLimit(opts.limit, 25, 100);
14873
- const res = await client.api("/me/messages").header("ConsistencyLevel", "eventual").top(limit).search(`"${query.replace(/"/g, '\\"')}"`).select(
14874
- [
14875
- "id",
14876
- "subject",
14877
- "from",
14878
- "toRecipients",
14879
- "receivedDateTime",
14880
- "bodyPreview",
14881
- "isRead",
14882
- "hasAttachments"
14883
- ].join(",")
14884
- ).get();
14885
- return res.value.map((m) => mapSummary(m));
14974
+ const res = await client.api("/me/messages").header("ConsistencyLevel", "eventual").header("Prefer", OUTLOOK_IMMUTABLE_ID_PREFER).top(limit).search(`"${query.replace(/"/g, '\\"')}"`).select(MESSAGE_SELECT).get();
14975
+ const summaries = res.value.map((m) => mapSummary(m));
14976
+ return Promise.all(
14977
+ summaries.map(async (summary) => {
14978
+ const readable = await probeSearchResult(client, summary.id);
14979
+ return readable ? summary : { ...summary, stale: true };
14980
+ })
14981
+ );
14886
14982
  }
14887
14983
  async function readEmail(client, account, id) {
14888
- const m = await client.api(`/me/messages/${encodeURIComponent(id)}`).select(
14889
- [
14890
- "id",
14891
- "subject",
14892
- "from",
14893
- "toRecipients",
14894
- "ccRecipients",
14895
- "bccRecipients",
14896
- "receivedDateTime",
14897
- "bodyPreview",
14898
- "isRead",
14899
- "hasAttachments",
14900
- "body"
14901
- ].join(",")
14902
- ).get();
14984
+ const { message: m, useImmutableIds } = await getMessage(client, id);
14903
14985
  let attachments = void 0;
14904
14986
  if (m.hasAttachments) {
14905
14987
  try {
14906
- const attRes = await client.api(`/me/messages/${encodeURIComponent(id)}/attachments`).select("id,name,contentType,size").get();
14907
- attachments = attRes.value.map((a) => ({
14988
+ const attRes = await listAttachments(client, m.id, useImmutableIds);
14989
+ attachments = attRes.map((a) => ({
14908
14990
  id: a.id,
14909
14991
  name: a.name,
14910
14992
  contentType: a.contentType,
@@ -14925,8 +15007,22 @@ async function readEmail(client, account, id) {
14925
15007
  };
14926
15008
  }
14927
15009
  async function readAttachment(client, account, messageId, attachmentId) {
14928
- const att = await client.api(`/me/messages/${encodeURIComponent(messageId)}/attachments/${encodeURIComponent(attachmentId)}`).select("name,contentType").get();
14929
- const data = await client.api(`/me/messages/${encodeURIComponent(messageId)}/attachments/${encodeURIComponent(attachmentId)}/$value`).responseType(ResponseType.ARRAYBUFFER).get();
15010
+ let useImmutableIds = true;
15011
+ let att;
15012
+ try {
15013
+ att = await getAttachmentMetadata(client, messageId, attachmentId, true);
15014
+ } catch (err) {
15015
+ if (!isStaleMessageIdError(err)) throw err;
15016
+ useImmutableIds = false;
15017
+ att = await getAttachmentMetadata(client, messageId, attachmentId, false);
15018
+ }
15019
+ let data;
15020
+ try {
15021
+ data = await getAttachmentValue(client, messageId, attachmentId, useImmutableIds);
15022
+ } catch (err) {
15023
+ if (!useImmutableIds || !isStaleMessageIdError(err)) throw err;
15024
+ data = await getAttachmentValue(client, messageId, attachmentId, false);
15025
+ }
14930
15026
  const outPath = pathJoin(tmpdir(), att.name);
14931
15027
  writeFileSync(outPath, Buffer.from(data));
14932
15028
  return {
@@ -15264,6 +15360,17 @@ function resolveTrashMailbox(mailboxes) {
15264
15360
  }
15265
15361
  return "Trash";
15266
15362
  }
15363
+ function resolveDraftMailbox(mailboxes) {
15364
+ for (const mailbox of mailboxes) {
15365
+ const specialUse = mailbox.specialUse?.toLowerCase();
15366
+ if (specialUse === "\\drafts") return mailbox.path;
15367
+ const flags = mailbox.flags ? Array.from(mailbox.flags) : [];
15368
+ if (flags.some((flag) => flag.toLowerCase() === "\\drafts")) {
15369
+ return mailbox.path;
15370
+ }
15371
+ }
15372
+ return "Drafts";
15373
+ }
15267
15374
  function clampLimit2(v, dflt, max) {
15268
15375
  if (!v || v <= 0) return dflt;
15269
15376
  return Math.min(v, max);
@@ -15643,10 +15750,13 @@ async function sendEmail(clients, account, msg) {
15643
15750
  }
15644
15751
  async function saveDraft(clients, account, msg) {
15645
15752
  const client = clients.get(account);
15646
- const folder = "Drafts";
15647
15753
  const rawMsg = await buildRawMessage(client, account, msg);
15754
+ let folder = "Drafts";
15648
15755
  try {
15649
- const result = await client.run((imap) => appendDraft(imap, folder, rawMsg));
15756
+ const result = await client.run(async (imap) => {
15757
+ folder = resolveDraftMailbox(await imap.list());
15758
+ return appendDraft(imap, folder, rawMsg);
15759
+ });
15650
15760
  return { id: encodeId(folder, appendUid(result, folder)) };
15651
15761
  } catch (err) {
15652
15762
  throw imapOperationError(`failed to save IMAP draft to ${folder}`, err);
@@ -17215,12 +17325,41 @@ function fail(message) {
17215
17325
  content: [{ type: "text", text: message }]
17216
17326
  };
17217
17327
  }
17328
+ function graphErrorDetails(err) {
17329
+ if (typeof err !== "object" || err === null) return void 0;
17330
+ const record2 = err;
17331
+ const details = {};
17332
+ for (const key of ["code", "statusCode", "requestId", "date", "innerError"]) {
17333
+ if (record2[key] !== void 0) details[key] = record2[key];
17334
+ }
17335
+ if (typeof record2.body === "string") {
17336
+ try {
17337
+ const parsed = JSON.parse(record2.body);
17338
+ if (typeof parsed === "object" && parsed !== null) {
17339
+ const graphError = parsed.error;
17340
+ if (typeof graphError === "object" && graphError !== null) {
17341
+ const graphRecord = graphError;
17342
+ for (const key of ["code", "message", "innerError"]) {
17343
+ if (graphRecord[key] !== void 0 && details[key] === void 0) {
17344
+ details[key] = graphRecord[key];
17345
+ }
17346
+ }
17347
+ }
17348
+ }
17349
+ } catch {
17350
+ }
17351
+ }
17352
+ return Object.keys(details).length > 0 ? details : void 0;
17353
+ }
17218
17354
  function errMsg(err) {
17219
17355
  const message = err instanceof Error ? err.message : String(err);
17220
17356
  if (message.includes("HYPERMAIL_GMAIL_CLIENT_ID")) {
17221
17357
  return "Missing Gmail OAuth configuration: set HYPERMAIL_GMAIL_CLIENT_ID before adding a Gmail account.";
17222
17358
  }
17223
- return message;
17359
+ const details = graphErrorDetails(err);
17360
+ if (!details) return message;
17361
+ return `${message}
17362
+ ${JSON.stringify({ error: details }, null, 2)}`;
17224
17363
  }
17225
17364
  var providerIdEnum = z2.enum(["outlook", "imap", "gmail"]);
17226
17365
  var emailAddrSchema = z2.object({
@@ -17280,7 +17419,8 @@ var emailSummaryOutputSchema = z2.object({
17280
17419
  preview: z2.string().optional(),
17281
17420
  isRead: z2.boolean().optional(),
17282
17421
  hasAttachments: z2.boolean().optional(),
17283
- folder: z2.string().optional()
17422
+ folder: z2.string().optional(),
17423
+ stale: z2.boolean().optional()
17284
17424
  });
17285
17425
  var attachmentMetaOutputSchema = z2.object({
17286
17426
  id: z2.string(),
@@ -18522,10 +18662,21 @@ var editDraftSchema = z8.object({
18522
18662
 
18523
18663
  // src/tools/compose.ts
18524
18664
  function registerComposeTools(server, ctx) {
18525
- const { store, registry, tools } = ctx;
18665
+ const { store, registry, tools, logger } = ctx;
18526
18666
  async function handleSendOrDraft(args, action, resultKey, toolName) {
18527
18667
  try {
18528
18668
  const { provider, account } = registry.resolveByEmail(args.account);
18669
+ logger?.debug("compose", "start", {
18670
+ tool: toolName,
18671
+ account: account.email,
18672
+ provider: provider.id,
18673
+ toCount: args.to.length,
18674
+ ccCount: args.cc?.length ?? 0,
18675
+ bccCount: args.bcc?.length ?? 0,
18676
+ hasReply: !!args.inReplyTo,
18677
+ hasForward: !!args.forwardMessageId,
18678
+ attachmentCount: args.attachments?.length ?? 0
18679
+ });
18529
18680
  if (args.include_signature && !account.signature) {
18530
18681
  return fail(
18531
18682
  "include_signature is true but no signature is configured for this account. Set up a signature first with set_account_settings."
@@ -18538,6 +18689,14 @@ function registerComposeTools(server, ctx) {
18538
18689
  style: account.style,
18539
18690
  includeSignature: args.include_signature
18540
18691
  });
18692
+ logger?.debug("compose", "composed", {
18693
+ tool: toolName,
18694
+ account: account.email,
18695
+ provider: provider.id,
18696
+ format: args.format,
18697
+ isHtml: composed.isHtml,
18698
+ includeSignature: args.include_signature
18699
+ });
18541
18700
  if (args.inReplyTo && args.forwardMessageId) {
18542
18701
  return fail(
18543
18702
  "inReplyTo and forwardMessageId are mutually exclusive \u2014 use one or the other"
@@ -18555,6 +18714,12 @@ function registerComposeTools(server, ctx) {
18555
18714
  };
18556
18715
  });
18557
18716
  }
18717
+ logger?.debug("compose", "attachmentsProcessed", {
18718
+ tool: toolName,
18719
+ account: account.email,
18720
+ provider: provider.id,
18721
+ attachmentCount: processedAttachments?.length ?? 0
18722
+ });
18558
18723
  const res = await action(provider, account, {
18559
18724
  to: args.to,
18560
18725
  cc: args.cc,
@@ -18567,13 +18732,42 @@ function registerComposeTools(server, ctx) {
18567
18732
  forwardMessageId: args.forwardMessageId,
18568
18733
  attachments: processedAttachments
18569
18734
  });
18735
+ logger?.debug("compose", "providerActionSuccess", {
18736
+ tool: toolName,
18737
+ account: account.email,
18738
+ provider: provider.id,
18739
+ hasId: !!res.id
18740
+ });
18570
18741
  const result = { [resultKey]: true, ...res };
18571
18742
  if (toolName === "draft_email" && res.id) {
18572
- const draft = await provider.readEmail(account, res.id);
18573
- result.draftHtml = draft.bodyHtml;
18743
+ try {
18744
+ const draft = await provider.readEmail(account, res.id);
18745
+ result.draftHtml = draft.bodyHtml;
18746
+ logger?.debug("compose", "draftReadbackSuccess", {
18747
+ tool: toolName,
18748
+ account: account.email,
18749
+ provider: provider.id,
18750
+ hasDraftHtml: draft.bodyHtml !== void 0
18751
+ });
18752
+ } catch (readErr) {
18753
+ const message = errMsg(readErr);
18754
+ result.warning = "Draft was created, but reading it back for draftHtml failed. Use read_email with the returned id to inspect it, or continue with send_draft if appropriate.";
18755
+ result.draftReadbackError = message;
18756
+ logger?.debug("compose", "draftReadbackError", {
18757
+ tool: toolName,
18758
+ account: account.email,
18759
+ provider: provider.id,
18760
+ message
18761
+ });
18762
+ }
18574
18763
  }
18575
18764
  return ok(result, result);
18576
18765
  } catch (err) {
18766
+ logger?.debug("compose", "error", {
18767
+ tool: toolName,
18768
+ account: args.account,
18769
+ message: errMsg(err)
18770
+ });
18577
18771
  return fail(errMsg(err));
18578
18772
  }
18579
18773
  }
@@ -18600,7 +18794,9 @@ function registerComposeTools(server, ctx) {
18600
18794
  const draftEmailOutputSchema = {
18601
18795
  draft: z9.literal(true),
18602
18796
  id: z9.string(),
18603
- draftHtml: z9.string().optional()
18797
+ draftHtml: z9.string().optional(),
18798
+ warning: z9.string().optional(),
18799
+ draftReadbackError: z9.string().optional()
18604
18800
  };
18605
18801
  if (shouldRegister("draft_email", tools)) {
18606
18802
  server.registerTool(
@@ -18797,13 +18993,13 @@ function registerTools(server, opts) {
18797
18993
  registerBrowseTools(server, { store, registry, tools, logger });
18798
18994
  registerFolderTools(server, { registry, tools });
18799
18995
  registerOrganizeTools(server, { registry, tools });
18800
- registerComposeTools(server, { store, registry, tools });
18996
+ registerComposeTools(server, { store, registry, tools, logger });
18801
18997
  }
18802
18998
 
18803
18999
  // package.json
18804
19000
  var package_default = {
18805
19001
  name: "hypermail-mcp",
18806
- version: "0.7.15",
19002
+ version: "0.7.17",
18807
19003
  description: "Unified email MCP server \u2014 operate any inbox (Outlook now, IMAP/Gmail later) by passing an email address.",
18808
19004
  type: "module",
18809
19005
  bin: {
@@ -19210,10 +19406,16 @@ function parseArgs(argv) {
19210
19406
  if (argv.length > 1) {
19211
19407
  throw new Error(`Unknown argument for generate-key: ${argv[1]}`);
19212
19408
  }
19213
- return { command: "generate-key", help: false, overrides: {} };
19409
+ return {
19410
+ command: "generate-key",
19411
+ help: false,
19412
+ version: false,
19413
+ overrides: {}
19414
+ };
19214
19415
  }
19215
19416
  const out = {
19216
19417
  help: false,
19418
+ version: false,
19217
19419
  overrides: {}
19218
19420
  };
19219
19421
  for (let i = 0; i < argv.length; i++) {
@@ -19244,6 +19446,9 @@ function parseArgs(argv) {
19244
19446
  case "--help":
19245
19447
  out.help = true;
19246
19448
  break;
19449
+ case "--version":
19450
+ out.version = true;
19451
+ break;
19247
19452
  default:
19248
19453
  if (arg.startsWith("-")) {
19249
19454
  throw new Error(`Unknown option: ${arg}`);
@@ -19268,6 +19473,7 @@ Options:
19268
19473
  --port <n> HTTP port (default: 3000)
19269
19474
  --host <addr> HTTP bind address (default: 127.0.0.1)
19270
19475
  --data-dir <path> Where to store the encrypted accounts file
19476
+ --version Show package version
19271
19477
  -h, --help Show this help
19272
19478
 
19273
19479
  Configuration:
@@ -19311,6 +19517,11 @@ async function main() {
19311
19517
  process.stdout.write(helpText());
19312
19518
  return;
19313
19519
  }
19520
+ if (opts.version) {
19521
+ process.stdout.write(`${VERSION}
19522
+ `);
19523
+ return;
19524
+ }
19314
19525
  const { config, warnings } = loadConfig(opts.overrides);
19315
19526
  for (const warning of warnings) {
19316
19527
  process.stderr.write(`[hypermail-mcp] warning: ${warning}