hypermail-mcp 0.7.1 → 0.7.2

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,7 +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.7.1** — Every config field is now settable via a dedicated
6
+ > **v0.7.2** — `add_attachment_to_draft` now accepts `filePath` (absolute path to
7
+ a local file) as an alternative to `contentBytes`. The file is read from disk and
8
+ base64-encoded automatically, the MIME type is inferred from the extension, and
9
+ `name` defaults to the file's basename.
10
+ >> **v0.7.1** — Every config field is now settable via a dedicated
7
11
  > `HYPERMAIL_*` env var. Legacy env vars (`MS_CLIENT_ID`, `MS_TENANT_ID`,
8
12
  > `GOOGLE_CLIENT_ID`, `GOOGLE_CLIENT_SECRET`) still work as fallbacks. See
9
13
  > [Environment Variables](#environment-variables) for the full reference.
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({
@@ -3506,6 +3508,31 @@ function registerComposeTools(server, ctx) {
3506
3508
  }
3507
3509
  );
3508
3510
  }
3511
+ const MIME_TYPES = {
3512
+ ".pdf": "application/pdf",
3513
+ ".png": "image/png",
3514
+ ".jpg": "image/jpeg",
3515
+ ".jpeg": "image/jpeg",
3516
+ ".gif": "image/gif",
3517
+ ".svg": "image/svg+xml",
3518
+ ".webp": "image/webp",
3519
+ ".txt": "text/plain",
3520
+ ".html": "text/html",
3521
+ ".css": "text/css",
3522
+ ".csv": "text/csv",
3523
+ ".json": "application/json",
3524
+ ".xml": "application/xml",
3525
+ ".zip": "application/zip",
3526
+ ".gz": "application/gzip",
3527
+ ".tar": "application/x-tar",
3528
+ ".doc": "application/msword",
3529
+ ".docx": "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
3530
+ ".xls": "application/vnd.ms-excel",
3531
+ ".xlsx": "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
3532
+ ".pptx": "application/vnd.openxmlformats-officedocument.presentationml.presentation",
3533
+ ".mp3": "audio/mpeg",
3534
+ ".mp4": "video/mp4"
3535
+ };
3509
3536
  const addAttachmentOutputSchema = {
3510
3537
  attached: z6.literal(true),
3511
3538
  id: z6.string(),
@@ -3519,25 +3546,48 @@ function registerComposeTools(server, ctx) {
3519
3546
  server.registerTool(
3520
3547
  "add_attachment_to_draft",
3521
3548
  {
3522
- description: "Add a file attachment to an existing draft email by ID. `contentBytes` must be base64-encoded file content. `contentType` is the MIME type (e.g. 'application/pdf'); defaults to 'application/octet-stream' if omitted. Disabled in --read-only mode.",
3523
- inputSchema: {
3549
+ 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.",
3550
+ inputSchema: z6.object({
3524
3551
  account: z6.string().email(),
3525
3552
  id: z6.string().min(1).describe("Draft message ID"),
3526
- name: z6.string().min(1).describe("Attachment filename (e.g. 'report.pdf')"),
3527
- contentBytes: z6.string().min(1).describe("Base64-encoded file content"),
3528
- contentType: z6.string().optional().describe("MIME type (e.g. 'application/pdf')")
3529
- },
3553
+ name: z6.string().min(1).optional().describe(
3554
+ "Attachment filename (e.g. 'report.pdf'). Defaults to the file's basename when `filePath` is used."
3555
+ ),
3556
+ contentBytes: z6.string().min(1).optional().describe("Base64-encoded file content. Mutually exclusive with `filePath`."),
3557
+ filePath: z6.string().min(1).optional().describe(
3558
+ "Absolute path to a local file. The file is read and base64-encoded automatically. Mutually exclusive with `contentBytes`."
3559
+ ),
3560
+ contentType: z6.string().optional().describe("MIME type (e.g. 'application/pdf'). Inferred from `filePath` extension if omitted.")
3561
+ }).refine(
3562
+ (v) => Boolean(v.contentBytes) !== Boolean(v.filePath),
3563
+ { message: "Provide exactly one of `contentBytes` or `filePath`." }
3564
+ ),
3530
3565
  outputSchema: addAttachmentOutputSchema
3531
3566
  },
3532
3567
  async (args) => {
3533
3568
  try {
3534
3569
  const { provider, account } = registry.resolveByEmail(args.account);
3570
+ let contentBytes;
3571
+ let name;
3572
+ let contentType = args.contentType;
3573
+ if (args.filePath) {
3574
+ const fileData = readFileSync(args.filePath);
3575
+ contentBytes = fileData.toString("base64");
3576
+ name = args.name ?? basename(args.filePath);
3577
+ if (!contentType) {
3578
+ const ext = extname(args.filePath).toLowerCase();
3579
+ contentType = MIME_TYPES[ext] ?? "application/octet-stream";
3580
+ }
3581
+ } else {
3582
+ contentBytes = args.contentBytes;
3583
+ name = args.name;
3584
+ }
3535
3585
  const res = await provider.addAttachmentToDraft(
3536
3586
  account,
3537
3587
  args.id,
3538
- args.name,
3539
- args.contentBytes,
3540
- args.contentType
3588
+ name,
3589
+ contentBytes,
3590
+ contentType
3541
3591
  );
3542
3592
  const data = {
3543
3593
  attached: true,
@@ -3566,7 +3616,7 @@ function registerTools(server, opts) {
3566
3616
  // package.json
3567
3617
  var package_default = {
3568
3618
  name: "hypermail-mcp",
3569
- version: "0.7.1",
3619
+ version: "0.7.2",
3570
3620
  description: "Unified email MCP server \u2014 operate any inbox (Outlook now, IMAP/Gmail later) by passing an email address.",
3571
3621
  type: "module",
3572
3622
  bin: {
@@ -3747,7 +3797,7 @@ var WatcherManager = class {
3747
3797
  };
3748
3798
 
3749
3799
  // src/config.ts
3750
- import { readFileSync } from "fs";
3800
+ import { readFileSync as readFileSync2 } from "fs";
3751
3801
  import { z as z7 } from "zod";
3752
3802
  var httpConfigSchema = z7.object({
3753
3803
  enabled: z7.boolean().default(false),
@@ -3886,7 +3936,7 @@ function loadConfig(configPath, cliOverrides = {}) {
3886
3936
  let raw = {};
3887
3937
  if (configPath) {
3888
3938
  try {
3889
- raw = JSON.parse(readFileSync(configPath, "utf-8"));
3939
+ raw = JSON.parse(readFileSync2(configPath, "utf-8"));
3890
3940
  } catch (err) {
3891
3941
  const detail = err instanceof SyntaxError ? "Invalid JSON" : err instanceof Error ? err.message : String(err);
3892
3942
  throw new Error(`Failed to read config file "${configPath}": ${detail}`);