feat(mcp): add purchase_orders.add_attachment tool
This commit is contained in:
parent
a6139b7c8a
commit
0e59d83107
2 changed files with 84 additions and 0 deletions
|
|
@ -126,6 +126,8 @@ describe("POST /api/mcp", () => {
|
|||
"quotes.append_reply",
|
||||
"quotes.find_by_message_id",
|
||||
"attachments.upload_for_quote",
|
||||
// Purchase order additional attachments (added 2026-07-09 — PO email extras)
|
||||
"purchase_orders.add_attachment",
|
||||
// Bulk quote_items (added 2026-07-02 — AI auto-fill agents)
|
||||
"bulk_update_quote_items",
|
||||
// Management CRUD (added 2026-06-28 — Hermes agent support)
|
||||
|
|
@ -386,6 +388,49 @@ describe("POST /api/mcp", () => {
|
|||
expect(JSON.parse(body.result.content[0].text).quote).toBeNull();
|
||||
});
|
||||
|
||||
it("tools/call purchase_orders.add_attachment sends a FormData file and returns the updated attachments array", async () => {
|
||||
update.mockResolvedValueOnce({ additional_attachments: ["new-token"] });
|
||||
const res = await POST(
|
||||
mcpRequest({
|
||||
method: "tools/call",
|
||||
id: 15,
|
||||
params: {
|
||||
name: "purchase_orders.add_attachment",
|
||||
arguments: {
|
||||
po_id: "po-1",
|
||||
filename: "supplier-quote.pdf",
|
||||
content_type: "application/pdf",
|
||||
// base64-encoded "hi" → proves bytes round-trip through FormData.
|
||||
base64: Buffer.from("hi").toString("base64"),
|
||||
},
|
||||
},
|
||||
})
|
||||
);
|
||||
expect(res.status).toBe(200);
|
||||
expect(update).toHaveBeenCalledTimes(1);
|
||||
expect(update).toHaveBeenCalledWith("po-1", expect.any(FormData));
|
||||
const body = await res.json();
|
||||
const parsed = JSON.parse(body.result.content[0].text);
|
||||
expect(parsed).toEqual({ ok: true, attachments: ["new-token"] });
|
||||
});
|
||||
|
||||
it("tools/call purchase_orders.add_attachment fails fast when po_id/filename/base64 are missing", async () => {
|
||||
const res = await POST(
|
||||
mcpRequest({
|
||||
method: "tools/call",
|
||||
id: 16,
|
||||
params: {
|
||||
name: "purchase_orders.add_attachment",
|
||||
arguments: { filename: "x.pdf" },
|
||||
},
|
||||
})
|
||||
);
|
||||
expect(res.status).toBe(500);
|
||||
const body = await res.json();
|
||||
expect(body.error.message).toMatch(/Missing po_id, filename, or base64/);
|
||||
expect(update).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("returns method-not-found for unknown JSON-RPC methods", async () => {
|
||||
const res = await POST(mcpRequest({ method: "tools/wat", id: 8 }));
|
||||
expect(res.status).toBe(404);
|
||||
|
|
|
|||
|
|
@ -282,6 +282,22 @@ const tools = [
|
|||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "purchase_orders.add_attachment",
|
||||
description:
|
||||
"Upload a single additional file (supplier quote, drawing, NDA) to an existing PO. The file travels with the outbound email alongside the versioned PO PDF. Call once per file. Max 25 MB per file.",
|
||||
inputSchema: {
|
||||
type: "object",
|
||||
required: ["po_id", "filename", "base64"],
|
||||
properties: {
|
||||
po_id: { type: "string" },
|
||||
filename: { type: "string" },
|
||||
content_type: { type: "string" },
|
||||
size: { type: "number" },
|
||||
base64: { type: "string" },
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "bulk_update_quote_items",
|
||||
description:
|
||||
|
|
@ -738,6 +754,29 @@ export async function POST(req: NextRequest) {
|
|||
});
|
||||
}
|
||||
|
||||
if (toolName === "purchase_orders.add_attachment") {
|
||||
if (!args.po_id || !args.filename || !args.base64) {
|
||||
throw new Error("Missing po_id, filename, or base64 for purchase_orders.add_attachment");
|
||||
}
|
||||
const bytes = Buffer.from(args.base64, "base64");
|
||||
const fd = new FormData();
|
||||
fd.append(
|
||||
"file",
|
||||
new Blob([new Uint8Array(bytes)], { type: args.content_type || "application/octet-stream" }),
|
||||
args.filename,
|
||||
);
|
||||
const updated = await pb.collection("purchase_orders").update<{
|
||||
additional_attachments: string[] | string;
|
||||
}>(args.po_id, fd);
|
||||
const raw = updated?.additional_attachments ?? [];
|
||||
const arr = Array.isArray(raw) ? raw : raw ? [raw] : [];
|
||||
return NextResponse.json({
|
||||
jsonrpc: "2.0",
|
||||
id: body.id ?? null,
|
||||
result: { content: [{ type: "text", text: JSON.stringify({ ok: true, attachments: arr }) }] },
|
||||
});
|
||||
}
|
||||
|
||||
// ─── Quote items — bulk update ────────────────────────────────
|
||||
if (toolName === "bulk_update_quote_items") {
|
||||
const rawItems = args?.items;
|
||||
|
|
|
|||
Loading…
Reference in a new issue