@gethmy/mcp 2.9.2 → 2.9.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/dist/cli.js CHANGED
@@ -695,6 +695,10 @@ function getMemoryDir() {
695
695
  return config.memoryDir;
696
696
  return join(homedir(), ".harmony", "memory");
697
697
  }
698
+
699
+ // src/server.ts
700
+ import { readFile } from "node:fs/promises";
701
+ import { basename } from "node:path";
698
702
  // ../memory/dist/sync.js
699
703
  import { createHash } from "node:crypto";
700
704
  import { existsSync as existsSync2, mkdirSync as mkdirSync2, readdirSync, readFileSync as readFileSync2, rmSync, writeFileSync as writeFileSync2 } from "node:fs";
@@ -1523,6 +1527,9 @@ class HarmonyApiClient {
1523
1527
  async getCardAttachments(cardId) {
1524
1528
  return this.request("GET", `/cards/${cardId}/attachments`);
1525
1529
  }
1530
+ async uploadCardAttachment(cardId, data) {
1531
+ return this.request("POST", `/cards/${cardId}/attachments`, data);
1532
+ }
1526
1533
  async getCardExternalLinks(cardId) {
1527
1534
  return this.request("GET", `/cards/${cardId}/external-links`);
1528
1535
  }
@@ -3485,6 +3492,32 @@ var TOOLS = {
3485
3492
  required: ["cardId"]
3486
3493
  }
3487
3494
  },
3495
+ harmony_upload_card_attachment: {
3496
+ description: "Upload a file attachment to a card (e.g. a pasted screenshot or a document). Provide the file either as `filePath` (a local path the MCP server can read — works in local/stdio mode) or as `base64Data` (raw base64 bytes — works everywhere, including remote mode). Max 5MB. Allowed: PNG, JPEG, GIF, WebP, HEIC/HEIF, PDF, DOC/DOCX, XLS/XLSX, TXT. Returns the stored attachment with a short-lived signed URL.",
3497
+ inputSchema: {
3498
+ type: "object",
3499
+ properties: {
3500
+ cardId: { type: "string", description: "Card UUID" },
3501
+ filePath: {
3502
+ type: "string",
3503
+ description: "Absolute path to a local file the MCP server process can read. Mutually exclusive with base64Data."
3504
+ },
3505
+ base64Data: {
3506
+ type: "string",
3507
+ description: "Base64-encoded file bytes (a `data:` URL prefix is accepted and stripped). Requires fileName. Mutually exclusive with filePath."
3508
+ },
3509
+ fileName: {
3510
+ type: "string",
3511
+ description: "File name including extension (e.g. 'screenshot.png'). Required with base64Data; defaults to the basename of filePath otherwise."
3512
+ },
3513
+ contentType: {
3514
+ type: "string",
3515
+ description: "Optional MIME type (e.g. 'image/png'). Inferred from the file extension when omitted."
3516
+ }
3517
+ },
3518
+ required: ["cardId"]
3519
+ }
3520
+ },
3488
3521
  harmony_get_card_external_links: {
3489
3522
  description: "Get external URL references attached to a card (links to docs, gists, dashboards, etc.).",
3490
3523
  inputSchema: {
@@ -4903,6 +4936,38 @@ async function handleToolCall(name, args, deps) {
4903
4936
  const result = await client3.getCardAttachments(cardId);
4904
4937
  return result;
4905
4938
  }
4939
+ case "harmony_upload_card_attachment": {
4940
+ const cardId = z.string().uuid().parse(args.cardId);
4941
+ const filePath = args.filePath != null ? z.string().parse(args.filePath) : undefined;
4942
+ const base64Data = args.base64Data != null ? z.string().parse(args.base64Data) : undefined;
4943
+ let fileName = args.fileName != null ? z.string().parse(args.fileName) : undefined;
4944
+ const contentType = args.contentType != null ? z.string().parse(args.contentType) : undefined;
4945
+ if (filePath && base64Data) {
4946
+ throw new Error("Provide either filePath or base64Data, not both.");
4947
+ }
4948
+ let data;
4949
+ if (filePath) {
4950
+ const bytes = await readFile(filePath);
4951
+ if (bytes.byteLength === 0) {
4952
+ throw new Error(`File is empty: ${filePath}`);
4953
+ }
4954
+ data = bytes.toString("base64");
4955
+ fileName = fileName || basename(filePath);
4956
+ } else if (base64Data) {
4957
+ if (!fileName) {
4958
+ throw new Error("fileName is required when using base64Data.");
4959
+ }
4960
+ data = base64Data;
4961
+ } else {
4962
+ throw new Error("Provide either filePath or base64Data.");
4963
+ }
4964
+ const result = await client3.uploadCardAttachment(cardId, {
4965
+ fileName,
4966
+ data,
4967
+ fileType: contentType
4968
+ });
4969
+ return result;
4970
+ }
4906
4971
  case "harmony_get_card_external_links": {
4907
4972
  const cardId = z.string().uuid().parse(args.cardId);
4908
4973
  const result = await client3.getCardExternalLinks(cardId);
package/dist/index.js CHANGED
@@ -506,6 +506,10 @@ var init_prompt_builder = __esm(() => {
506
506
  execute: `EXECUTE MODE: Implement this task completely. Write production-ready code following best practices. Include necessary tests and documentation.`
507
507
  };
508
508
  });
509
+
510
+ // src/server.ts
511
+ import { readFile } from "node:fs/promises";
512
+ import { basename } from "node:path";
509
513
  // ../memory/dist/sync.js
510
514
  import { createHash } from "node:crypto";
511
515
  import { existsSync, mkdirSync, readdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
@@ -1519,6 +1523,9 @@ class HarmonyApiClient {
1519
1523
  async getCardAttachments(cardId) {
1520
1524
  return this.request("GET", `/cards/${cardId}/attachments`);
1521
1525
  }
1526
+ async uploadCardAttachment(cardId, data) {
1527
+ return this.request("POST", `/cards/${cardId}/attachments`, data);
1528
+ }
1522
1529
  async getCardExternalLinks(cardId) {
1523
1530
  return this.request("GET", `/cards/${cardId}/external-links`);
1524
1531
  }
@@ -3481,6 +3488,32 @@ var TOOLS = {
3481
3488
  required: ["cardId"]
3482
3489
  }
3483
3490
  },
3491
+ harmony_upload_card_attachment: {
3492
+ description: "Upload a file attachment to a card (e.g. a pasted screenshot or a document). Provide the file either as `filePath` (a local path the MCP server can read — works in local/stdio mode) or as `base64Data` (raw base64 bytes — works everywhere, including remote mode). Max 5MB. Allowed: PNG, JPEG, GIF, WebP, HEIC/HEIF, PDF, DOC/DOCX, XLS/XLSX, TXT. Returns the stored attachment with a short-lived signed URL.",
3493
+ inputSchema: {
3494
+ type: "object",
3495
+ properties: {
3496
+ cardId: { type: "string", description: "Card UUID" },
3497
+ filePath: {
3498
+ type: "string",
3499
+ description: "Absolute path to a local file the MCP server process can read. Mutually exclusive with base64Data."
3500
+ },
3501
+ base64Data: {
3502
+ type: "string",
3503
+ description: "Base64-encoded file bytes (a `data:` URL prefix is accepted and stripped). Requires fileName. Mutually exclusive with filePath."
3504
+ },
3505
+ fileName: {
3506
+ type: "string",
3507
+ description: "File name including extension (e.g. 'screenshot.png'). Required with base64Data; defaults to the basename of filePath otherwise."
3508
+ },
3509
+ contentType: {
3510
+ type: "string",
3511
+ description: "Optional MIME type (e.g. 'image/png'). Inferred from the file extension when omitted."
3512
+ }
3513
+ },
3514
+ required: ["cardId"]
3515
+ }
3516
+ },
3484
3517
  harmony_get_card_external_links: {
3485
3518
  description: "Get external URL references attached to a card (links to docs, gists, dashboards, etc.).",
3486
3519
  inputSchema: {
@@ -4899,6 +4932,38 @@ async function handleToolCall(name, args, deps) {
4899
4932
  const result = await client3.getCardAttachments(cardId);
4900
4933
  return result;
4901
4934
  }
4935
+ case "harmony_upload_card_attachment": {
4936
+ const cardId = z.string().uuid().parse(args.cardId);
4937
+ const filePath = args.filePath != null ? z.string().parse(args.filePath) : undefined;
4938
+ const base64Data = args.base64Data != null ? z.string().parse(args.base64Data) : undefined;
4939
+ let fileName = args.fileName != null ? z.string().parse(args.fileName) : undefined;
4940
+ const contentType = args.contentType != null ? z.string().parse(args.contentType) : undefined;
4941
+ if (filePath && base64Data) {
4942
+ throw new Error("Provide either filePath or base64Data, not both.");
4943
+ }
4944
+ let data;
4945
+ if (filePath) {
4946
+ const bytes = await readFile(filePath);
4947
+ if (bytes.byteLength === 0) {
4948
+ throw new Error(`File is empty: ${filePath}`);
4949
+ }
4950
+ data = bytes.toString("base64");
4951
+ fileName = fileName || basename(filePath);
4952
+ } else if (base64Data) {
4953
+ if (!fileName) {
4954
+ throw new Error("fileName is required when using base64Data.");
4955
+ }
4956
+ data = base64Data;
4957
+ } else {
4958
+ throw new Error("Provide either filePath or base64Data.");
4959
+ }
4960
+ const result = await client3.uploadCardAttachment(cardId, {
4961
+ fileName,
4962
+ data,
4963
+ fileType: contentType
4964
+ });
4965
+ return result;
4966
+ }
4902
4967
  case "harmony_get_card_external_links": {
4903
4968
  const cardId = z.string().uuid().parse(args.cardId);
4904
4969
  const result = await client3.getCardExternalLinks(cardId);
@@ -1126,6 +1126,9 @@ class HarmonyApiClient {
1126
1126
  async getCardAttachments(cardId) {
1127
1127
  return this.request("GET", `/cards/${cardId}/attachments`);
1128
1128
  }
1129
+ async uploadCardAttachment(cardId, data) {
1130
+ return this.request("POST", `/cards/${cardId}/attachments`, data);
1131
+ }
1129
1132
  async getCardExternalLinks(cardId) {
1130
1133
  return this.request("GET", `/cards/${cardId}/external-links`);
1131
1134
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gethmy/mcp",
3
- "version": "2.9.2",
3
+ "version": "2.9.3",
4
4
  "description": "MCP server for Harmony Kanban board - enables AI coding agents to manage your boards",
5
5
  "publishConfig": {
6
6
  "access": "public"
package/src/api-client.ts CHANGED
@@ -583,6 +583,13 @@ export class HarmonyApiClient {
583
583
  return this.request("GET", `/cards/${cardId}/attachments`);
584
584
  }
585
585
 
586
+ async uploadCardAttachment(
587
+ cardId: string,
588
+ data: { fileName: string; data: string; fileType?: string },
589
+ ): Promise<{ attachment: CardAttachment }> {
590
+ return this.request("POST", `/cards/${cardId}/attachments`, data);
591
+ }
592
+
586
593
  async getCardExternalLinks(
587
594
  cardId: string,
588
595
  ): Promise<{ external_links: CardExternalLinkRow[] }> {
package/src/server.ts CHANGED
@@ -1,3 +1,5 @@
1
+ import { readFile } from "node:fs/promises";
2
+ import { basename } from "node:path";
1
3
  import { syncFull, syncPull, syncPush } from "@harmony/memory";
2
4
  import { Server } from "@modelcontextprotocol/sdk/server/index.js";
3
5
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
@@ -616,6 +618,37 @@ export const TOOLS = {
616
618
  required: ["cardId"],
617
619
  },
618
620
  },
621
+ harmony_upload_card_attachment: {
622
+ description:
623
+ "Upload a file attachment to a card (e.g. a pasted screenshot or a document). Provide the file either as `filePath` (a local path the MCP server can read — works in local/stdio mode) or as `base64Data` (raw base64 bytes — works everywhere, including remote mode). Max 5MB. Allowed: PNG, JPEG, GIF, WebP, HEIC/HEIF, PDF, DOC/DOCX, XLS/XLSX, TXT. Returns the stored attachment with a short-lived signed URL.",
624
+ inputSchema: {
625
+ type: "object",
626
+ properties: {
627
+ cardId: { type: "string", description: "Card UUID" },
628
+ filePath: {
629
+ type: "string",
630
+ description:
631
+ "Absolute path to a local file the MCP server process can read. Mutually exclusive with base64Data.",
632
+ },
633
+ base64Data: {
634
+ type: "string",
635
+ description:
636
+ "Base64-encoded file bytes (a `data:` URL prefix is accepted and stripped). Requires fileName. Mutually exclusive with filePath.",
637
+ },
638
+ fileName: {
639
+ type: "string",
640
+ description:
641
+ "File name including extension (e.g. 'screenshot.png'). Required with base64Data; defaults to the basename of filePath otherwise.",
642
+ },
643
+ contentType: {
644
+ type: "string",
645
+ description:
646
+ "Optional MIME type (e.g. 'image/png'). Inferred from the file extension when omitted.",
647
+ },
648
+ },
649
+ required: ["cardId"],
650
+ },
651
+ },
619
652
  harmony_get_card_external_links: {
620
653
  description:
621
654
  "Get external URL references attached to a card (links to docs, gists, dashboards, etc.).",
@@ -2344,6 +2377,48 @@ async function handleToolCall(
2344
2377
  return result;
2345
2378
  }
2346
2379
 
2380
+ case "harmony_upload_card_attachment": {
2381
+ const cardId = z.string().uuid().parse(args.cardId);
2382
+ const filePath =
2383
+ args.filePath != null ? z.string().parse(args.filePath) : undefined;
2384
+ const base64Data =
2385
+ args.base64Data != null ? z.string().parse(args.base64Data) : undefined;
2386
+ let fileName =
2387
+ args.fileName != null ? z.string().parse(args.fileName) : undefined;
2388
+ const contentType =
2389
+ args.contentType != null
2390
+ ? z.string().parse(args.contentType)
2391
+ : undefined;
2392
+
2393
+ if (filePath && base64Data) {
2394
+ throw new Error("Provide either filePath or base64Data, not both.");
2395
+ }
2396
+
2397
+ let data: string;
2398
+ if (filePath) {
2399
+ const bytes = await readFile(filePath);
2400
+ if (bytes.byteLength === 0) {
2401
+ throw new Error(`File is empty: ${filePath}`);
2402
+ }
2403
+ data = bytes.toString("base64");
2404
+ fileName = fileName || basename(filePath);
2405
+ } else if (base64Data) {
2406
+ if (!fileName) {
2407
+ throw new Error("fileName is required when using base64Data.");
2408
+ }
2409
+ data = base64Data;
2410
+ } else {
2411
+ throw new Error("Provide either filePath or base64Data.");
2412
+ }
2413
+
2414
+ const result = await client.uploadCardAttachment(cardId, {
2415
+ fileName: fileName as string,
2416
+ data,
2417
+ fileType: contentType,
2418
+ });
2419
+ return result;
2420
+ }
2421
+
2347
2422
  case "harmony_get_card_external_links": {
2348
2423
  const cardId = z.string().uuid().parse(args.cardId);
2349
2424
  const result = await client.getCardExternalLinks(cardId);