JEISA-WEBAPP/docs/MCP_AND_N8N.md
Dave 0b8f003f87 feat(po): add MCP server, agent-friendly PO API, and PDF rendering
- server/pb-mcp.mjs: 15-tool MCP server for purchase-order workflow
  (CRUD, suppliers, stock, customers, customer POs, validate_draft,
  generate_order_number)
- src/lib/api/purchaseOrders.ts: typed helpers shared by UI + MCP + n8n
- src/components/purchase-orders/pdf.ts: jsPDF PO generator
- src/components/purchase-orders/ViewOrderDialog.tsx: read-only dialog
- src/components/purchase-orders/AttachmentsDialog.tsx: attachment mgmt
- src/components/purchase-orders/NumberInput.tsx: numeric input
- scripts/mcp-create-po.mjs: end-to-end driver (MCP stdio smoke test)
- pocketbase/pb_migrations/1786200007_*: schema migration
- docs/MCP_AND_N8N.md: MCP + n8n integration guide

Verified end-to-end via MCP: generate_order_number → create_purchase_order
with snapshot_items for an actual quote (DRILL-19042 / 161053).
2026-09-07 11:12:13 +02:00

7 KiB

JEISA — MCP & n8n integration plan

This document describes how to give an AI agent (or an n8n workflow) full control of the JEISA purchase-order system: create POs, ingest them from a photo, decrement stock after QuickBooks invoices go out.

It assumes the following PB collections exist on the remote instance (https://pb.jeistk.apphub.co.za):

  • suppliers
  • stock_items
  • purchase_orders (with deleted field — see pocketbase/pb_migrations/1786200007_updated_purchase_orders.js)
  • customers
  • invoices (created by 1786200003_created_invoices.js)
  • invoice_items (created by 1786200004_created_invoice_items.js)
  • customer_pos + customer_po_lines (created by 1786200001_* / 1786200002_*)
  • workflow_runs (created by 1786200006_created_workflow_runs.js)

1. The agent-side surface (what an MCP exposes)

We bundle a small HTTP shim — server/po-api.mjs — that wraps the typed helpers in src/lib/api/purchaseOrders.ts and exposes them as JSON endpoints. The shim is intentionally tiny: it just authenticates against PB, calls the helper, and returns plain JSON. Anything that can call HTTP can be an "agent" — Claude/Cursor MCP, n8n Code node, Zapier, etc.

Method Path Helper Purpose
GET /purchase-orders listPurchaseOrders List POs (soft-delete filter applied).
POST /purchase-orders createPurchaseOrder Create a PO with line items + attachments.
GET /purchase-orders/:id (PB SDK getOne) Fetch a single PO.
PATCH /purchase-orders/:id updatePurchaseOrder Update fields / append attachments / change status.
DELETE /purchase-orders/:id softDeletePurchaseOrder Mark deleted = true.
POST /purchase-orders/:id/restore restorePurchaseOrder Undo soft-delete.
POST /purchase-orders/:id/receive receiveStock Bump stock + flip status to Received.
POST /purchase-orders/draft:validate validatePurchaseOrderDraft Pre-flight check before write.
POST /purchase-orders/draft:generate-id generateOrderNumber Allocate a fresh PO number.
POST /suppliers (PB SDK create) Create supplier on demand (rare).
GET /stock-items?q=… (PB SDK list) Search stock for the photo-OCR path.

The shim ships with Bearer-token auth. The token is a PocketBase admin or user token issued by pb.admins.authWithPassword / pb.collection('users').authWithPassword. Rotate via the env vars.

Running the shim locally

# 1. Add a `PO_API_TOKEN` to .env (any long random string).
# 2. Add the credentials the shim uses:
POCKETBASE_URL=https://pb.jeistk.apphub.co.za
POCKETBASE_ADMIN_EMAIL=david@wfsales.co.za
POCKETBASE_ADMIN_PASSWORD=1234567890
PO_API_TOKEN=<long-random-string>

# 3. Start the shim
node ./server/po-api.mjs
# → listening on :8787

Calling from an MCP

server/po-api.mjs already returns JSON, so any MCP-style wrapper (or just fetch) can call it. The Cursor MCP server (@modelcontextprotocol/server-fetch) can be pointed at the shim and will surface every endpoint as a tool.


2. Photo → PO ingestion (the "JEI emailed a photo" path)

scripts/po-photo-ingest.mjs is a small wrapper that:

  1. Accepts a base64 image OR a file path.
  2. Sends it to OpenAI Vision (or Google Document AI) with a strict schema: { supplier, order_number?, items: [{ sku, qty, unit_price_gbp }] }.
  3. Calls validatePurchaseOrderDraft to confirm the supplier / SKUs exist.
  4. Calls createPurchaseOrder with the structured draft.
  5. Returns the new PO id + a confidence report.

This is what server/po-api.mjs's POST /purchase-orders/from-photo endpoint delegates to.


3. n8n workflow — "JEI emailed us a PO" → QB invoice

[ Email inbox trigger ]
        │  imap / gmail node watches `jei@...`
        ▼
[ Attachment extractor ]          ── keeps PDFs + image attachments
        │
        ▼
[ HTTP POST /purchase-orders/from-photo ]   ◄── scripts/po-photo-ingest.mjs
        │   body: { image: base64, email_from, subject }
        │
        ▼
[ IF status == "ok" ] ──────────► [ HTTP PATCH /purchase-orders/:id ]   ◄── attach the original PDF
        │                                body: { newFiles: [{filename, data, type}] }
        ▼                                  +
[ QuickBooks node — Create Invoice ]       status: "Approved"
        │   from PO + customer (mapped from email)
        ▼
[ HTTP POST /purchase-orders/:id/receive ]
        │   body: { received: [ { stockItemId, quantity } ] }
        │   ↳ decrements stock_items.quantity
        │
        ▼
[ SendGrid / Gmail node — Email invoice to client ]
        │
        ▼
[ HTTP PATCH /purchase-orders/:id ]  status: "Ordered"
        ▼
[ HTTP POST /workflow_runs ]   (record audit trail)

Each step has explicit retry + error branches that log into workflow_runs for observability — see 1786200006_created_workflow_runs.js.


4. Credentials and environment

Env var Where Notes
POCKETBASE_URL .cursor/mcp.json, server/po-api.mjs Cloud instance URL.
POCKETBASE_ADMIN_EMAIL ditto Use a dedicated admin user.
POCKETBASE_ADMIN_PASSWORD ditto Rotate quarterly.
PO_API_TOKEN server/po-api.mjs Any long random string.
OPENAI_API_KEY scripts/po-photo-ingest.mjs For the Vision OCR step.
QUICKBOOKS_CLIENT_ID/SECRET n8n QuickBooks node OAuth2 per n8n credentials section.

5. Phase 2 (not built yet)

  • server/po-api.mjs (HTTP wrapper) — not yet committed.
  • scripts/po-photo-ingest.mjs — not yet committed.
  • The n8n workflow JSON export — not yet exported.

Each of these is a single small file. The shape is locked in so they can be added without changing the UI.