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
- Inventoried every page (
src/app/**/page.tsx) — 53 pages. - Inventoried every
/api/v1/**/route.ts— 165 route files. - Inventoried every tool in
src/app/api/mcp/route.ts— 142 tools. - Inventoried every export from
src/lib/server/*-service.ts(~40 services). - Inventoried every export from the legacy
src/lib/pb-helpers.ts(~60 functions). - Inventoried Server Actions (
src/app/**/actions.ts) — 1 file (settings/ai only). - Cross-referenced against
src/app/api/mcp/FEATURE-REQUESTS.mdto avoid duplicating already-known gaps. - 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 |
✅ |
| 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: 27 capabilities, 25 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_numbertolookupor add a thinorders.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_replyworks, but the underlying REST isPOST /quoteswith anaction: "append_reply"discriminator — not the naturalPOST /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-replyas 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})andcollection_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
/deliveriesboard (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:
/repairsGET+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.getMCP wrapper. Consistent withquotes.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_pdfMCP tool.
1.11 Customer PO creation has no MCP wrapper
- Severity: now
- Effort: small (route exists; missing wrapper)
- Issue: the
orders.createtool creates orders, but creating a customer purchase order (the supplier-facing doc, distinct from the stockstock_purchasesflow) is done via a server action on/orders/[id]and is not exposed via MCP. - REST surface: no dedicated
/api/v1/purchase-ordersPOST; the server action lives in the order detail component. - Recommended slice: extract the PO creation into
POST /api/v1/purchase-orders+purchase_orders.createMCP 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_slipMCP tools.
1.13 pricelist / raw_price_list MCP domain entirely missing
- Severity: now (catalogue is the price-discovery backbone)
- Effort: medium
- Routes needed:
/pricelist/rawGET+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:
/categoriesGET+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.fetchreturns 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 cancurldirectly.
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/v1route 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_invoiceMCP. Backend service:qb-invoice-service.tsmirroring the structure ofstock-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 (usefiles.fetchas 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(bearerAGENT_API_KEY) ORassertBrowserSession(PB cookie)/api/mcp— JSON-RPC, bearerAGENT_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.
Recommended implementation slices
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.liststill 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_pdfdiagnostics 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
- 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.
- 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.
- Auth: does the new MCP auth model (currently being designed for the security-perf-prod remediation) require an explicit
allowed_toolsACL per agent? If yes, that changes the per-tool file shape (each tool needs anallowedRolesmanifest). - 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/mcpproxy operator context for audit, instead ofAGENT_API_KEY(which has no operator identity).