agenthub-multiagent-mcp 1.70.0 → 1.72.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.
@@ -0,0 +1,13 @@
1
+ export declare const ZOHO_MAX_SEND_ATTACHMENTS = 10;
2
+ export declare const ZOHO_MAX_ATTACHMENT_BYTES: number;
3
+ export declare const ZOHO_MAX_SEND_ATTACHMENT_BYTES: number;
4
+ export interface ZohoOutboundAttachment {
5
+ filename: string;
6
+ content_base64: string;
7
+ }
8
+ export declare function readZohoAttachments(paths: string[] | undefined, limits?: {
9
+ count: number;
10
+ perFile: number;
11
+ perMessage: number;
12
+ }): ZohoOutboundAttachment[];
13
+ //# sourceMappingURL=zohoAttachments.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"zohoAttachments.d.ts","sourceRoot":"","sources":["../src/zohoAttachments.ts"],"names":[],"mappings":"AAWA,eAAO,MAAM,yBAAyB,KAAK,CAAC;AAC5C,eAAO,MAAM,yBAAyB,QAAmB,CAAC;AAE1D,eAAO,MAAM,8BAA8B,QAAkB,CAAC;AAE9D,MAAM,WAAW,sBAAsB;IACrC,QAAQ,EAAE,MAAM,CAAC;IACjB,cAAc,EAAE,MAAM,CAAC;CACxB;AAED,wBAAgB,mBAAmB,CACjC,KAAK,EAAE,MAAM,EAAE,GAAG,SAAS,EAC3B,MAAM;;;;CAIL,GACA,sBAAsB,EAAE,CAyC1B"}
@@ -0,0 +1,62 @@
1
+ // add-zoho-send-reply-attachments — local files for zoho_send_email.
2
+ //
3
+ // The files are on this machine; the Zoho credentials are on the server. The
4
+ // MCP reads each file and sends it base64-encoded in the one send request (the
5
+ // slack_upload_file pattern). Everything is checked here first so a bad path
6
+ // fails the tool call before any request — never a partial send. The server
7
+ // re-checks the same caps and is authoritative.
8
+ import fs from "fs";
9
+ import path from "path";
10
+ export const ZOHO_MAX_SEND_ATTACHMENTS = 10;
11
+ export const ZOHO_MAX_ATTACHMENT_BYTES = 25 * 1024 * 1024;
12
+ // 7 MB: ~9.4 MB once base64-encoded, under the 10 MB the proxy accepts.
13
+ export const ZOHO_MAX_SEND_ATTACHMENT_BYTES = 7 * 1024 * 1024;
14
+ export function readZohoAttachments(paths, limits = {
15
+ count: ZOHO_MAX_SEND_ATTACHMENTS,
16
+ perFile: ZOHO_MAX_ATTACHMENT_BYTES,
17
+ perMessage: ZOHO_MAX_SEND_ATTACHMENT_BYTES,
18
+ }) {
19
+ if (!paths || paths.length === 0)
20
+ return [];
21
+ if (paths.length > limits.count) {
22
+ throw new Error(`At most ${limits.count} attachments per message; got ${paths.length}.`);
23
+ }
24
+ // Stat everything before reading anything, so the error names the first bad
25
+ // file without having loaded the others into memory.
26
+ let total = 0;
27
+ for (const p of paths) {
28
+ // A prompt-injected agent must not be able to mail out credentials:
29
+ // refuse anything inside a dot-directory or named as a dotfile
30
+ // (~/.ssh, ~/.agenthub/state, .env, ~/.aws, …).
31
+ if (path.resolve(p).split(/[\\/]/).some((seg) => seg.startsWith(".") && seg !== "." && seg !== "..")) {
32
+ throw new Error(`Refusing to attach a hidden file or a file inside a hidden folder: ${p}`);
33
+ }
34
+ let st;
35
+ try {
36
+ st = fs.statSync(p);
37
+ }
38
+ catch {
39
+ throw new Error(`Attachment not found or unreadable: ${p}`);
40
+ }
41
+ if (!st.isFile())
42
+ throw new Error(`Attachment is not a file: ${p}`);
43
+ if (st.size > limits.perFile) {
44
+ throw new Error(`Attachment ${p} is ${st.size} bytes; the per-file cap is ${limits.perFile}.`);
45
+ }
46
+ total += st.size;
47
+ if (total > limits.perMessage) {
48
+ throw new Error(`Attachments total more than the per-message cap of ${limits.perMessage} bytes (exceeded at ${p}).`);
49
+ }
50
+ }
51
+ return paths.map((p) => {
52
+ let content;
53
+ try {
54
+ content = fs.readFileSync(p);
55
+ }
56
+ catch {
57
+ throw new Error(`Attachment not found or unreadable: ${p}`);
58
+ }
59
+ return { filename: path.basename(p), content_base64: content.toString("base64") };
60
+ });
61
+ }
62
+ //# sourceMappingURL=zohoAttachments.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"zohoAttachments.js","sourceRoot":"","sources":["../src/zohoAttachments.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=zohoAttachments.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"zohoAttachments.test.d.ts","sourceRoot":"","sources":["../src/zohoAttachments.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,51 @@
1
+ import { describe, it, expect, beforeEach, afterEach } from "vitest";
2
+ import fs from "fs";
3
+ import os from "os";
4
+ import path from "path";
5
+ import { readZohoAttachments } from "./zohoAttachments.js";
6
+ describe("readZohoAttachments", () => {
7
+ let dir;
8
+ beforeEach(() => {
9
+ dir = fs.mkdtempSync(path.join(os.tmpdir(), "zatt-"));
10
+ });
11
+ afterEach(() => {
12
+ fs.rmSync(dir, { recursive: true, force: true });
13
+ });
14
+ const file = (name, content) => {
15
+ const p = path.join(dir, name);
16
+ fs.writeFileSync(p, content);
17
+ return p;
18
+ };
19
+ it("returns base names and base64 content", () => {
20
+ const out = readZohoAttachments([file("report.docx", "docx"), file("data.xlsx", "xlsx")]);
21
+ expect(out).toEqual([
22
+ { filename: "report.docx", content_base64: Buffer.from("docx").toString("base64") },
23
+ { filename: "data.xlsx", content_base64: Buffer.from("xlsx").toString("base64") },
24
+ ]);
25
+ });
26
+ it("returns nothing for no paths", () => {
27
+ expect(readZohoAttachments(undefined)).toEqual([]);
28
+ expect(readZohoAttachments([])).toEqual([]);
29
+ });
30
+ it("fails naming a missing file", () => {
31
+ const missing = path.join(dir, "nope.pdf");
32
+ expect(() => readZohoAttachments([file("a.pdf", "x"), missing])).toThrow(missing);
33
+ });
34
+ it("refuses hidden files and files in hidden folders", () => {
35
+ fs.mkdirSync(path.join(dir, ".ssh"));
36
+ const key = path.join(dir, ".ssh", "id_ed25519");
37
+ fs.writeFileSync(key, "secret");
38
+ expect(() => readZohoAttachments([key])).toThrow(/hidden/);
39
+ expect(() => readZohoAttachments([file(".env", "K=V")])).toThrow(/hidden/);
40
+ });
41
+ it("refuses a directory", () => {
42
+ expect(() => readZohoAttachments([dir])).toThrow(/not a file/);
43
+ });
44
+ it("enforces the per-file, per-message and count caps", () => {
45
+ const limits = { count: 2, perFile: 5, perMessage: 8 };
46
+ expect(() => readZohoAttachments([file("big.pdf", "123456")], limits)).toThrow(/big\.pdf.*per-file/);
47
+ expect(() => readZohoAttachments([file("a.pdf", "12345"), file("b.pdf", "12345")], limits)).toThrow(/per-message/);
48
+ expect(() => readZohoAttachments([file("1", "1"), file("2", "2"), file("3", "3")], limits)).toThrow(/At most 2/);
49
+ });
50
+ });
51
+ //# sourceMappingURL=zohoAttachments.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"zohoAttachments.test.js","sourceRoot":"","sources":["../src/zohoAttachments.test.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agenthub-multiagent-mcp",
3
- "version": "1.70.0",
3
+ "version": "1.72.0",
4
4
  "description": "MCP server for AgentHub multi-agent communication",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -86,6 +86,14 @@
86
86
  "prior_hashes": [
87
87
  "e7cacbf45ac93de3c8b23705fd94d4f03be9f01a3970050e1c3ebf06665d04cb"
88
88
  ]
89
+ },
90
+ {
91
+ "path": "skills/zoho-mail/SKILL.md",
92
+ "name": "zoho-mail",
93
+ "description": "Read and reply to email in a registered Zoho mailbox: find the message, read its attachments, and save a threaded Reply-All draft with files attached, then verify what was saved. Written after a 2026-10-30 bid draft lost its attachments, cc and thread.",
94
+ "category": "skill",
95
+ "current_hash": "017cd03e191c847ade9b21fa356faf3bbe96515942f1580360394820dc29af55",
96
+ "prior_hashes": []
89
97
  }
90
98
  ]
91
99
  }
@@ -53,6 +53,11 @@
53
53
  "e7cacbf45ac93de3c8b23705fd94d4f03be9f01a3970050e1c3ebf06665d04cb",
54
54
  "bf4a56716ecf89534e790e66edcc88a01d0bc8d7e99e13c9c85119c8579fc393"
55
55
  ]
56
+ },
57
+ "skills/zoho-mail/SKILL.md": {
58
+ "hashes": [
59
+ "017cd03e191c847ade9b21fa356faf3bbe96515942f1580360394820dc29af55"
60
+ ]
56
61
  }
57
62
  }
58
63
  }
@@ -0,0 +1,114 @@
1
+ ---
2
+ name: zoho-mail
3
+ description: Read and reply to email in a registered Zoho mailbox with the AgentHub zoho_* tools — find a message, read its attachments, and save a threaded Reply-All draft with files attached. Use whenever you are asked to check, answer, draft, forward or attach anything in Zoho mail (e.g. sumit@contetial.com), or a teammate asks you to "reply with the attachments".
4
+ license: MIT
5
+ ---
6
+
7
+ # Zoho mail: read, attach, reply in thread
8
+
9
+ The `zoho_*` MCP tools act on a Zoho mailbox that **you** registered
10
+ (`zoho_mailbox_list` shows yours). Read this before drafting anything with
11
+ attachments: the easy mistakes here produce an email that looks right in the
12
+ tool result and is wrong in the recipient's inbox.
13
+
14
+ On 2026-10-30 a draft for a bid due in four days said "please find attached
15
+ our preparation pack" and had **no attachments, no cc, and no thread**. The
16
+ agent was on an MCP release older than 1.71.0, where `zoho_send_email` has no
17
+ attachment or reply options, and it set its own subject. Every rule below is
18
+ there to stop that happening again.
19
+
20
+ ## 0. Check you have the tools
21
+
22
+ `zoho_send_email` must accept `in_reply_to`, `folder`, `reply_all`, `cc`, `bcc`
23
+ and `attachments`. If its schema lacks them, your MCP is older than **1.71.0**.
24
+ **Stop and tell your human to restart this Claude Code session** (configs use
25
+ `npx -y agenthub-multiagent-mcp@latest`, so a restart upgrades it). Do not
26
+ work around it with a plain new email: that is the failure described above.
27
+
28
+ ## 1. Find the message you are answering
29
+
30
+ ```
31
+ zoho_search(mailbox, query, since?)
32
+ ```
33
+
34
+ - `query` is a plain term, or Zoho's typed form: `subject:<term>`,
35
+ `sender:<addr>`, `to:<addr>`, `cc:<addr>`, `entire:<term>`; combine with
36
+ `::` (e.g. `subject:Karnataka::cc:cstech.ai`).
37
+ - It searches the **last 30 days** unless you pass `since` (RFC3339).
38
+ - Each result carries `message_id` **and** `folder_id`. You need both.
39
+
40
+ `zoho_list_messages(mailbox, folder?)` lists one folder (inbox by default).
41
+ You cannot list the folders themselves (Zoho answers 401), so take folder ids
42
+ from search results. Reply to the **latest** message in the thread that you
43
+ mean to answer, not the first.
44
+
45
+ ## 2. Read it, and its attachments
46
+
47
+ - `zoho_read_message(mailbox, message_id, folder)` returns headers, body, and
48
+ an `attachments` summary (id, filename, type, size). Read `to` and `cc`
49
+ so you know who a Reply-All will reach.
50
+ - `zoho_list_attachments` lists them without downloading.
51
+ - `zoho_download_attachment(mailbox, message_id, attachment_id, folder)`
52
+ writes the file **locally** and returns `file_path`. Hand that path to a
53
+ PDF/doc/spreadsheet reader; do not ask for `inline` content except for
54
+ small text files (capped at 1 MB). Files over 25 MB fail with
55
+ `413 attachment_too_large`, never a truncated file.
56
+
57
+ ## 3. Draft the reply
58
+
59
+ ```
60
+ zoho_send_email(
61
+ mailbox="<your mailbox>",
62
+ in_reply_to="<message_id>",
63
+ folder="<folder_id of that message>",
64
+ reply_all=true,
65
+ attachments=["<absolute local path>", ...],
66
+ body_html="<div>...</div>",
67
+ mode="draft")
68
+ ```
69
+
70
+ - **Leave `subject` out.** A reply defaults to `Re: <original>` and stays in
71
+ the thread. A subject of your own, or a missing `in_reply_to`, makes a new
72
+ unthreaded email.
73
+ - **`reply_all=true`** copies the original's to + cc (minus your mailbox).
74
+ `to` / `cc` / `bcc` you pass are added on top. One bare address per entry,
75
+ never a comma-joined string.
76
+ - **`attachments` are local paths on the machine running the MCP.** At most
77
+ 10 files and **7 MB in total**, checked before anything is saved. Check
78
+ each file exists first, and attach the version the body names (v4 body, v4
79
+ file). Hidden files and anything under a hidden folder (`.env`, `~/.ssh`)
80
+ are refused.
81
+ - **`mode` defaults to `draft`.** Use `send` only if your human asked for it
82
+ and the mailbox's `send_mode` is `direct`.
83
+ - `body_html` keeps paragraphs and lists; plain `body_text` loses them. Escape
84
+ `&` as `&amp;`.
85
+
86
+ ## 4. Verify what was saved, then report
87
+
88
+ The result says `Threaded as a reply. N attachment(s).` Do not stop there:
89
+
90
+ 1. `zoho_read_message` on the new draft id. Its folder is the Drafts folder
91
+ (find it with `zoho_search` on the subject if you do not know the id).
92
+ 2. Check: subject is `Re:`/`RE:` the original, `cc` is what Reply-All should
93
+ carry, every attachment is listed with the expected size, and the body
94
+ mentions nothing that is not attached.
95
+ 3. Report to whoever asked: draft id, to, cc, and each attachment's name and
96
+ size **as saved**.
97
+
98
+ The API **cannot delete drafts**. If you made a wrong one, say so and give
99
+ its id; the human deletes it in Zoho web so it is never sent by mistake.
100
+
101
+ ## Errors
102
+
103
+ | Status / code | Meaning | Do |
104
+ |---|---|---|
105
+ | `409 mailbox_draft_only` | `mode=send` on a draft-only mailbox | Save as draft; the human sends from Zoho web |
106
+ | `403 recipient_not_allowed` | An address is not on the recipient allowlist (staging allows `@cstech.ai`, `@contetial.com`, `@annam.ai`) | Tell your human which address; never retry with a variant |
107
+ | `413 attachment_too_large` | Over 7 MB in total (sending) or 25 MB (downloading) | Split, compress, or link the file; say so |
108
+ | `400 invalid_email` | A recipient is not one plain address (display name, comma, bracket) | Pass bare addresses, one per entry |
109
+ | `404 message_not_found` | Wrong `message_id` / `folder` pair | Take both from the same search result |
110
+ | `502 reply_recipients_unavailable` | Zoho did not return the original's headers for Reply-All | Retry once; otherwise pass `to`/`cc` explicitly from `zoho_read_message` |
111
+ | `422 reply_draft_unsupported` | Zoho did not confirm the reply was saved as a draft | Stop and report; do not retry as `send` |
112
+ | `429 send_cap_exceeded` | Daily send cap reached | Stop; tell your human |
113
+
114
+ Full reference: `zoho-mail-connector-guide`.