Documentation source
Documentation source
Documentation source
Operator runbook for bringing the first live finance connection online for daily-cash ops, plus how to verify the MCP endpoint and read connection readiness honestly.
## Honest current state **Production has zero live finance connections today.** QuickBooks and Plaid are **registered and enabled** in the integration registry, but neither has a connected organization — `agent_connections` holds **0** finance rows for the tenant. Nothing in the app fakes, seeds, or mocks a connection to look "connected". Until an operator completes the steps below, the readiness badge reads **Needs setup** or **Not connected** — by design. This page is the operator runbook to bring the **first** finance connection online (the daily-cash use case) and to verify it. It does **not** re-document credentials — those live in the provider reference docs it links to. ## Readiness state legend `getIntegrationOverview()` returns a derived `readiness` field (`features/integrations/framework/readiness.ts`) — a view over the `agent_connections` row plus a boolean env-presence check. It never reads or returns a secret value. | State | Meaning | Operator action | | --- | --- | --- | | **Needs setup** (`needs_credentials`) | Provider app credentials are not configured. The OAuth/Link flow cannot start. | Set the provider env vars (below). | | **Not connected** (`not_connected`) | Credentials present; no account linked yet. | Run the in-app Connect / Link flow. | | **Connected** (`connected`) | A connection row exists and is not erroring. | None — run a sync. | | **Action needed** (`error`) | A linked connection is erroring / needs re-auth. | Reconnect the account. | > `planned` is folded into **Needs setup**; there is no `syncing` state — a > stale `lastSyncAt` would lie, so we never claim it. ## Step 1 — Configure provider credentials (env) These are **app-level** keys set in the Vercel project environment (server-only, never `NEXT_PUBLIC_`). Per-account access tokens are NOT env vars — they are encrypted on the `agent_connections` row after the connect flow. | Provider | Required env vars | Reference | | --- | --- | --- | | QuickBooks | `QUICKBOOKS_CLIENT_ID`, `QUICKBOOKS_CLIENT_SECRET`, `QUICKBOOKS_API_BASE_URL` (sandbox vs prod host) | [QuickBooks](/docs/integrations/quickbooks) | | Plaid | `PLAID_CLIENT_ID`, `PLAID_SECRET`, `PLAID_ENV` (`sandbox` \| `production`) | [Plaid](/docs/integrations/plaid) | When all required keys for a provider are present, its readiness flips from **Needs setup** → **Not connected**. ## Step 2 — Link the first account (manual prod step) This is a deliberate human action — there is no automation that links an account for you. - **QuickBooks:** register the Intuit app, add the stable redirect URI `…/api/oauth/connection-callback` to it, then **Admin → Integrations → QuickBooks → Connect** and approve the Intuit consent screen. Each company becomes one `agent_connections` row keyed by its `realmId`. Full flow: [QuickBooks](/docs/integrations/quickbooks). - **Plaid:** run **Admin → Integrations → Plaid → Link**, complete Plaid Link for the institution; the exchange route writes the encrypted item token to `agent_connections`. Full flow: [Plaid](/docs/integrations/plaid). Readiness flips to **Connected** once the row exists. ## Step 3 — Verify (in order) 1. **MCP endpoint + audience.** Prove the tenant-scoped MCP endpoint answers and fails closed for the wrong tenant: ```bash MCP_SMOKE_TOKEN=sk_... pnpm mcp:smoke:marbella ``` If a token is rejected, the smoke now surfaces the server's **classified reason** from the `WWW-Authenticate` challenge instead of a bare `status 401` — e.g. `streamable initialize — invalid_token — Token audience mismatch: token is tenant-scoped`. Common causes: | Classified reason | Cause | Fix | | --- | --- | --- | | `token is tenant-scoped` | A tenant token used against the global `/api/mcp/server`. | Use the tenant URL `…/api/mcp/t/<slug>/server`, or a global token. | | `token is for the global endpoint` | A global token used against the tenant URL. | Mint a tenant-scoped token, or call the global endpoint. | | `bound to a different tenant` | The API key belongs to another tenant. | Use a key created under this tenant. | The challenge never contains the token; the smoke also asserts the token never leaks into any rejection response. 2. **Readiness badge.** Admin → Integrations shows the badge walking **Needs setup → Not connected → Connected** as you complete each step. 3. **One manual sync.** Trigger a sync and confirm the created-data counts on the overview increment. ## Related - [QuickBooks](/docs/integrations/quickbooks) · [Plaid](/docs/integrations/plaid) - [MCP OAuth](/docs/integrations/mcp-oauth) — tenant-scoped connector URLs and audience binding - [Integration Substrate](/docs/integrations/integration-substrate) — the shared connector engine