hypermail-mcp 0.4.2 → 0.4.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 CHANGED
@@ -3,6 +3,10 @@
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.4.3** — Upgraded Zod to v4.4.3. Fixed MCP SDK v1.29.0 compatibility
7
+ > by wrapping all tool schemas in `z.object()` and replacing discriminated
8
+ > union output schemas that caused `validateToolOutput` crashes.
9
+
6
10
  The agent doesn't care whether an address is a work Outlook account, a personal
7
11
  Microsoft account, or (soon) a personal IMAP mailbox — it just calls
8
12
  `list_emails`, `search_emails`, `read_email`, `send_email` and passes the email
package/dist/cli.js CHANGED
@@ -2504,7 +2504,7 @@ var accountFullOutputSchema = z.object({
2504
2504
  email: z.string(),
2505
2505
  provider: providerIdEnum,
2506
2506
  displayName: z.string().optional(),
2507
- tokens: z.record(z.unknown()),
2507
+ tokens: z.record(z.string(), z.unknown()),
2508
2508
  addedAt: z.string(),
2509
2509
  signature: z.string().optional(),
2510
2510
  style: styleOutputSchema.optional()
@@ -2578,15 +2578,15 @@ function shouldRegister(name, tools) {
2578
2578
  import { z as z2 } from "zod";
2579
2579
  function registerAccountTools(server, ctx) {
2580
2580
  const { store, registry, tools } = ctx;
2581
- const listAccountsOutputSchema = {
2581
+ const listAccountsOutputSchema = z2.object({
2582
2582
  accounts: z2.array(accountSummaryOutputSchema)
2583
- };
2583
+ });
2584
2584
  if (shouldRegister("list_accounts", tools)) {
2585
2585
  server.registerTool(
2586
2586
  "list_accounts",
2587
2587
  {
2588
2588
  description: "List all email accounts known to this server (no secrets). Use the returned `email` value as the `account` argument to other tools.",
2589
- inputSchema: {},
2589
+ inputSchema: z2.object({}),
2590
2590
  outputSchema: listAccountsOutputSchema
2591
2591
  },
2592
2592
  async () => {
@@ -2603,36 +2603,31 @@ function registerAccountTools(server, ctx) {
2603
2603
  }
2604
2604
  );
2605
2605
  }
2606
- const addAccountOutputSchema = z2.discriminatedUnion("status", [
2607
- z2.object({
2608
- status: z2.literal("pending"),
2609
- handle: z2.string(),
2610
- verification: z2.object({
2611
- userCode: z2.string(),
2612
- verificationUri: z2.string(),
2613
- expiresAt: z2.string(),
2614
- message: z2.string()
2615
- })
2616
- }),
2617
- z2.object({
2618
- status: z2.literal("ready"),
2619
- account: accountFullOutputSchema
2620
- })
2621
- ]);
2606
+ const addAccountOutputSchema = z2.object({
2607
+ status: z2.enum(["pending", "ready"]),
2608
+ handle: z2.string().optional(),
2609
+ verification: z2.object({
2610
+ userCode: z2.string(),
2611
+ verificationUri: z2.string(),
2612
+ expiresAt: z2.string(),
2613
+ message: z2.string()
2614
+ }).optional(),
2615
+ account: accountFullOutputSchema.optional()
2616
+ });
2622
2617
  if (shouldRegister("add_account", tools)) {
2623
2618
  server.registerTool(
2624
2619
  "add_account",
2625
2620
  {
2626
2621
  description: "Start adding an email account. For Outlook this returns a device code the user must enter at the verification URL; then call `complete_add_account` with the returned `handle` to finalize. Disabled in --read-only mode.",
2627
- inputSchema: {
2622
+ inputSchema: z2.object({
2628
2623
  provider: providerIdEnum.describe("Email backend. 'outlook' (Microsoft Graph) and 'imap' are fully implemented."),
2629
2624
  email: z2.string().email().optional().describe(
2630
2625
  "Optional hint \u2014 the provider will verify it against the auth result."
2631
2626
  ),
2632
- config: z2.record(z2.unknown()).optional().describe(
2627
+ config: z2.record(z2.string(), z2.unknown()).optional().describe(
2633
2628
  "Provider-specific config (e.g. IMAP host/port). Unused for Outlook."
2634
2629
  )
2635
- },
2630
+ }),
2636
2631
  outputSchema: addAccountOutputSchema
2637
2632
  },
2638
2633
  async (args) => {
@@ -2659,10 +2654,10 @@ function registerAccountTools(server, ctx) {
2659
2654
  "complete_add_account",
2660
2655
  {
2661
2656
  description: "Poll/finalize a pending add_account flow. Returns `pending` until the user completes the device-code step, then `ready` with the persisted account.",
2662
- inputSchema: {
2657
+ inputSchema: z2.object({
2663
2658
  provider: providerIdEnum,
2664
2659
  handle: z2.string().min(1)
2665
- },
2660
+ }),
2666
2661
  outputSchema: completeAddAccountOutputSchema
2667
2662
  },
2668
2663
  async (args) => {
@@ -2681,16 +2676,16 @@ function registerAccountTools(server, ctx) {
2681
2676
  }
2682
2677
  );
2683
2678
  }
2684
- const accountSettingsOutputSchema = {
2679
+ const accountSettingsOutputSchema = z2.object({
2685
2680
  signature: z2.string().nullable(),
2686
2681
  style: styleOutputSchema.nullable()
2687
- };
2682
+ });
2688
2683
  if (shouldRegister("get_account_settings", tools)) {
2689
2684
  server.registerTool(
2690
2685
  "get_account_settings",
2691
2686
  {
2692
2687
  description: "Get signature (HTML) and style preferences for an account.",
2693
- inputSchema: { account: z2.string().email() },
2688
+ inputSchema: z2.object({ account: z2.string().email() }),
2694
2689
  outputSchema: accountSettingsOutputSchema
2695
2690
  },
2696
2691
  async (args) => {
@@ -2714,7 +2709,7 @@ function registerAccountTools(server, ctx) {
2714
2709
  "set_account_settings",
2715
2710
  {
2716
2711
  description: "Set signature (HTML snippet) and/or style preferences for an account. Disabled in --read-only mode.",
2717
- inputSchema: {
2712
+ inputSchema: z2.object({
2718
2713
  account: z2.string().email(),
2719
2714
  signature: z2.string().optional().describe(
2720
2715
  "HTML snippet \u2014 may contain formatting, images, links. Pass null to clear."
@@ -2726,7 +2721,7 @@ function registerAccountTools(server, ctx) {
2726
2721
  }).optional().describe(
2727
2722
  "Font preferences applied to outgoing HTML emails. Pass null to clear."
2728
2723
  )
2729
- },
2724
+ }),
2730
2725
  outputSchema: accountSettingsOutputSchema
2731
2726
  },
2732
2727
  async (args) => {
@@ -2750,16 +2745,16 @@ function registerAccountTools(server, ctx) {
2750
2745
  }
2751
2746
  );
2752
2747
  }
2753
- const removeAccountOutputSchema = {
2748
+ const removeAccountOutputSchema = z2.object({
2754
2749
  removed: z2.boolean(),
2755
2750
  email: z2.string()
2756
- };
2751
+ });
2757
2752
  if (shouldRegister("remove_account", tools)) {
2758
2753
  server.registerTool(
2759
2754
  "remove_account",
2760
2755
  {
2761
2756
  description: "Forget an account and delete its stored tokens. Disabled in --read-only mode.",
2762
- inputSchema: { email: z2.string().email() },
2757
+ inputSchema: z2.object({ email: z2.string().email() }),
2763
2758
  outputSchema: removeAccountOutputSchema
2764
2759
  },
2765
2760
  async (args) => {
@@ -2803,30 +2798,30 @@ function selectBody(msg, format) {
2803
2798
  // src/tools/browse.ts
2804
2799
  function registerBrowseTools(server, ctx) {
2805
2800
  const { registry, tools } = ctx;
2806
- const emailListOutputSchema = {
2801
+ const emailListOutputSchema = z3.object({
2807
2802
  account: z3.string(),
2808
2803
  count: z3.number(),
2809
2804
  items: z3.array(emailSummaryOutputSchema),
2810
2805
  skip: z3.number(),
2811
2806
  hasMore: z3.boolean()
2812
- };
2813
- const searchEmailsOutputSchema = {
2807
+ });
2808
+ const searchEmailsOutputSchema = z3.object({
2814
2809
  account: z3.string(),
2815
2810
  count: z3.number(),
2816
2811
  items: z3.array(emailSummaryOutputSchema)
2817
- };
2812
+ });
2818
2813
  if (shouldRegister("list_emails", tools)) {
2819
2814
  server.registerTool(
2820
2815
  "list_emails",
2821
2816
  {
2822
2817
  description: "List recent emails in a folder of the given account. Pass the user's email address as `account`; the server routes to the correct backend automatically.",
2823
- inputSchema: {
2818
+ inputSchema: z3.object({
2824
2819
  account: z3.string().email(),
2825
2820
  folder: z3.string().default("inbox").optional(),
2826
2821
  limit: z3.number().int().positive().max(100).optional(),
2827
2822
  unreadOnly: z3.boolean().optional(),
2828
2823
  skip: z3.number().int().min(0).optional()
2829
- },
2824
+ }),
2830
2825
  outputSchema: emailListOutputSchema
2831
2826
  },
2832
2827
  async (args) => {
@@ -2857,11 +2852,11 @@ function registerBrowseTools(server, ctx) {
2857
2852
  "search_emails",
2858
2853
  {
2859
2854
  description: "Search emails by free-text query (KQL on Outlook). Returns lightweight summaries.",
2860
- inputSchema: {
2855
+ inputSchema: z3.object({
2861
2856
  account: z3.string().email(),
2862
2857
  query: z3.string().min(1),
2863
2858
  limit: z3.number().int().positive().max(100).optional()
2864
- },
2859
+ }),
2865
2860
  outputSchema: searchEmailsOutputSchema
2866
2861
  },
2867
2862
  async (args) => {
@@ -2882,7 +2877,7 @@ function registerBrowseTools(server, ctx) {
2882
2877
  }
2883
2878
  );
2884
2879
  }
2885
- const readEmailOutputSchema = {
2880
+ const readEmailOutputSchema = z3.object({
2886
2881
  id: z3.string(),
2887
2882
  subject: z3.string(),
2888
2883
  from: emailAddrOutputSchema.optional(),
@@ -2897,19 +2892,19 @@ function registerBrowseTools(server, ctx) {
2897
2892
  attachments: z3.array(attachmentMetaOutputSchema).optional(),
2898
2893
  body: z3.string(),
2899
2894
  bodyFormat: z3.enum(["markdown", "html", "text"])
2900
- };
2895
+ });
2901
2896
  if (shouldRegister("read_email", tools)) {
2902
2897
  server.registerTool(
2903
2898
  "read_email",
2904
2899
  {
2905
2900
  description: "Fetch a single email with full body and recipients by id. Body is returned as `body` with `bodyFormat` indicating the format. Default format is 'markdown' \u2014 HTML is automatically converted to save context tokens.",
2906
- inputSchema: {
2901
+ inputSchema: z3.object({
2907
2902
  account: z3.string().email(),
2908
2903
  id: z3.string().min(1),
2909
2904
  format: z3.enum(["markdown", "html", "text"]).default("markdown").optional().describe(
2910
2905
  "Output body format. 'markdown' converts HTML to Markdown (default), 'html' returns the raw HTML, 'text' returns plain text."
2911
2906
  )
2912
- },
2907
+ }),
2913
2908
  outputSchema: readEmailOutputSchema
2914
2909
  },
2915
2910
  async (args) => {
@@ -2941,21 +2936,21 @@ function registerBrowseTools(server, ctx) {
2941
2936
  }
2942
2937
  );
2943
2938
  }
2944
- const readAttachmentOutputSchema = {
2939
+ const readAttachmentOutputSchema = z3.object({
2945
2940
  name: z3.string(),
2946
2941
  contentType: z3.string().optional(),
2947
2942
  path: z3.string()
2948
- };
2943
+ });
2949
2944
  if (shouldRegister("read_attachment", tools)) {
2950
2945
  server.registerTool(
2951
2946
  "read_attachment",
2952
2947
  {
2953
2948
  description: "Download an email attachment to a temporary file and return its path. Use messageId and attachmentId from a prior read_email call.",
2954
- inputSchema: {
2949
+ inputSchema: z3.object({
2955
2950
  account: z3.string().email(),
2956
2951
  messageId: z3.string().min(1),
2957
2952
  attachmentId: z3.string().min(1)
2958
- },
2953
+ }),
2959
2954
  outputSchema: readAttachmentOutputSchema
2960
2955
  },
2961
2956
  async (args) => {
@@ -2979,22 +2974,22 @@ function registerBrowseTools(server, ctx) {
2979
2974
  import { z as z4 } from "zod";
2980
2975
  function registerFolderTools(server, ctx) {
2981
2976
  const { registry, tools } = ctx;
2982
- const listFoldersOutputSchema = {
2977
+ const listFoldersOutputSchema = z4.object({
2983
2978
  account: z4.string(),
2984
2979
  count: z4.number(),
2985
2980
  items: z4.array(folderInfoOutputSchema)
2986
- };
2981
+ });
2987
2982
  if (shouldRegister("list_folders", tools)) {
2988
2983
  server.registerTool(
2989
2984
  "list_folders",
2990
2985
  {
2991
2986
  description: "List available mail folders. Returns top-level folders by default, or child folders of the given parent when `parentFolderId` is provided.",
2992
- inputSchema: {
2987
+ inputSchema: z4.object({
2993
2988
  account: z4.string().email(),
2994
2989
  parentFolderId: z4.string().optional().describe(
2995
2990
  "When provided, lists child folders of this folder. When omitted, lists top-level folders (children of the root)."
2996
2991
  )
2997
- },
2992
+ }),
2998
2993
  outputSchema: listFoldersOutputSchema
2999
2994
  },
3000
2995
  async (args) => {
@@ -3015,22 +3010,22 @@ function registerFolderTools(server, ctx) {
3015
3010
  }
3016
3011
  );
3017
3012
  }
3018
- const createFolderOutputSchema = {
3013
+ const createFolderOutputSchema = z4.object({
3019
3014
  created: z4.literal(true),
3020
3015
  folder: folderInfoOutputSchema
3021
- };
3016
+ });
3022
3017
  if (shouldRegister("create_folder", tools)) {
3023
3018
  server.registerTool(
3024
3019
  "create_folder",
3025
3020
  {
3026
3021
  description: "Create a new mail folder. Creates under the root folder by default, or under the specified parent when `parentFolderId` is provided. Disabled in --read-only mode.",
3027
- inputSchema: {
3022
+ inputSchema: z4.object({
3028
3023
  account: z4.string().email(),
3029
3024
  displayName: z4.string().min(1).describe("Name of the new folder"),
3030
3025
  parentFolderId: z4.string().optional().describe(
3031
3026
  "When provided, creates the folder as a child of this folder. When omitted, creates under the root folder."
3032
3027
  )
3033
- },
3028
+ }),
3034
3029
  outputSchema: createFolderOutputSchema
3035
3030
  },
3036
3031
  async (args) => {
@@ -3048,19 +3043,19 @@ function registerFolderTools(server, ctx) {
3048
3043
  }
3049
3044
  );
3050
3045
  }
3051
- const deleteFolderOutputSchema = {
3046
+ const deleteFolderOutputSchema = z4.object({
3052
3047
  deleted: z4.literal(true),
3053
3048
  id: z4.string()
3054
- };
3049
+ });
3055
3050
  if (shouldRegister("delete_folder", tools)) {
3056
3051
  server.registerTool(
3057
3052
  "delete_folder",
3058
3053
  {
3059
3054
  description: "Delete a mail folder by ID. Disabled in --read-only mode.",
3060
- inputSchema: {
3055
+ inputSchema: z4.object({
3061
3056
  account: z4.string().email(),
3062
3057
  folderId: z4.string().min(1).describe("ID of the folder to delete")
3063
- },
3058
+ }),
3064
3059
  outputSchema: deleteFolderOutputSchema
3065
3060
  },
3066
3061
  async (args) => {
@@ -3075,20 +3070,20 @@ function registerFolderTools(server, ctx) {
3075
3070
  }
3076
3071
  );
3077
3072
  }
3078
- const renameFolderOutputSchema = {
3073
+ const renameFolderOutputSchema = z4.object({
3079
3074
  renamed: z4.literal(true),
3080
3075
  folder: folderInfoOutputSchema
3081
- };
3076
+ });
3082
3077
  if (shouldRegister("rename_folder", tools)) {
3083
3078
  server.registerTool(
3084
3079
  "rename_folder",
3085
3080
  {
3086
3081
  description: "Rename an existing mail folder. Disabled in --read-only mode.",
3087
- inputSchema: {
3082
+ inputSchema: z4.object({
3088
3083
  account: z4.string().email(),
3089
3084
  folderId: z4.string().min(1).describe("ID of the folder to rename"),
3090
3085
  newName: z4.string().min(1).describe("New display name for the folder")
3091
- },
3086
+ }),
3092
3087
  outputSchema: renameFolderOutputSchema
3093
3088
  },
3094
3089
  async (args) => {
@@ -3126,14 +3121,14 @@ function registerOrganizeTools(server, ctx) {
3126
3121
  const data = { marked: true, id: args.id, isRead };
3127
3122
  return ok(data, data);
3128
3123
  }
3129
- const archiveMoveSchema = {
3124
+ const archiveMoveSchema = z5.object({
3130
3125
  account: z5.string().email(),
3131
3126
  id: z5.string().min(1).describe("Message ID to move")
3132
- };
3133
- const archiveOutputSchema = {
3127
+ });
3128
+ const archiveOutputSchema = z5.object({
3134
3129
  archived: z5.literal(true),
3135
3130
  id: z5.string()
3136
- };
3131
+ });
3137
3132
  if (shouldRegister("archive_email", tools)) {
3138
3133
  server.registerTool(
3139
3134
  "archive_email",
@@ -3151,10 +3146,10 @@ function registerOrganizeTools(server, ctx) {
3151
3146
  }
3152
3147
  );
3153
3148
  }
3154
- const trashOutputSchema = {
3149
+ const trashOutputSchema = z5.object({
3155
3150
  trashed: z5.literal(true),
3156
3151
  id: z5.string()
3157
- };
3152
+ });
3158
3153
  if (shouldRegister("trash_email", tools)) {
3159
3154
  server.registerTool(
3160
3155
  "trash_email",
@@ -3172,23 +3167,23 @@ function registerOrganizeTools(server, ctx) {
3172
3167
  }
3173
3168
  );
3174
3169
  }
3175
- const moveEmailOutputSchema = {
3170
+ const moveEmailOutputSchema = z5.object({
3176
3171
  moved: z5.literal(true),
3177
3172
  id: z5.string(),
3178
3173
  destination: z5.string()
3179
- };
3174
+ });
3180
3175
  if (shouldRegister("move_email", tools)) {
3181
3176
  server.registerTool(
3182
3177
  "move_email",
3183
3178
  {
3184
3179
  description: "Move a message to any folder by well-known name (e.g. 'inbox', 'drafts', 'junkemail', 'sentitems', 'outbox') or custom folder ID. Disabled in --read-only mode.",
3185
- inputSchema: {
3180
+ inputSchema: z5.object({
3186
3181
  account: z5.string().email(),
3187
3182
  id: z5.string().min(1).describe("Message ID to move"),
3188
3183
  destination: z5.string().min(1).describe(
3189
3184
  "Destination folder \u2014 a well-known folder name ('archive', 'deleteditems', 'inbox', 'drafts', 'junkemail', 'sentitems', 'outbox') or a raw folder ID."
3190
3185
  )
3191
- },
3186
+ }),
3192
3187
  outputSchema: moveEmailOutputSchema
3193
3188
  },
3194
3189
  async (args) => {
@@ -3207,15 +3202,15 @@ function registerOrganizeTools(server, ctx) {
3207
3202
  }
3208
3203
  );
3209
3204
  }
3210
- const markReadInputSchema = {
3205
+ const markReadInputSchema = z5.object({
3211
3206
  account: z5.string().email(),
3212
3207
  id: z5.string().min(1).describe("Message ID to mark as read")
3213
- };
3214
- const markReadOutputSchema = {
3208
+ });
3209
+ const markReadOutputSchema = z5.object({
3215
3210
  marked: z5.literal(true),
3216
3211
  id: z5.string(),
3217
3212
  isRead: z5.boolean()
3218
- };
3213
+ });
3219
3214
  if (shouldRegister("mark_read", tools)) {
3220
3215
  server.registerTool(
3221
3216
  "mark_read",