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.
- package/dist/client.d.ts +13 -2
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js.map +1 -1
- package/dist/skillInstaller.d.ts.map +1 -1
- package/dist/skillInstaller.gate.test.js +2 -0
- package/dist/skillInstaller.gate.test.js.map +1 -1
- package/dist/skillInstaller.js +3 -0
- package/dist/skillInstaller.js.map +1 -1
- package/dist/skillInstaller.test.js +10 -2
- package/dist/skillInstaller.test.js.map +1 -1
- package/dist/tools/index.d.ts.map +1 -1
- package/dist/tools/index.js +49 -6
- package/dist/tools/index.js.map +1 -1
- package/dist/zohoAttachments.d.ts +13 -0
- package/dist/zohoAttachments.d.ts.map +1 -0
- package/dist/zohoAttachments.js +62 -0
- package/dist/zohoAttachments.js.map +1 -0
- package/dist/zohoAttachments.test.d.ts +2 -0
- package/dist/zohoAttachments.test.d.ts.map +1 -0
- package/dist/zohoAttachments.test.js +51 -0
- package/dist/zohoAttachments.test.js.map +1 -0
- package/package.json +1 -1
- package/skills/catalog.json +8 -0
- package/skills/manifest.json +5 -0
- package/skills/skills/zoho-mail/SKILL.md +114 -0
|
@@ -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":"AAAA,qEAAqE;AACrE,EAAE;AACF,6EAA6E;AAC7E,+EAA+E;AAC/E,6EAA6E;AAC7E,4EAA4E;AAC5E,gDAAgD;AAEhD,OAAO,EAAE,MAAM,IAAI,CAAC;AACpB,OAAO,IAAI,MAAM,MAAM,CAAC;AAExB,MAAM,CAAC,MAAM,yBAAyB,GAAG,EAAE,CAAC;AAC5C,MAAM,CAAC,MAAM,yBAAyB,GAAG,EAAE,GAAG,IAAI,GAAG,IAAI,CAAC;AAC1D,wEAAwE;AACxE,MAAM,CAAC,MAAM,8BAA8B,GAAG,CAAC,GAAG,IAAI,GAAG,IAAI,CAAC;AAO9D,MAAM,UAAU,mBAAmB,CACjC,KAA2B,EAC3B,MAAM,GAAG;IACP,KAAK,EAAE,yBAAyB;IAChC,OAAO,EAAE,yBAAyB;IAClC,UAAU,EAAE,8BAA8B;CAC3C;IAED,IAAI,CAAC,KAAK,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,EAAE,CAAC;IAC5C,IAAI,KAAK,CAAC,MAAM,GAAG,MAAM,CAAC,KAAK,EAAE,CAAC;QAChC,MAAM,IAAI,KAAK,CAAC,WAAW,MAAM,CAAC,KAAK,iCAAiC,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC;IAC3F,CAAC;IACD,4EAA4E;IAC5E,qDAAqD;IACrD,IAAI,KAAK,GAAG,CAAC,CAAC;IACd,KAAK,MAAM,CAAC,IAAI,KAAK,EAAE,CAAC;QACtB,oEAAoE;QACpE,+DAA+D;QAC/D,gDAAgD;QAChD,IAAI,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,GAAG,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,GAAG,KAAK,GAAG,IAAI,GAAG,KAAK,IAAI,CAAC,EAAE,CAAC;YACrG,MAAM,IAAI,KAAK,CAAC,sEAAsE,CAAC,EAAE,CAAC,CAAC;QAC7F,CAAC;QACD,IAAI,EAAY,CAAC;QACjB,IAAI,CAAC;YACH,EAAE,GAAG,EAAE,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC;QACtB,CAAC;QAAC,MAAM,CAAC;YACP,MAAM,IAAI,KAAK,CAAC,uCAAuC,CAAC,EAAE,CAAC,CAAC;QAC9D,CAAC;QACD,IAAI,CAAC,EAAE,CAAC,MAAM,EAAE;YAAE,MAAM,IAAI,KAAK,CAAC,6BAA6B,CAAC,EAAE,CAAC,CAAC;QACpE,IAAI,EAAE,CAAC,IAAI,GAAG,MAAM,CAAC,OAAO,EAAE,CAAC;YAC7B,MAAM,IAAI,KAAK,CAAC,cAAc,CAAC,OAAO,EAAE,CAAC,IAAI,+BAA+B,MAAM,CAAC,OAAO,GAAG,CAAC,CAAC;QACjG,CAAC;QACD,KAAK,IAAI,EAAE,CAAC,IAAI,CAAC;QACjB,IAAI,KAAK,GAAG,MAAM,CAAC,UAAU,EAAE,CAAC;YAC9B,MAAM,IAAI,KAAK,CACb,sDAAsD,MAAM,CAAC,UAAU,uBAAuB,CAAC,IAAI,CACpG,CAAC;QACJ,CAAC;IACH,CAAC;IACD,OAAO,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE;QACrB,IAAI,OAAe,CAAC;QACpB,IAAI,CAAC;YACH,OAAO,GAAG,EAAE,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC;QAC/B,CAAC;QAAC,MAAM,CAAC;YACP,MAAM,IAAI,KAAK,CAAC,uCAAuC,CAAC,EAAE,CAAC,CAAC;QAC9D,CAAC;QACD,OAAO,EAAE,QAAQ,EAAE,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC,EAAE,cAAc,EAAE,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAC,EAAE,CAAC;IACpF,CAAC,CAAC,CAAC;AACL,CAAC"}
|
|
@@ -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":"AAAA,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,QAAQ,CAAC;AACrE,OAAO,EAAE,MAAM,IAAI,CAAC;AACpB,OAAO,EAAE,MAAM,IAAI,CAAC;AACpB,OAAO,IAAI,MAAM,MAAM,CAAC;AACxB,OAAO,EAAE,mBAAmB,EAAE,MAAM,sBAAsB,CAAC;AAE3D,QAAQ,CAAC,qBAAqB,EAAE,GAAG,EAAE;IACnC,IAAI,GAAW,CAAC;IAChB,UAAU,CAAC,GAAG,EAAE;QACd,GAAG,GAAG,EAAE,CAAC,WAAW,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,MAAM,EAAE,EAAE,OAAO,CAAC,CAAC,CAAC;IACxD,CAAC,CAAC,CAAC;IACH,SAAS,CAAC,GAAG,EAAE;QACb,EAAE,CAAC,MAAM,CAAC,GAAG,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;IACnD,CAAC,CAAC,CAAC;IACH,MAAM,IAAI,GAAG,CAAC,IAAY,EAAE,OAAe,EAAE,EAAE;QAC7C,MAAM,CAAC,GAAG,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC;QAC/B,EAAE,CAAC,aAAa,CAAC,CAAC,EAAE,OAAO,CAAC,CAAC;QAC7B,OAAO,CAAC,CAAC;IACX,CAAC,CAAC;IAEF,EAAE,CAAC,uCAAuC,EAAE,GAAG,EAAE;QAC/C,MAAM,GAAG,GAAG,mBAAmB,CAAC,CAAC,IAAI,CAAC,aAAa,EAAE,MAAM,CAAC,EAAE,IAAI,CAAC,WAAW,EAAE,MAAM,CAAC,CAAC,CAAC,CAAC;QAC1F,MAAM,CAAC,GAAG,CAAC,CAAC,OAAO,CAAC;YAClB,EAAE,QAAQ,EAAE,aAAa,EAAE,cAAc,EAAE,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,QAAQ,CAAC,QAAQ,CAAC,EAAE;YACnF,EAAE,QAAQ,EAAE,WAAW,EAAE,cAAc,EAAE,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,QAAQ,CAAC,QAAQ,CAAC,EAAE;SAClF,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,8BAA8B,EAAE,GAAG,EAAE;QACtC,MAAM,CAAC,mBAAmB,CAAC,SAAS,CAAC,CAAC,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;QACnD,MAAM,CAAC,mBAAmB,CAAC,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;IAC9C,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,6BAA6B,EAAE,GAAG,EAAE;QACrC,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,UAAU,CAAC,CAAC;QAC3C,MAAM,CAAC,GAAG,EAAE,CAAC,mBAAmB,CAAC,CAAC,IAAI,CAAC,OAAO,EAAE,GAAG,CAAC,EAAE,OAAO,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC;IACpF,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,kDAAkD,EAAE,GAAG,EAAE;QAC1D,EAAE,CAAC,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,MAAM,CAAC,CAAC,CAAC;QACrC,MAAM,GAAG,GAAG,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,MAAM,EAAE,YAAY,CAAC,CAAC;QACjD,EAAE,CAAC,aAAa,CAAC,GAAG,EAAE,QAAQ,CAAC,CAAC;QAChC,MAAM,CAAC,GAAG,EAAE,CAAC,mBAAmB,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC;QAC3D,MAAM,CAAC,GAAG,EAAE,CAAC,mBAAmB,CAAC,CAAC,IAAI,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC;IAC7E,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,qBAAqB,EAAE,GAAG,EAAE;QAC7B,MAAM,CAAC,GAAG,EAAE,CAAC,mBAAmB,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,YAAY,CAAC,CAAC;IACjE,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,mDAAmD,EAAE,GAAG,EAAE;QAC3D,MAAM,MAAM,GAAG,EAAE,KAAK,EAAE,CAAC,EAAE,OAAO,EAAE,CAAC,EAAE,UAAU,EAAE,CAAC,EAAE,CAAC;QACvD,MAAM,CAAC,GAAG,EAAE,CAAC,mBAAmB,CAAC,CAAC,IAAI,CAAC,SAAS,EAAE,QAAQ,CAAC,CAAC,EAAE,MAAM,CAAC,CAAC,CAAC,OAAO,CAAC,oBAAoB,CAAC,CAAC;QACrG,MAAM,CAAC,GAAG,EAAE,CACV,mBAAmB,CAAC,CAAC,IAAI,CAAC,OAAO,EAAE,OAAO,CAAC,EAAE,IAAI,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC,EAAE,MAAM,CAAC,CAC9E,CAAC,OAAO,CAAC,aAAa,CAAC,CAAC;QACzB,MAAM,CAAC,GAAG,EAAE,CACV,mBAAmB,CAAC,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,CAAC,EAAE,IAAI,CAAC,GAAG,EAAE,GAAG,CAAC,EAAE,IAAI,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC,EAAE,MAAM,CAAC,CAC9E,CAAC,OAAO,CAAC,WAAW,CAAC,CAAC;IACzB,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
|
package/package.json
CHANGED
package/skills/catalog.json
CHANGED
|
@@ -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
|
}
|
package/skills/manifest.json
CHANGED
|
@@ -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 `&`.
|
|
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`.
|