155 lines
4.5 KiB
JavaScript
155 lines
4.5 KiB
JavaScript
|
|
#!/usr/bin/env node
|
||
|
|
/**
|
||
|
|
* Create a purchase order through the JEISA PocketBase MCP server.
|
||
|
|
*
|
||
|
|
* node scripts/mcp-create-po.mjs
|
||
|
|
*
|
||
|
|
* Drives `server/pb-mcp.mjs` over stdio and calls, in order:
|
||
|
|
* 1. generate_order_number → allocate a fresh PO-YYYYMMDD-NNN
|
||
|
|
* 2. list_suppliers → pick the first supplier
|
||
|
|
* 3. list_stock_items → pick the first stock item
|
||
|
|
* 4. create_purchase_order → submit the draft
|
||
|
|
* 5. get_purchase_order → read it back to confirm
|
||
|
|
*/
|
||
|
|
|
||
|
|
import { spawn } from "node:child_process";
|
||
|
|
import { fileURLToPath } from "node:url";
|
||
|
|
import path from "node:path";
|
||
|
|
|
||
|
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
||
|
|
const ROOT = path.resolve(__dirname, "..");
|
||
|
|
|
||
|
|
const child = spawn("node", ["--import", "tsx/esm", "server/pb-mcp.mjs"], {
|
||
|
|
cwd: ROOT,
|
||
|
|
stdio: ["pipe", "pipe", "inherit"],
|
||
|
|
});
|
||
|
|
|
||
|
|
let buffer = "";
|
||
|
|
const pending = new Map();
|
||
|
|
let nextId = 1;
|
||
|
|
|
||
|
|
function send(method, params = {}) {
|
||
|
|
return new Promise((resolve, reject) => {
|
||
|
|
const id = nextId++;
|
||
|
|
pending.set(id, { resolve, reject });
|
||
|
|
const msg = JSON.stringify({ jsonrpc: "2.0", id, method, params });
|
||
|
|
child.stdin.write(msg + "\n");
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
child.stdout.on("data", (chunk) => {
|
||
|
|
buffer += chunk.toString("utf8");
|
||
|
|
let nl;
|
||
|
|
while ((nl = buffer.indexOf("\n")) !== -1) {
|
||
|
|
const line = buffer.slice(0, nl).trim();
|
||
|
|
buffer = buffer.slice(nl + 1);
|
||
|
|
if (!line) continue;
|
||
|
|
let parsed;
|
||
|
|
try {
|
||
|
|
parsed = JSON.parse(line);
|
||
|
|
} catch {
|
||
|
|
console.error("[po] non-JSON line:", line);
|
||
|
|
continue;
|
||
|
|
}
|
||
|
|
if (parsed.id != null && pending.has(parsed.id)) {
|
||
|
|
const { resolve, reject } = pending.get(parsed.id);
|
||
|
|
pending.delete(parsed.id);
|
||
|
|
if (parsed.error) reject(new Error(JSON.stringify(parsed.error)));
|
||
|
|
else resolve(parsed.result);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
});
|
||
|
|
|
||
|
|
const shutdown = () => {
|
||
|
|
child.kill();
|
||
|
|
process.exit(0);
|
||
|
|
};
|
||
|
|
process.on("SIGINT", shutdown);
|
||
|
|
process.on("SIGTERM", shutdown);
|
||
|
|
|
||
|
|
function firstJsonText(result) {
|
||
|
|
return JSON.parse(result.content[0].text);
|
||
|
|
}
|
||
|
|
|
||
|
|
try {
|
||
|
|
console.log("[po] initialize");
|
||
|
|
const init = await send("initialize", {
|
||
|
|
protocolVersion: "2024-11-05",
|
||
|
|
capabilities: {},
|
||
|
|
clientInfo: { name: "mcp-create-po", version: "0.0.0" },
|
||
|
|
});
|
||
|
|
console.log("[po] server:", init.serverInfo);
|
||
|
|
|
||
|
|
// 1. Allocate a PO number
|
||
|
|
console.log("\n[po] generate_order_number");
|
||
|
|
const gen = await send("tools/call", { name: "generate_order_number", arguments: {} });
|
||
|
|
const { order_number } = firstJsonText(gen);
|
||
|
|
console.log("[po] order_number:", order_number);
|
||
|
|
|
||
|
|
// 2. Pick a supplier
|
||
|
|
console.log("\n[po] list_suppliers (perPage=5)");
|
||
|
|
const sup = await send("tools/call", {
|
||
|
|
name: "list_suppliers",
|
||
|
|
arguments: { perPage: 5 },
|
||
|
|
});
|
||
|
|
const suppliers = firstJsonText(sup);
|
||
|
|
if (!suppliers.items?.length) throw new Error("No suppliers in PB");
|
||
|
|
const supplier = suppliers.items[0];
|
||
|
|
console.log("[po] supplier:", supplier.id, supplier.supplier_name);
|
||
|
|
|
||
|
|
// 3. Pick a stock item
|
||
|
|
console.log("\n[po] list_stock_items (limit=5)");
|
||
|
|
const stk = await send("tools/call", {
|
||
|
|
name: "list_stock_items",
|
||
|
|
arguments: { limit: 5 },
|
||
|
|
});
|
||
|
|
const stock = firstJsonText(stk);
|
||
|
|
if (!stock.items?.length) throw new Error("No stock items in PB");
|
||
|
|
const item = stock.items[0];
|
||
|
|
console.log("[po] stock_item:", item.id, item.name, "sku:", item.sku);
|
||
|
|
|
||
|
|
// 4. Build + send the draft
|
||
|
|
const draft = {
|
||
|
|
order_number,
|
||
|
|
supplierName: supplier.id,
|
||
|
|
orderDate: new Date().toISOString(),
|
||
|
|
carriage_charge: 0,
|
||
|
|
status: "Pending",
|
||
|
|
items: [
|
||
|
|
{
|
||
|
|
stockItemId: item.id,
|
||
|
|
itemName: item.name,
|
||
|
|
sku: item.sku,
|
||
|
|
quantity: 1,
|
||
|
|
costPrice: Number(item.cost_price ?? 0),
|
||
|
|
costPriceGBP: Number(item.cost_price_gbp ?? item.cost_price ?? 0),
|
||
|
|
entryMode: "stock",
|
||
|
|
},
|
||
|
|
],
|
||
|
|
};
|
||
|
|
console.log("\n[po] create_purchase_order");
|
||
|
|
console.log(JSON.stringify(draft, null, 2));
|
||
|
|
const created = await send("tools/call", {
|
||
|
|
name: "create_purchase_order",
|
||
|
|
arguments: { draft },
|
||
|
|
});
|
||
|
|
console.log("\n[po] create result:");
|
||
|
|
console.log(JSON.stringify(created, null, 2));
|
||
|
|
|
||
|
|
// 5. Read it back
|
||
|
|
const createdJson = firstJsonText(created);
|
||
|
|
if (createdJson?.id) {
|
||
|
|
console.log("\n[po] get_purchase_order (read-back)");
|
||
|
|
const readBack = await send("tools/call", {
|
||
|
|
name: "get_purchase_order",
|
||
|
|
arguments: { id: createdJson.id },
|
||
|
|
});
|
||
|
|
console.log(JSON.stringify(readBack, null, 2));
|
||
|
|
}
|
||
|
|
} catch (err) {
|
||
|
|
console.error("[po] FAILED:", err?.message || err);
|
||
|
|
process.exitCode = 1;
|
||
|
|
} finally {
|
||
|
|
child.kill();
|
||
|
|
}
|