JEISA-WEBAPP/docs/MCP_AND_N8N.md

144 lines
7 KiB
Markdown
Raw Permalink Normal View History

# 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
```bash
# 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.