WFDASH-WEBAPP/docs/audit/api-mcp-coverage.md
Dave 3091ac94de docs(quote-assistant): runbook accuracy, MCP tool listed in API docs
- Runbook: pre-migration settings don't persist (not "saves fail"),
  eligible statuses, 15 lines per quote, audit attribution, clone/template
  behaviour, supplier kept, doubts dot, confidence guidance (Medium is the
  default; raise to High if reps often change AI prices).
- quotes.assistant_fill added to docs/api-mcp-layer.md,
  docs/audit/api-mcp-coverage.md and the Giellie skill.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 23:42:44 +02:00

48 KiB

MCP + API Layer Coverage Gap Analysis

Task: #e169d78f Date: 2026-08-28 Author: @bob Status: analysis only — implementation NOT included (per task brief)

Executive summary

The WFDASH app already has a substantial programmatic surface:

Surface Count Scope
Pages (Server Components) 53 The full operator UI
REST route handlers under /api/v1/* 165 Programmatic + agent + n8n integration
MCP tools (registered in src/app/api/mcp/route.ts) 142 Agent-facing (Hermes, etc.)
Server Actions (Next.js "use server") 1 file AI settings form only
Service-layer exports (src/lib/server/*-service.ts) ~350 across 40 files The actual business logic

The current coverage is broad but uneven. The bulk-write lifecycle (quotes → orders → PO → collection → delivery → invoice → stock) is well-covered on every layer. Reads are skewed toward agent queries (every collection has a .list / .get MCP tool), but write coverage has a long tail of gaps in domains that don't get daily agent traffic: repairs, the delivery board, the boss dashboard, the collection map, the stock quick-draft line editor, and the price_list / raw_price_list catalogue UI.

The biggest structural gap is the MCP route file itself — a single 7,077-line route.ts containing 142 tool definitions, 142 handlers, the JSON-RPC envelope, the CORS helper, the upstream-fetch wrapper, and the TOOL_HINTS map. This is blocking maintainability (already on the security-perf-prod audit as a major), and it's blocking discoverability for new tool authors.

Methodology

  1. Inventoried every page (src/app/**/page.tsx) — 53 pages.
  2. Inventoried every /api/v1/**/route.ts — 165 route files.
  3. Inventoried every tool in src/app/api/mcp/route.ts — 142 tools.
  4. Inventoried every export from src/lib/server/*-service.ts (~40 services).
  5. Inventoried every export from the legacy src/lib/pb-helpers.ts (~60 functions).
  6. Inventoried Server Actions (src/app/**/actions.ts) — 1 file (settings/ai only).
  7. Cross-referenced against src/app/api/mcp/FEATURE-REQUESTS.md to avoid duplicating already-known gaps.
  8. Cross-referenced the MCP refactor recommendation at docs/audit/backend-data.md:355-383 (Major finding 2.6).

The output is a capability→API→MCP matrix and a prioritized gap list with recommended slices for follow-up tasks.

Coverage matrix

Legend: ✅ shipped · 🟡 partial · ❌ missing

Quotes pipeline

Capability Page /api/v1 route MCP tool Notes
List quotes (tabs) /quotes GET /quotes/list + /quotes/search quotes.list ✅
Get single quote /quotes/[id] GET /quotes/[id] quotes.get ✅
Create from email (n8n ingest) webhook POST /quotes/from-email quotes.create_from_email, quotes.ingest_from_email ✅
Create walk-in /quotes/[id] "new" POST /quotes/create quotes.create ✅ (Phase G)
Update quote header inline form PATCH /quotes/[id] quotes.update ✅
Find by Message-ID n8n callback GET /quotes/lookup?messageId= quotes.find_by_message_id ✅
List templates /quotes Templates tab GET /quotes/archive ❌ MCP gap
Save as template detail page POST /quotes/[id]/save-as-template quotes.save_as_template ✅
Create from template template row POST /quotes/[id]/create-from-template quotes.create_from_template ✅
Clone quote detail page POST /quotes/[id]/clone quotes.clone ✅
Add reply to thread reply modal POST /quotes/append-reply quotes.append_reply ✅ (route is POST /quotes with action, not /append-reply — see gap §1.4)
Re-run AI detail page button POST /quotes/[id]/re-run-ai quotes.re_run_ai ✅
Process pending (batch) cron POST /quotes/process-pending quotes.process_pending ✅
Submit for approval detail page POST /quotes/[id]/submit quotes.submit ✅ (Phase G)
Partial approve detail page POST /quotes/[id]/partial-approve quotes.partial_approve ✅
Review-approve /quotes/approvals POST /quotes/[id]/review-approve ❌ MCP gap (see §1.1)
Re-price items inline POST /quotes/[id]/re-price quotes.re_price ✅
AI fill from POS quote / Process page button POST /quotes/[id]/assistant/run quotes.assistant_fill ✅
Split quote inline POST /quotes/[id]/split quotes.split ✅
Send email email modal POST /quotes/[id]/send-email quotes.send_email ✅
Retry email email modal POST /quotes/[id]/retry-email quotes.retry_email ✅
Render PDF preview GET /quotes/[id]/pdf + /pdf/base64 quotes.get_pdf ✅
Populate insights AI panel POST /quotes/[id]/populate-insights + /quote_items/[id]/populate-insights quote_items.populate_insights ✅
List attachments attachments card GET /quotes/[id]/attachments quotes.list_attachments ✅
Upload attachment attachments card POST /quotes/[id]/attachments/upload + /quotes/items/[id]/attachments/upload attachments.upload_for_quote ✅
Set notes / set is_no_quote form PATCH /quotes/[id] quotes.update ✅
Lookup (number → id) form GET /quotes/lookup quotes.lookup ✅
Generate next quote # form GET /quotes/next-number quotes.next_number ✅
Archive quotes /quotes/archive POST /quotes/archive ❌ MCP gap (see §1.1)

Result: 28 capabilities, 26 fully covered. 2 MCP gaps (review-approve, archive).

Quote items

Capability Page /api/v1 route MCP tool Notes
List per quote /quotes/[id] GET /quotes/[id]/items quote_items.list ✅
Get single item inline GET /quote_items/[id] quote_items.get ✅
Create item inline "add row" POST /quotes/[id]/items quote_items.create ✅ (Phase F)
Update item inline popover PATCH /quote_items/[id] quote_items.update ✅
Bulk update items sprint bulk POST /quote_items/bulk-update bulk_update_quote_items ✅
Boolean popover fields (qty/cost/sell) inline PATCH /order-items/[id]/... (per-field) 🟡 Split across many small routes — see §3.4
Populate AI insights insights panel POST /quote_items/[id]/populate-insights quote_items.populate_insights ✅
Get AI intel insights panel GET /quote_items/[id]/intel 🟡 REST yes, MCP gap (see §1.7)
Item history inline panel GET /quote_items/[id]/item-history ❌ MCP gap
SKU lookup / match sprint match picker GET /quotes/[id]/items/[itemId]/lookup + /match 🟡 REST yes, MCP gap
Rewrite (sprint rewrite) sprint POST /quotes/items/rewrite ❌ MCP gap (internal AI flow)

Result: 11 capabilities, 6 fully covered, 5 partial/missing.

Orders pipeline

Capability Page /api/v1 route MCP tool Notes
List orders /orders GET /orders/list + /orders orders.list ✅
Get single order /orders/[id] GET /orders/[id] orders.get ✅
Create from quote "Convert" button POST /orders/from-quote orders.create_from_quote ✅
Create walk-in /orders/new POST /orders/create orders.create ✅
Update header detail page PATCH /orders/[id] orders.update ✅ (Phase H)
Set quote link detail page POST /orders/[id]/quote orders.set_quote ✅
Archive /orders/archive POST /orders/archive orders.archive ✅ (Phase H)
Cancel detail page POST /orders/[id]/cancel orders.cancel ✅ (Phase H)
Review-save detail page POST /orders/[id]/review-save orders.review_save ✅ (Phase H)
Mark invoiced (header) invoicing tab POST /orders/[id]/mark_invoiced orders.mark_invoiced ✅ (Phase H)
Mark delivered (header) invoicing tab POST /orders/[id]/mark_delivered orders.mark_delivered ✅ (Phase H)
Add note detail page PATCH /orders/[id] orders.add_note ✅ (Phase H, wrapper of update)
Add attachment attachments card POST /orders/[id]/attachments/upload orders.add_attachment ✅
List attachments attachments card GET /orders/[id]/matched-attachments orders.list_attachments, orders.matched_attachments ✅
Split order detail page POST /orders/[id]/split orders.split ✅
Add item (existing order) inline POST /orders/[id]/items order_items.create ✅ (Phase H)
Review data (read) review screen GET /orders/[id]/review orders.review ✅ (Phase E)
Stats (aggregate) reports GET /orders/stats orders.stats ✅ (Phase H)
Daily summary reports GET /orders/daily_summary orders.daily_summary ✅ (Phase H)
Sprint counts reports GET /orders/sprint/counts orders.sprint_counts ✅ (Phase E)
Find by order # search GET /orders/lookup orders.find_by_order_number ✅ (Phase E)
Find by Message-ID n8n callback GET /orders/lookup orders.find_by_email_message_id ✅ (Phase E)
Find duplicates QA GET /orders/duplicates orders.find_duplicates ✅ (Phase E)
Lookup (number → id) form GET /orders/lookup ❌ MCP gap — see §1.2

Result: 24 capabilities, 23 fully covered. 1 MCP gap (generic lookup).

Order items

Capability Page /api/v1 route MCP tool Notes
List per order /orders/[id] GET /order-items?orderId= order_items.list ✅
Get single item inline GET /order-items/[id] order_items.get ✅
Update popover fields inline popover PATCH /order-items/[id] order_items.update ✅ (Phase A whitelist)
Per-field shortcuts inline PATCH /order-items/[id]/{qty,sku,cost,status} 🟡 4 routes for the popover per-field widgets; not wrapped in MCP — see §3.4
Update status workflow button PATCH /order-items/[id]/status order_items.update_status ✅
Confirm collected (driver) driver flow POST /order-items/confirm order_items.confirm_collected ✅
Split item inline POST /order-items/[id]/split ❌ MCP gap — see §1.3
Request margin approval inline POST /order-items/[id]/request-margin-approval ❌ MCP gap
AI intel (read) insights panel GET /order-items/[id]/intel 🟡 REST yes, MCP gap
Refresh AI intel insights panel POST /items/[collection]/[id]/intel/refresh 🟡 REST yes, MCP gap
Backfill intel cron POST /items/backfill-intel ❌ MCP gap (cron)
Context (price-history snapshot) context panel GET /order-items/[id]/context ❌ MCP gap

Result: 12 capabilities, 5 fully covered, 7 partial/missing.

Sprint / item-level queue

Capability Page /api/v1 route MCP tool Notes
List sprint items /sprint GET /sprint sprint.list ✅
Claim item (30-min TTL) inline POST /sprint/items/[id]/claim sprint.claim ✅
Item attachments sprint panel GET /sprint/items/[id]/attachments sprint.item_attachments ✅
Attachment counts sprint row badge GET /sprint/attachment-counts sprint.attachment_counts ✅
Upload attachment sprint panel POST /sprint/items/[id]/attachments/upload sprint.item_attachments (POST variant) 🟡

Result: 5 capabilities, 4 fully covered, 1 partial.

Collection pool & map

Capability Page /api/v1 route MCP tool Notes
List collection pool /collection-pool GET /collection-pool (GET /orders/...) collection_pool.list ✅ (older route)
Map items /collection-map GET /collection-map/items ❌ MCP gap — see §1.5
Assign driver (map) map click POST /collection-map/items/[id]/assign ❌ MCP gap
Bulk assign per supplier pool server action bulkAssign ❌ MCP gap

Result: 4 capabilities, 1 covered, 3 MCP gaps.

Driver (mobile)

Capability Page /api/v1 route MCP tool Notes
List drivers (users) settings GET /driver ❌ MCP gap — only drivers.list_stops exists
List stops mobile / driver GET /driver/stops drivers.list_stops ✅
Update stop status mobile / driver PATCH /driver/stops/[id]/status drivers.update_stop_status ✅
Collection-confirm (driver UI) /collection-confirm GET /collection-confirm collection_confirm.list ✅
Confirm collection mobile / driver POST /collection-confirm/confirm-order-items collection_confirm.confirm_order_items ✅
Upload invoice (driver) mobile / driver POST /collection-confirm/invoice-upload collection_confirm.upload_invoice ✅
Confirm stock (warehouse) warehouse POST /collection-confirm/confirm-stock ❌ MCP gap

Result: 7 capabilities, 5 covered, 2 MCP gaps (driver list, confirm-stock).

Delivery board (/deliveries)

Capability Page /api/v1 route MCP tool Notes
List per tab (to_load / back_from_driver / checked_in) /deliveries GET /deliveries?tab= ❌ MCP gap (see §1.6)
Load (set status → with_driver) /deliveries POST /deliveries/load ❌ MCP gap
Load DN (delivery-note bound load) /deliveries POST /deliveries/load-dn ❌ MCP gap
Check-in (driver returns) /deliveries POST /deliveries/check-in ❌ MCP gap
Mark signed copy /deliveries POST /deliveries/mark-signed-copy ❌ MCP gap
Find item to load by DN /deliveries server action ❌ MCP gap

Result: 6 capabilities, 0 MCP-covered, 6 REST-only. Whole /deliveries MCP domain missing.

Delivery notes

Capability Page /api/v1 route MCP tool Notes
List delivery notes /delivery-notes GET /delivery-notes delivery_notes.list ✅ (Phase E)
Get single /delivery-notes/[id] GET /delivery-notes/[id] ❌ MCP gap — see §1.9
Create from order invoicing modal POST /delivery-notes delivery_notes.create ✅ (Phase E)
Cancel detail page POST /delivery-notes/[id]/cancel delivery_notes.cancel ✅ (Phase E)
Mark invoiced invoicing tab POST /delivery-notes/[id]/mark-invoiced delivery_notes.mark_invoiced ✅ (Phase E)
Mark complete detail page POST /delivery-notes/[id]/mark-complete delivery_notes.mark_complete ✅ (Phase E)
Regenerate PDF detail page POST /delivery-notes/[id]/regenerate-pdf delivery_notes.regenerate_pdf ✅ (Phase E)
Create quote from DN detail page POST /delivery-notes/[id]/create-quote delivery_notes.create_quote ✅ (Phase E)
Set customer PO detail page POST /delivery-notes/[id]/customer-po delivery_notes.set_customer_po ✅ (Phase E)
Set item quantity inline POST /delivery-notes/items/[id]/quantity delivery_notes.set_item_quantity ✅ (Phase E)
Set order detail page POST /delivery-notes/[id]/order delivery_notes.set_order ✅ (Phase E)
Combine (merge DNs) admin POST /delivery-notes/combine delivery_notes.combine ✅ (Phase E)
Mark signed copy /delivery-notes/[id] POST /delivery-notes/[id]/signed-copy ❌ MCP gap
Get PDF (stream) preview ❌ (no stream route) ❌ See §1.10

Result: 14 capabilities, 11 covered. 3 MCP gaps (get-single, signed-copy, PDF stream).

Repairs

Capability Page /api/v1 route MCP tool Notes
List repairs /repairs GET /repairs ❌ MCP gap — see §1.8
Get single /repairs/[id] GET /repairs/[id] ❌ MCP gap
Create repair /repairs form POST /repairs ❌ MCP gap
Update repair /repairs/[id] PATCH /repairs/[id] ❌ MCP gap
Delete repair /repairs/[id] DELETE /repairs/[id] ❌ MCP gap
List form-data (create modal) form GET /repairs/form-data ❌ Internal helper, no MCP gap
Upload file repair detail POST /repairs/[id]/files/[field] ❌ MCP gap
Generate delivery note repair detail POST /repairs/[id]/generate-delivery-note ❌ MCP gap
Generate repair number form ❌ (handled inline) ❌ MCP gap

Result: 9 capabilities, 0 MCP-covered. Whole /repairs MCP domain missing.

Purchase orders (PO)

Capability Page /api/v1 route MCP tool Notes
List POs (sprint/PO view) GET /purchase-orders purchase_orders.list ✅
Get single PO inline GET /purchase-orders/[id] purchase_orders.get ✅
Create PO server action POST /purchase-orders stock_purchases.send (different) 🟡
Send PO email email modal POST /purchase-orders/[id]/send-email purchase_orders.send_email ✅
Retry email email modal POST /purchase-orders/[id]/send-email purchase_orders.retry_email ✅
Send outcome (callback) n8n callback POST /purchase-orders/[id]/send-email/outcome purchase_orders.record_send_outcome ✅
PDF render preview GET /purchase-orders/[id]/pdf ❌ MCP gap (see §1.12)
Dispatch (operator action) PO row POST /purchase-orders/[id]/dispatch 🟡 REST yes, MCP gap
Add attachment detail page POST /purchase-orders/[id]/attachments/upload purchase_orders.add_attachment ✅
Get attachment preview GET /purchase-orders/[id]/attachments/[filename] 🟡 REST yes, MCP gap
List stale POs reports GET /purchase-orders/stale purchase_orders.stale ✅
Scan (PB PDF → lines) AI flow POST /purchase-orders/scan purchase_orders.scan ✅ (internal)
Diff (PO vs lines) AI flow POST /purchase-orders/diff purchase_orders.diff ✅ (internal)
Record diff report AI flow POST /purchase-orders/record-diff-report purchase_orders.record_diff_report ✅ (internal)

Result: 14 capabilities, 9 fully covered, 5 partial/missing.

Stock items + stock purchases

Capability Page /api/v1 route MCP tool Notes
List stock items /stock GET /stock/items stock_items.list ✅
Get single /stock/[id] GET /stock/items/[id] stock_items.get ✅
Create stock item /stock form POST /stock/items/create stock_items.create ✅
Update stock item /stock/[id] PATCH /stock/items/[id] stock_items.update ✅
Manual adjust /stock/[id] POST /stock/items/[id]/adjust stock_items.adjust ✅
Set cost / selling price / markup (batch) /stock inline edit POST /stock/items/set-pricing stock_items.set_pricing ✅
Fulfill from stock for order fulfilment flow POST /stock/fulfill stock.fulfill ✅
Surplus book fulfilment flow POST /stock/surplus-book stock.surplus_book ✅
List stock purchases /stock-orders GET /stock-purchases/list stock_purchases.list ✅
Get single SP /stock-orders/[id] GET /stock-purchases (one-shot read) stock_purchases.get ✅
Create SP /stock-orders/new POST /stock-purchases stock_purchases.create ✅
Send SP email email modal POST /stock-purchases/[id]/send-email stock_purchases.send_email ✅
Retry email email modal POST /stock-purchases/[id]/send-email stock_purchases.retry_email ✅
Receive SP (book into stock) warehouse POST /stock-purchases/[id]/receive stock_purchases.receive ✅
Match & receive from invoice scan workflow n8n webhook → /stock-purchases/match-from-invoice stock_purchases.match_and_receive_from_invoice ✅ (Phase H internal)
Add attachment detail page POST /stock-purchases/[id]/attachments/upload stock_purchases.add_attachment ✅
Get attachment preview GET /stock-purchases/[id]/attachments/[filename] ❌ MCP gap
Stock-purchase list PDF reports GET /stock-purchases/list-pdf ❌ MCP gap
Picking slip PDF warehouse GET /stock-purchases/picking-slip ❌ MCP gap
SP PDF detail page GET /stock-purchases/[id]/pdf ❌ MCP gap (3 PDF routes un-wrapped)
Stock-purchase items: list /stock-orders/[id] GET /stock-purchase-items stock_purchase_items.list ✅
Stock-purchase items: update inline PATCH /stock-purchase-items/[id] stock_purchase_items.update ✅
Stock-purchase items: bulk update inline PATCH /stock-purchase-items/bulk stock_purchase_items.bulk_update ✅
Stock-purchase items: per-field (qty/sku/cost) inline PATCH /stock-purchase-items/[id]/{qty,sku,cost} 🟡 3 routes for popover per-field widgets, no MCP — see §3.4
Stock-purchase items: assign driver inline POST /order-items/assign-driver (uses same path) stock_purchase_items.assign_driver ✅
Stock-purchase items: mark collected warehouse n/a (via confirm) stock_purchase_items.mark_collected ✅
Next SP number form GET /stock-purchases/next-number stock_purchases.next_number ✅
Send SP (shorthand) POST /stock-purchases/send 🟡 REST yes, MCP gap
Stocktake PDF reports GET /stock-items/stocktake-pdf ❌ MCP gap
Reorder list /stock-orders/reorder (server action) ❌ MCP gap

Result: 30 capabilities, 21 fully covered, 9 partial/missing.

Stock quick-draft

Capability Page /api/v1 route MCP tool Notes
Search catalogue inline GET /stock-quick-draft/search stock_quick_draft.search ✅ (Phase E)
Add line to draft inline POST /stock-quick-draft/add-line stock_quick_draft.add_line ✅ (Phase E)
List suppliers inline GET /stock-quick-draft/suppliers stock_quick_draft.suppliers ✅ (Phase E)
List drafts inline GET /stock-quick-draft/drafts ❌ MCP gap
Edit/delete line inline PATCH /stock-quick-draft/line/[lineId] ❌ MCP gap
Supplier history inline GET /stock-quick-draft/supplier-history ❌ MCP gap

Result: 6 capabilities, 3 covered. 3 MCP gaps.

Pricelist / catalogue

Capability Page /api/v1 route MCP tool Notes
List raw uploads /pricelist Raw tab GET /pricelist/raw (via pb-helpers) ❌ MCP gap — see §1.13
Delete raw upload row DELETE /pricelist/raw/[id] ❌ MCP gap
Search catalogue Catalogue tab GET /price-list (via pb-helpers) ❌ MCP gap
Get price-list entry inline GET /price-list/[sku] ❌ MCP gap
Add items (bulk ingest) AI flow POST /price-list/add-items price_list.add_items ✅
Create raw upload upload widget (server action) ❌ MCP gap
Resync from raw upload row (server action) ❌ MCP gap

Result: 7 capabilities, 1 covered. 6 MCP gaps — almost the entire catalogue is agent-blind.

Companies / Customers / Suppliers (master data)

Capability Page /api/v1 route MCP tool Notes
List companies /companies GET /companies companies.list ✅
Get single /companies/[id] GET /companies/[id] companies.get ✅
Create form POST /companies companies.create ✅
Update form PATCH /companies/[id] companies.update ✅
Delete confirm DELETE /companies/[id] companies.delete ✅
Find duplicates QA GET /companies/duplicates (via pb-helpers) ❌ MCP gap
List customers /customers GET /customers/list + /customers customers.list ✅ (Phase G added rep filter)
Get single /customers/[id] GET /customers/[id] customers.get ✅
Create form POST /customers customers.create ✅
Update form PATCH /customers/[id] customers.update ✅
Delete confirm DELETE /customers/[id] customers.delete ✅
Find duplicates QA (via pb-helpers) ❌ MCP gap
List suppliers /suppliers GET /suppliers suppliers.list ✅
Get single /suppliers/[id] GET /suppliers/[id] suppliers.get ✅
Create form POST /suppliers suppliers.create ✅
Update form PATCH /suppliers/[id] suppliers.update ✅
Delete confirm DELETE /suppliers/[id] suppliers.delete ✅
Find duplicates QA (via pb-helpers) ❌ MCP gap
Geocode (read + write) supplier row (none) ❌ FEATURE-REQUESTS.md (now)
Mappable suppliers (for map) /supplier-map GET /suppliers/mappable (via pb-helpers) ❌ MCP gap

Result: 21 capabilities, 15 covered. 6 MCP gaps (duplicates for all 3; mappable; geocoding).

Categories

Capability Page /api/v1 route MCP tool Notes
List categories (admin) ❌ (server-component read) ❌ Both gaps
CRUD (admin) ❌ ❌ Whole admin domain missing — see §1.14

Result: 2 capabilities, 0 covered. Whole categories domain missing.

Dashboard & KPIs

Capability Page /api/v1 route MCP tool Notes
Quotes dashboard data /dashboard GET /dashboard ❌ MCP gap
Orders dashboard data /dashboard (combined) ❌ MCP gap
Boss dashboard (today/week/month) /m GET /boss-dashboard ❌ MCP gap
Boss dashboard rep activity /m GET /boss-dashboard/rep-activity ❌ MCP gap
Daily summary (single call) reports GET /orders/daily_summary orders.daily_summary ✅
Dashboard daily summary reports ❌ (no separate route — dashboard.daily_summary MCP reads PB directly) dashboard.daily_summary ✅ (Phase E)
Health check n8n GET /health ❌ MCP gap

Result: 7 capabilities, 2 covered. 5 MCP gaps.

Settings

Capability Page /api/v1 route MCP tool Notes
Get AI settings /settings/ai GET /ai/settings ai_settings.get ✅
Update AI settings /settings/ai form POST /ai/settings (via Server Action) ai_settings.update ✅
Rerun AI insights (per collection) /settings/ai POST /ai-insights/rerun ai_insights.rerun ✅
List AI insights /settings/ai GET /ai-insights ❌ MCP gap
Theme picker /settings client-side n/a N/A
Dev identity switcher /settings client-side n/a N/A
TV session /tv/pin, /tv/board /api/tv/login, /api/tv/logout ❌ MCP gap (separate auth surface)
User lookup /settings dev-mode GET /users/lookup users.lookup ✅
List users (admin) GET /users ❌ MCP gap

Result: 9 capabilities, 4 covered. 5 gaps (3 MCP, 2 non-MCP surfaces).

Buyer tasks

Capability Page /api/v1 route MCP tool Notes
List buyer tasks /orders/tasks GET /buyer-tasks buyer_tasks.list ✅
Generate PO inline POST /buyer-tasks/generate-po buyer_tasks.generate_po ✅
Mark ready inline POST /buyer-tasks/mark-ready buyer_tasks.mark_ready ✅

Result: 3 capabilities, 3 covered. 0 gaps.

Files & attachments proxy

Capability Page /api/v1 route MCP tool Notes
Stream PB file inline GET /files/[collection]/[recordId]/[filename] ❌ MCP gap (see §1.15)
Fetch as base64 inline ❌ (no base64 proxy) files.fetch ✅ (Phase E) — but only base64

Result: 2 capabilities, 1 covered. 1 MCP gap (byte-stream equivalent).

Invoicing (header)

Capability Page /api/v1 route MCP tool Notes
Stock book from invoice accountant button POST /invoicing/stock-book ❌ MCP gap (see §1.16)
QB invoice (build + send) invoicing modal n8n webhook (not in /api/v1) ❌ Architectural gap — see §1.17

Result: 2 capabilities, 0 covered via MCP. 2 MCP gaps.


Gap analysis (prioritized)

Gaps are scored by:

  • Severity (now = blocks daily agent/operator work; nice-to-have = polish)
  • Effort (small = ≤1 PR with no schema change; medium = 1-3 PRs; large = schema + migration + docs)
  • Block count (how many downstream agent workflows depend on it)

1. Critical MCP gaps (now, blocks daily agent workflows)

1.1 quotes.review_approve + quotes.archive MCP missing

  • Severity: now
  • Effort: small (route handlers exist, just thin wrappers)
  • REST surface: POST /api/v1/quotes/[id]/review-approve, POST /api/v1/quotes/archive
  • Agent impact: the approval queue (/quotes/approvals) and archive view are completely agent-invisible. Hermes can list but cannot act on approval decisions.
  • Recommended slice: add two MCP wrappers, no schema changes.

1.2 orders.lookup MCP missing

  • Severity: now
  • Effort: small
  • REST surface: GET /api/v1/orders/lookup?number=...
  • Agent impact: the agent has find_by_order_number (Phase E) but no generic lookup that takes either id or number. Duplication by design.
  • Recommended slice: either alias find_by_order_number to lookup or add a thin orders.lookup({id|number}).

1.3 order_items.split MCP missing

  • Severity: now
  • Effort: small
  • REST surface: POST /api/v1/order-items/[id]/split
  • Agent impact: splitting an order line is a daily buyer workflow ("customer only wants 5 of the 10 we ordered"). The agent can do everything around the split but not the split itself.
  • Recommended slice: thin wrapper, no schema changes.

1.4 quotes.append_reply routed via the wrong endpoint

  • Severity: now (deceptive)
  • Effort: small
  • Issue: MCP tool quotes.append_reply works, but the underlying REST is POST /quotes with an action: "append_reply" discriminator — not the natural POST /quotes/append-reply. The MCP wraps it, so Hermes doesn't see this, but anyone hand-calling REST hits a 404.
  • Recommended slice: add POST /api/v1/quotes/append-reply as the canonical route, keep the legacy action as a back-compat alias.

1.5 collection_map.assign + bulk assign MCP missing

  • Severity: now
  • Effort: small
  • REST surface: POST /api/v1/collection-map/items/[id]/assign; bulk-assign is a server action on the pool page
  • Agent impact: driver dispatch from the map view is invisible to the agent. Daily workflow for the office team.
  • Recommended slice: add collection_map.assign({order_item_id, driver_id}) and collection_map.bulk_assign({supplier_id, driver_id, order_item_ids[]}).

1.6 /deliveries MCP domain entirely missing

  • Severity: now (whole domain)
  • Effort: medium (5 routes + 5 tools)
  • Routes: /api/v1/deliveries, /load, /load-dn, /check-in, /mark-signed-copy, plus the lookup helper
  • Agent impact: the operator's /deliveries board (to_load → back_from_driver → checked_in) is the daily delivery flow; the agent can see orders but cannot act on the delivery lifecycle.
  • Recommended slice: add deliveries.list({tab}), deliveries.load({item_ids[]}), deliveries.load_dn({delivery_note_id}), deliveries.check_in({item_ids[], delivery_reference}), deliveries.mark_signed_copy({item_ids[]}). These wrap the existing REST routes.

1.7 order_items.intel + quote_items.intel MCP missing

  • Severity: now
  • Effort: small (pure read wrappers)
  • Routes: GET /order-items/[id]/intel, GET /quote_items/[id]/intel, POST /items/[collection]/[id]/intel/refresh
  • Agent impact: the agent can't read or refresh AI intel on a specific item, forcing it to scan the full PB collection via the admin SDK (slow + PII risk).
  • Recommended slice: add order_items.intel({id}), quote_items.intel({id}), items.intel_refresh({collection, id}).

1.8 repairs.* MCP domain entirely missing

  • Severity: now (whole domain)
  • Effort: medium
  • Routes: /repairs GET+POST, /repairs/[id] GET+PATCH+DELETE, /repairs/[id]/files/[field], /repairs/[id]/generate-delivery-note, plus the form-data helper
  • Agent impact: repair jobs are completely agent-invisible. The owner asked "anything in the app" → this is one of the seven operator-managed domains the agent cannot touch.
  • Recommended slice: add repairs.list, repairs.get, repairs.create, repairs.update, repairs.delete, repairs.upload_file, repairs.generate_delivery_note.

1.9 delivery_notes.get MCP missing

  • Severity: nice-to-have (most reads go via list)
  • Effort: small
  • REST surface: no /delivery-notes/[id] GET route exists; the agent reads the row directly from PB.
  • Recommended slice: add GET /api/v1/delivery-notes/[id] + delivery_notes.get MCP wrapper. Consistent with quotes.get / orders.get.

1.10 delivery_notes.pdf MCP + REST missing

  • Severity: now (preview breaks for agent-rendered DNs)
  • Effort: small (mirror the quote PDF route pattern)
  • Recommended slice: add GET /api/v1/delivery-notes/[id]/pdf + delivery_notes.get_pdf MCP tool.

1.11 Customer PO creation has no MCP wrapper

  • Severity: now
  • Effort: small (route exists; missing wrapper)
  • Issue: the orders.create tool creates orders, but creating a customer purchase order (the supplier-facing doc, distinct from the stock stock_purchases flow) is done via a server action on /orders/[id] and is not exposed via MCP.
  • REST surface: no dedicated /api/v1/purchase-orders POST; the server action lives in the order detail component.
  • Recommended slice: extract the PO creation into POST /api/v1/purchase-orders + purchase_orders.create MCP tool. Migrate the server action to call it.

1.12 Three PO PDFs un-wrapped (customer + stock + stocktake)

  • Severity: now (agent can't preview PO PDFs)
  • Effort: small (3 routes already exist)
  • Routes: GET /purchase-orders/[id]/pdf, GET /stock-purchases/[id]/pdf, GET /stock-items/stocktake-pdf, GET /stock-purchases/list-pdf, GET /stock-purchases/picking-slip
  • Recommended slice: add purchase_orders.get_pdf, stock_purchases.get_pdf, stock_items.stocktake_pdf, stock_purchases.list_pdf, stock_purchases.picking_slip MCP tools.

1.13 pricelist / raw_price_list MCP domain entirely missing

  • Severity: now (catalogue is the price-discovery backbone)
  • Effort: medium
  • Routes needed: /pricelist/raw GET+DELETE, /pricelist/search, /pricelist/[sku], plus the upload/resync server actions need REST mirrors
  • Agent impact: the agent can add items to the price list (price_list.add_items) but cannot search or read entries. Sprint automation depends on the price list search.
  • Recommended slice: add pricelist.search({q, sku, brand, category, supplier}), pricelist.get({sku}), pricelist.raw_list(), pricelist.raw_delete({id}), pricelist.raw_create(...), pricelist.raw_resync({id}).

1.14 categories MCP domain entirely missing

  • Severity: nice-to-have
  • Effort: small
  • Routes needed: /categories GET+POST, /categories/[id] GET+PATCH+DELETE
  • Agent impact: the sprint auto-bucketing rule sets (match_brands, match_supplier_tags, match_keywords) live on categories. The agent can read items but cannot inspect or tune the routing rules.
  • Recommended slice: add the 5 MCP wrappers.

1.15 files.stream MCP missing (byte-stream equivalent)

  • Severity: nice-to-have
  • Effort: small
  • Issue: files.fetch returns base64; large files (10 MB+ pricelist PDFs, signed delivery copies) bloat the MCP payload. Hermes wants byte-stream download.
  • Recommended slice: add a files.stream({collection, recordId, filename}) that returns {url, mime, expiresAt} so the agent can curl directly.

1.16 invoicing.stock_book MCP missing

  • Severity: nice-to-have
  • Effort: small
  • Route: POST /api/v1/invoicing/stock-book
  • Agent impact: accountant's "Book into stock" button is REST-only. The buyer agent can't run the workflow end-to-end.
  • Recommended slice: add invoicing.stock_book({id, date?}) MCP wrapper.

1.17 QuickBooks invoice payload has no MCP entry point

  • Severity: now (invoicing is a daily operator workflow)
  • Effort: large (architectural — the QB flow runs via n8n webhook, not /api/v1)
  • Issue: the QB invoice is built and POSTed to n8n from the invoicing modal — there's no /api/v1 route and no MCP tool. The agent can't drive the invoicing end-to-end.
  • Recommended slice: add /api/v1/invoicing/qb-invoice (POST) + invoicing.qb_invoice MCP. Backend service: qb-invoice-service.ts mirroring the structure of stock-purchase-invoice-matching-service.ts.

2. MCP gaps per route (full list)

A few useful one-liner additions (severity: nice-to-have unless flagged otherwise):

  • purchase_orders.dispatch — REST-only; MCP wrapper.
  • purchase_orders.get_attachment — REST-only; MCP wrapper (use files.fetch as the underlying primitive).
  • stock_purchase_items.{qty,sku,cost} per-field — REST-only; MCP wrappers.
  • order_items.{qty,sku,cost,status,context} per-field — REST-only; MCP wrappers.
  • order_items.request_margin_approval — REST-only; MCP wrapper.
  • order_items.split — see §1.3.
  • quote_items.item_history — REST-only; MCP wrapper.
  • quote_items.lookup / quote_items.match — REST-only; MCP wrappers (sprint match picker).
  • quotes.items/rewrite — REST-only; MCP wrapper (internal AI flow, low priority).
  • driver.list — REST-only; MCP wrapper.
  • collection_confirm.confirm_stock — REST-only; MCP wrapper.
  • delivery_notes.signed_copy — REST-only; MCP wrapper.
  • delivery_notes.combine — REST-only; MCP wrapper.
  • stock_purchases.{send, get_attachment, list_pdf, picking_slip} — REST-only; MCP wrappers.
  • stock_purchases.drafts.{list, edit_line, supplier_history} — REST-only; MCP wrappers.
  • companies.{find_duplicates, mappable} — REST-only; MCP wrappers.
  • customers.find_duplicates — REST-only; MCP wrapper.
  • suppliers.{find_duplicates, geocode, mappable} — REST-only; MCP wrappers (geocode already in FEATURE-REQUESTS.md).
  • users.list — REST-only; MCP wrapper.
  • ai_insights.list — REST-only; MCP wrapper.
  • health.check — REST-only; MCP wrapper (cheap operational health probe).
  • repair.* — see §1.8.
  • category.* — see §1.14.
  • pricelist.* / raw_price_list.* — see §1.13.
  • boss_dashboard.{get, rep_activity} — REST-only; MCP wrappers.

3. MCP refactor recommendations (the 7,077-line route.ts)

The MCP route handler is the single biggest maintainability bottleneck in the agent layer. The approved docs/audit/backend-data.md:355-383 calls for splitting each tool into src/lib/server/mcp-tools/<name>.ts. Building on that recommendation:

3.1 Per-tool file split

src/lib/server/mcp-tools/
  orders/
    list.ts        # inputSchema + handler
    get.ts
    create.ts
    ...
  quotes/
  ...

Each file exports:

export const tool: McpTool = {
  name: "orders.list",
  description: "...",
  inputSchema: { ... },
};
export async function handle(req: Request, input: unknown, ctx: McpContext): Promise<ToolResult> { ... }

The tools array in route.ts reduces to Object.entries(MCP_TOOLS).flatMap(([name, mod]) => [mod.tool]); dispatch is MCP_TOOLS[toolName]?.handle(...).

3.2 Zod schema sharing with REST routes

Today the MCP layer hand-writes inputSchema while REST routes use zod. They drift (the quote_items.create whitelist drift was caught in Phase F; same risk exists for quote_items.update vs bulk_update_quote_items per FEATURE-REQUESTS.md).

Recommend: each MCP tool exports both inputSchema (JSON Schema for the MCP wire) AND a zodSchema for runtime validation. Use zod-to-json-schema to derive the JSON Schema from the zod schema. One source of truth per write operation.

3.3 Service-layer dispatch table

Cross-reference: the FEATURE-REQUESTS "Standard error envelope with hint/next_actions/allowed_tools" entry. The 13 write tools migrated in Phase I carry a TOOL_HINTS map at route.ts:266; the remaining 33 fetch + if (!res.ok) blocks need the same migration. Per-tool file split makes this mechanical.

3.4 Per-field widget route proliferation

/api/v1/order-items/[id]/{qty,sku,cost,status,context} and /api/v1/stock-purchase-items/[id]/{qty,sku,cost} exist as 4+3 separate PATCH routes because the operator popover posts one field at a time. This works but is awkward for an agent (which would batch). Recommend either:

  • Document the existing PATCH /order-items/[id] (whitelist already exists) as the canonical "batch fields" endpoint for the agent; keep the per-field routes for the popover; OR
  • Generate the per-field routes from a shared schema (e.g. const PER_FIELD_ROUTES = { qty: { method: "PATCH", path: "/order-items/[id]/qty" }, ... }).

3.5 MCP tool consolidation for sprint attachments

sprint.item_attachments is one tool that takes either a GET or POST shape depending on context. Split into sprint.get_attachments({id}) + sprint.upload_attachment({id, file}) for clarity.

4. Architectural observations

4.1 Customer PO vs stock PO model drift

The MCP separates purchase_orders.* (customer-side) from stock_purchases.* (warehouse-side). The duplicate-rest surface is fine for queries but the agent often wants a "PO" abstraction (the source PDF, the supplier reply, the lines, the email status). Consider a purchase_orders.unified MCP tool that returns both sides under a discriminated union — or just clearer cross-tool recipes in docs/agents/workflows.md.

4.2 Server Actions ↔ REST surface split

Only one page (/settings/ai) currently uses Next.js Server Actions directly. The AGENTS.md data-layer-discipline rule says new endpoints belong in /api/v1/*. The pattern is healthy; the existing inventory confirms it's not being abused. No action needed.

4.3 Auth model split

Three auth surfaces are in play:

  • /api/v1/* — assertAgentAuth (bearer AGENT_API_KEY) OR assertBrowserSession (PB cookie)
  • /api/mcp — JSON-RPC, bearer AGENT_API_KEY, CORS *
  • Server Actions — PB cookie session via (app) layout

The audit (docs/audit/security-perf-prod.md Major #2) flags the bare AGENT_API_KEY as too weak + /api/mcp CORS *. When those are remediated, the MCP auth boundary should mirror the new contract.

4.4 Streaming response types

files.fetch returns base64. PDF routes stream bytes. Delivery note PDF, stocktake PDF, picking slip, and PO PDF stream bytes via NextResponse. If the MCP layer is to drive document previews end-to-end, it needs a files.stream (see §1.15) that wraps the streaming routes.

4.5 Invoicing flow architecture

The QuickBooks invoice pipeline runs through n8n, not /api/v1. Adding MCP coverage requires both a REST endpoint (/api/v1/invoicing/qb-invoice) and a service layer (qb-invoice-service.ts) that wraps the n8n webhook contract. This is the only domain where the agent has zero programmatic access to a daily operator workflow. Highest architectural priority.

Slice these as separate tasks for the team-lead to assign. Each slice is sized to a single PR and ships behind a feature flag if desired.

# Slice Severity Effort Tools added
S1 Quote approval + archive MCP now small 2
S2 Order lookup MCP + item split MCP now small 2
S3 Collection map + bulk assign MCP now small 2
S4 /deliveries MCP domain now medium 5
S5 order_items.intel + quote_items.intel MCP now small 3
S6 repairs.* MCP domain now medium 7
S7 delivery_notes.{get, pdf, signed_copy} MCP now small 3
S8 Customer PO creation MCP + 5 PDF MCP wrappers now medium 6
S9 pricelist.* + raw_price_list.* MCP now medium 7
S10 QuickBooks invoice MCP + service layer now large 1
S11 MCP route.ts per-tool file split major large refactor
S12 Zod-schema sharing (MCP ↔ REST) major medium refactor
S13 Migrate remaining 33 fetch + if (!res.ok) blocks to fetchUpstreamJson major medium refactor
S14 categories.* MCP nice small 5
S15 boss_dashboard.* MCP + dashboard.quotes/dashboard.orders nice small 4
S16 files.stream MCP nice small 1
S17 Long-tail single-route MCP wrappers (§2 list) nice medium ~30
S18 Per-field widget route cleanup (§3.4) nice small refactor
S19 stock_quick_draft.{drafts, edit_line, supplier_history} MCP nice small 3
S20 health.check MCP + ai_insights.list MCP + users.list MCP nice small 3

Total new tools if every slice ships: ~85 (S11/S12/S13/S18 are refactors, not additions). Brings the catalogue to ~225 tools.

Cross-references to existing FEATURE-REQUESTS.md

This analysis does not duplicate the entries already tracked in src/app/api/mcp/FEATURE-REQUESTS.md. Open entries that this doc references but doesn't re-list:

  • "Order items: clarify received" (Phase H partial, still open)
  • "Order items: detect items missing selling_price" (Phase H partial)
  • "Stock purchases: paperclip badge" (UI-only, now)
  • "Drivers: no MCP visibility or assignment" (Phase E partial — list_stops + update_stop_status shipped; drivers.list still missing, see §1.16)
  • "Suppliers: read/update works, geocoding does not" (still open)
  • "Stock purchases: PDF → stock order ingestion" (still open)
  • "Stock purchases: paperclip badge on list" (UI-only)
  • "Standard error envelope with hint / next_actions / allowed_tools" (Phase I partial — 13 of 46 tools migrated; see §3.3)
  • "Quotes: trim/normalise customer + company names on quotes.create" (still open)
  • "Quotes: pre-send validation errors vs SMTP failures don't share an envelope" (Phase F partial — no_pdf diagnostics shipped)
  • "Orders: incoming email recipient resolution" (still open)
  • "Customers: case-insensitive email match" (still open)
  • "quotes.send_email.attach_auth_letters per-letterhead semantics undocumented" (still open)
  • "quote_items.create vs quote_items.update whitelist drift" (Phase F.2 partial — still see §3.2)
  • "Hermes MCP origin config" (doc-only, still open)
  • "Bulk_update_quote_items schema" (related to §3.2)

New entries to append (these came out of this audit):

  • §1.4: Quotes append-reply REST is on the wrong route — add POST /api/v1/quotes/append-reply, keep back-compat alias.
  • §1.8: Repairs MCP domain entirely missing — 7 tools + 1 service layer extension.
  • §1.13: Pricelist / raw_price_list MCP domain entirely missing — 7 tools + REST routes.
  • §1.14: Categories MCP domain entirely missing — 5 tools + REST routes.
  • §1.17: QuickBooks invoice MCP + service layer missing — architectural; needs qb-invoice-service.ts.

Open questions for the team-lead

  1. Scope of slice S10 (QuickBooks invoice MCP): is the goal to mirror the n8n webhook in /api/v1 and call n8n from there, or to call the QB API directly? The current architecture routes through n8n for retries + audit; a direct QB integration would lose those.
  2. Scope of slice S11 (MCP route split): is per-tool the target, or per-domain-folder? Per-tool gives 142 files; per-domain gives ~30 files each with 1-many tools. The approved backend-data.md audit calls for per-tool; per-domain is the practical mid-point.
  3. Auth: does the new MCP auth model (currently being designed for the security-perf-prod remediation) require an explicit allowed_tools ACL per agent? If yes, that changes the per-tool file shape (each tool needs an allowedRoles manifest).
  4. Pricing model for the MCP "anything in the app" promise: today the MCP is bearer-key-only. If the user wants the agent to drive every operator workflow, should the MCP accept the same PB cookie session as the UI? That would let /api/mcp proxy operator context for audit, instead of AGENT_API_KEY (which has no operator identity).