# Riv — QBO MCP Server Scoping Report
**Date:** 2026-05-20
**To:** Larry / Jimmie
**Re:** Intuit Official QBO MCP Server — Install Assessment for Ledger

---

## Executive Summary

The Intuit Official MCP Server is the right technology, but it **cannot be installed as-is** for J2. It has two hard conflicts with J2's existing architecture:

1. **Single-realm only** — it supports one QBO company at a time via env vars. J2 has 40 clients.
2. **Direct writes, no approval layer** — it would bypass the Sheet queue + tiered approval system already designed for Ledger.

The good news: J2 already has a purpose-built multi-client QBO infrastructure (`qbo_client.py`, `qbo_clients.json`, `qbo_oauth_add_client.py`) that's more capable than what the Intuit server provides. The right move is to build a **custom J2 QBO MCP server** that wraps this existing infrastructure.

**One owner decision blocks the build.** Details below.

---

## What the Intuit Official Server Gives Us

- **144 tools** across **29 entity types** (Customer, Invoice, Bill, Vendor, JE, Payment, Sales Receipt, etc.)
- **11 financial reports** (P&L, Balance Sheet, A/R Aging, A/P Aging, Trial Balance, General Ledger, Cash Flow, etc.)
- OAuth 2.0, stdio transport, Node.js

These are the right capabilities. The problem is the architecture around them.

---

## Why We Can't Use It As-Is

### Problem 1: Single-Realm Architecture

The Intuit Official server expects these env vars at startup:

```
QUICKBOOKS_CLIENT_ID
QUICKBOOKS_CLIENT_SECRET
QUICKBOOKS_REFRESH_TOKEN
QUICKBOOKS_REALM_ID          ← one company, hardcoded
QUICKBOOKS_ENVIRONMENT
```

J2 already has `qbo_clients.json` with a named-slug system supporting 40 realms:
```json
{
  "app": { "client_id": "...", "client_secret": "..." },
  "clients": {
    "afton": { "realm_id": "...", "refresh_token": "...", "chairman_group": false },
    "vip_play": { "realm_id": "...", "refresh_token": "...", "chairman_group": true },
    ...
  }
}
```

Installing the Intuit server would mean either: (a) running 40 separate instances — one per client — each with its own `.env`, or (b) manually swapping `.env` values when switching clients. Both are unworkable at scale.

### Problem 2: Write Approval Bypass

The Intuit server executes writes **immediately** when a tool is called. It has no concept of the `QBO Write Queue` Sheet or the Tier 1/2/3 approval system.

If Ledger calls `create_journal_entry` directly through the Intuit server, it posts to QBO without Jimmie's review. That's a safety problem — especially for the Chairman group (VIP Play / Loop TV / FuzeBox AI) which are permanently hard-fenced.

The existing architecture correctly solves this:
- **Tier 1 (auto):** reads
- **Tier 2 (single confirm):** reclassifies, renames
- **Tier 3 (full approval):** JEs, COA changes, invoices/bills/payments

A server that ignores these tiers isn't safe for production use.

### Problem 3: Credential Duplication

Installing the Intuit server would create a second credential system (flat `.env` files) running alongside `qbo_clients.json`. Two sources of truth for OAuth tokens = rotation bugs, stale tokens, and maintenance overhead across 40 clients.

---

## Recommended Path: Custom J2 QBO MCP Server

Build a Python MCP server that wraps `qbo_client.py` and gives Ledger everything the Intuit server offers — plus the multi-client and approval-routing capabilities J2 needs.

### What It Delivers

| Capability | Intuit Official | Custom J2 Server |
|---|---|---|
| 29 entity types (read) | ✅ | ✅ |
| 11 financial reports | ✅ | ✅ |
| Multi-client (40 realms) | ❌ | ✅ |
| Reuses `qbo_clients.json` | ❌ | ✅ |
| Chairman group hard fence | ❌ | ✅ |
| Write approval layer | ❌ | ✅ |
| Tier-aware routing | ❌ | ✅ |
| Afton already authorized | — | ✅ (OAuth done) |

### Architecture

```
Ledger (AI)
    ↓  calls tool with client="afton"
J2 QBO MCP Server  (Python, stdio)
    ↓ reads
  qbo_clients.json  →  qbo_client.py  →  QBO REST API
    ↓ writes
  QBO Write Queue (Google Sheet)  →  Jimmie approves  →  Apps Script  →  QBO REST API
```

**Read tools** — execute immediately, return data to Ledger:
- All 11 reports (P&L, Balance Sheet, A/R Aging, etc.)
- Entity queries (list invoices, get vendor, fetch account, etc.)
- Every call requires `client` param (slug from `qbo_clients.json`)

**Write tools** — append to Sheet queue and return "queued" status:
- `queue_journal_entry`, `queue_invoice`, `queue_bill`, `queue_reclassify`, etc.
- Ledger is told: "Row 47 queued — Jimmie approves with Y in column D"
- Chairman group slugs: write tools return an error before even queueing

### Build Scope

| Step | Task | Who | Est. |
|---|---|---|---|
| 1 | Scaffold Python MCP server with `mcp` SDK | Riv | 2–3 hrs |
| 2 | Wire in `qbo_client.py` — all read tools (reports + entity queries) | Riv | 3–4 hrs |
| 3 | Write tools → Sheet queue routing (appends row, returns queued status) | Riv | 2–3 hrs |
| 4 | Chairman group fence + tier-ceiling enforcement | Riv | 1 hr |
| 5 | Register in `claude_desktop_config.json` (or Claude Code MCP config) | Riv | 30 min |
| 6 | Afton pilot test — Ledger pulls a P&L, queues a reclassify | Ledger + Riv | 1 hr |

**Total: ~10–12 hrs of build, Afton live within 1–2 days of green light.**

### What We Use the Intuit Official Server For

Nothing in production. Optionally: install it against a **QBO sandbox company** for Ledger to experiment with entity shapes and test prompts without touching real books. Sandbox setup is ~30 min and uses throwaway credentials.

---

## 🚦 Blocker: One Owner Decision Required

**How much write autonomy does Ledger get through the MCP server?**

The Sheet queue design is already approved — that's not the question. The question is whether we implement **any** Tier 2 writes as direct MCP calls (bypassing the queue for low-risk ops), or whether **all writes** go through the Sheet queue.

| Option | Description | Tradeoff |
|---|---|---|
| **A — All writes via Sheet queue** | Every write Ledger initiates lands in the queue for Jimmie's review. Nothing posts to QBO without a Y. | Slowest for Ledger; safest for Jimmie; zero risk of unreviewed changes. |
| **B — Tier 1 reads direct, Tier 2 writes direct with confirmation, Tier 3 via queue** | Low-risk writes (reclassify a transaction, deactivate a vendor) execute after Ledger asks "proceed?" in chat. JEs, invoices, COA changes still go to the queue. | Faster for day-to-day bookkeeping; requires Jimmie to trust in-chat confirmations for Tier 2 ops. |
| **C — Reads direct, all writes via queue (recommended start)** | Same as A. After 10-consecutive-correct graduation rule triggers for Tier 2 ops, those can move to Option B. | Phased trust-building. Consistent with the graduation rule already in the architecture. |

**Riv's recommendation: Option C.** Start with everything in the queue. Let the graduation rule do its job. Tier 2 ops graduate to direct execution once Ledger has demonstrated consistent accuracy on Afton. This was already in the architecture — I'm just surfacing it as the active choice before build.

---

## What Happens After Jimmie Decides

1. Jimmie picks Option A, B, or C
2. Riv builds the custom J2 QBO MCP server (~1–2 days)
3. Riv registers it in the MCP config
4. Jimmie does the Afton OAuth handshake (already scripted — ~3 min, browser-based)
5. Ledger runs the existing 2026-05-13 A/R worklist through the queue as the first real batch
6. Graduated from there

No new Intuit Developer portal work needed. No new OAuth app. Reuses everything already live.

---

## Notes for Larry

- The Sheet queue (step 4 in the existing build sequence) is a dependency for write tools. If that's not built yet, Riv will build both together.
- VIP Play / Loop TV / FuzeBox AI (Chairman group) will be wired with the hard fence from day one — no exceptions regardless of which option Jimmie picks.
- Afton is already OAuth-authorized. First client is ready to go the moment the server is built.
