Content
# zoho-books-mcp
A local, **safety-gated** [Model Context Protocol](https://modelcontextprotocol.io) server for **Zoho Books** — it gives an AI agent the general-ledger writes, bank-feed categorization, and receipt attachment that Zoho's native connector doesn't expose, with hard guardrails so it can't run wild on your live books.
> ⚠️ **This lets an AI create, update, and (optionally) delete real records in your live accounting.** It ships **read-only by default** — every write is off until you deliberately turn it on. Read the [Safety model](#safety-model) before enabling anything, and supervise your agent.
> *Unofficial — not affiliated with, endorsed by, or sponsored by Zoho Corporation. "Zoho" and "Zoho Books" are trademarks of their respective owner. This is an independent community tool that talks to the public Zoho Books API using **your own** credentials.*
---
## Human-written note
Neither QBO nor Xero expose live bank feeds via their APIs, and by all indications never will. This is directly contradictory to their AI-native marketing posture, and functionally useless to an autonomous bookkeeping agent responsible for monitoring an email inbox for invoices and receipts, booking them against a bank feed, and maintaining the ledger. Zoho _does_ expose this, but their native MCP and Claude connectors are equally impotent, with respect to their tool calls and permission gates; they can't create accounts, post certain transactions, or delete stuff. It seems to me, as of July 2026, that they want their connectors to do data analysis and modeling, where I want my agents to actually _do my books_.
You could get the same bank feed data straight from Plaid or Yodlee, but that's another subscription to pay for, another MCP to manage, and another set of credentials to care about. Yuck. This MCP expands the capabilities of the agent to basically whatever the Zoho API allows, while keeping write permissions toggled off by default and routing everything through an inference-level approval gate. It's incredibly useful, but powerful enough to be dangerous, if you're irresponsible or lazy. Use this tool intelligently.
On file uploads: Zoho, like most accounting software, lets you attach documents to transactions, like invoices or receipts. When you give your bookkeeping agent that document, it wants to convert it to base64 or binary and pipe it through the API, which blows your context window. The solution is serverside, so your agent can pass POSTs straight from your Google Drive or Dropbox or whatever; this requires OAuth and possibly GCP-esque configuration, and is not included in this repo. In other words: if you want your agent to do the document-attaching for you, you'll need to configure its access to your cloud drive yourself.
Cheers, and happy accounting.
Steven
## Why this exists
We built this for our own AI bookkeeper, then cleaned it up to share. The short version:
**Autonomous bookkeeping lives or dies on API access to the bank feed** — the stream of real bank activity an agent must see in order to categorize and reconcile it. In our experience (as of 2026), **Xero and QuickBooks Online don't expose that feed through their public APIs**: you can push and pull transactions, but you can't reach the live bank feed itself, which leaves an agent blind to the one thing it most needs to act on.
**Zoho Books does expose it** — its bank feeds are Plaid-sourced and reachable via the API — which makes it the one mainstream option an agent can drive end-to-end. The catch was the tooling *around* it: the native connectors we tried couldn't post journals, build the chart of accounts, categorize feed lines, or attach source docs. So we wrote a thin, guarded server that does — straight against Zoho's public v3 API, no third-party SDK, no broker. Your credentials stay on your machine and leave only as ordinary HTTPS calls to Zoho.
**What it's for:** running behind a hosted AI-employee agent (e.g. Openclaw or Hermes), ideally on a VPS, under **strict permission gates and human supervision**. It ships read-only by default for exactly that reason.
**A word of caution:** this is genuinely useful *and* genuinely powerful — enough to make a real mess of real financial records if you point it at production with the gates open and walk away. Don't. Keep writes off until you trust your setup, scope the Zoho user's role tightly, and watch what your agent does. Treated with that respect it's a capable teammate; treated carelessly it's a footgun.
*Shared freely by [Operamatix, LLC](https://operamatix.com) as a contribution to the broader agentic-bookkeeping community.*
## Features
- **Verb-split passthrough** — `zoho_read` / `zoho_write` / `zoho_delete` reach any Zoho Books v3 endpoint.
- **Guarded GL wrappers** — manual journals (balance-enforced) and chart-of-accounts creation.
- **Bank-feed workflow** — categorize an uncategorized feed line in place, or match it to existing transactions. (Includes the endpoints and request bodies Zoho's docs don't render — see [Bank-feed categorization](#bank-feed-categorization).)
- **Attachments** — upload receipts / source docs to expenses, bills, and journals (by base64 or local file path).
- **Safety-first** — every mutating action sits behind an env flag that is off by default and enforced in code.
## Safety model
The guardrails are **hard floors enforced in the server** — not suggestions the model can talk its way past:
| Control | Default | Effect |
|---|---|---|
| `ZOHO_ALLOW_WRITES` | `false` | All create/update refuse until set to `"true"`. |
| `ZOHO_ALLOW_DELETE` | `false` | All `DELETE` refuse until set to `"true"`. |
| Balanced journals | always on | `zoho_create_journal` refuses to post unless total debits == total credits. |
| Verbatim errors | always on | Zoho's error response is surfaced as-is — writes fail loud, never silently. |
Recommended posture: keep `ZOHO_ALLOW_WRITES=false` while you learn the tool; enable writes once you trust your agent and prompts; leave `ZOHO_ALLOW_DELETE=false` unless you have a specific need. Zoho also enforces the **role** of the user your credentials belong to, on top of these flags — scope that user to the minimum the agent needs.
## Requirements
- Node.js ≥ 20
- A Zoho Books organization and API credentials (below)
## Setup
### 1. Get Zoho credentials (Self Client / client-credentials)
1. Go to the [Zoho API Console](https://api-console.zoho.com) → **Self Client**.
2. Generate a **grant token** with a Zoho Books scope (e.g. `ZohoBooks.fullaccess.all`, or something narrower).
3. Exchange the grant token for a **refresh token** — a standard OAuth `authorization_code` POST to `https://accounts.zoho.com/oauth/v2/token`. Keep the `refresh_token`.
4. Note your **organization_id** (Zoho Books → Settings, or `GET /organizations`).
> The credentials operate **as the Zoho user who created the Self Client**, so create it under the account/role you want the agent to act as. (Non-US data centers: use the matching `accounts.zoho.*` / `zohoapis.*` domains — see env vars.)
### 2. Install
```sh
npm install
```
### 3. Configure your MCP client
Example Claude Desktop `claude_desktop_config.json` entry. Use an **absolute** node path — desktop apps launch MCP servers with a minimal PATH, so a bare `node` won't resolve:
```jsonc
"zoho-books": {
"command": "/absolute/path/to/node",
"args": ["/absolute/path/to/zoho-books-mcp/zoho-books-mcp.mjs"],
"env": {
"ZOHO_CLIENT_ID": "...",
"ZOHO_CLIENT_SECRET": "...",
"ZOHO_REFRESH_TOKEN": "...",
"ZOHO_ORGANIZATION_ID": "...",
"ZOHO_ALLOW_WRITES": "false"
}
}
```
See [`.env.example`](.env.example) for every variable.
## Tools
| Tool | Action | Gate |
|---|---|---|
| `zoho_read` | GET any endpoint | always on |
| `zoho_write` | POST/PUT any endpoint | `ZOHO_ALLOW_WRITES` |
| `zoho_delete` | DELETE any endpoint | `ZOHO_ALLOW_DELETE` |
| `zoho_create_journal` | manual journal (balance-enforced) | `ZOHO_ALLOW_WRITES` |
| `zoho_create_account` | chart-of-accounts account | `ZOHO_ALLOW_WRITES` |
| `zoho_categorize_bank_txn` | categorize an uncategorized feed line | `ZOHO_ALLOW_WRITES` |
| `zoho_match_bank_txn` | match a feed line to existing transactions | `ZOHO_ALLOW_WRITES` |
| `zoho_attach_document` | upload a receipt/doc (base64 or path) | `ZOHO_ALLOW_WRITES` |
## Bank-feed categorization
Zoho's Banking API docs don't fully render the request bodies for categorizing an uncategorized transaction, so here is what this server confirmed against the live API:
- **Route (note the singular `uncategorized`):** `POST /banktransactions/uncategorized/{transaction_id}/categorize`, or `.../categorize/{expenses|customerpayments|vendorpayments}` for module types.
- **A deposit / owner contribution is a from/to model, not a flat account field:**
```json
{ "transaction_type": "deposit",
"from_account_id": "<offset, e.g. equity>", // credited
"to_account_id": "<bank account>", // debited
"amount": 500, "date": "2026-06-30" }
```
- **An expense (money out):** use `categorize_as: "expenses"` with `account_id` (the expense account), `paid_through_account_id` (the bank), `amount`, and `date`.
The `zoho_categorize_bank_txn` wrapper handles this for you: pass `offset_account_id` and it maps to the correct field per type, auto-deriving `amount`/`date`/bank from the feed line.
### Confirmed vs. inferred
This server was validated against a live **US** Zoho Books organization. Some behavior is **confirmed**, some **inferred** — treat inferred rows as "probably right, not yet verified," and remember every write can be reviewed in Zoho:
| Area | Status |
|---|---|
| Auth, read, write, delete, journals, accounts, contacts, expenses | ✅ confirmed |
| Categorize: deposit / owner_contribution (from/to model) | ✅ confirmed |
| Categorize: expenses, vendorpayments | ✅ confirmed |
| Attach: expense receipt (field `receipt`), journal (field `attachment`) | ✅ confirmed (live round-trip) |
| Attach: bill (field `attachment`) | ⚠️ inferred — same pattern, untested |
| Categorize: money-OUT directions (owner_drawings, transfer_fund, …) | ⚠️ inferred — pass explicit `from_account_id` / `to_account_id` |
| Non-US data centers | ⚠️ untested — set `ZOHO_ACCOUNTS_DOMAIN` / `ZOHO_API_DOMAIN` |
## Environment variables
| Variable | Required | Default | Notes |
|---|---|---|---|
| `ZOHO_CLIENT_ID` | ✅ | — | Self Client id |
| `ZOHO_CLIENT_SECRET` | ✅ | — | Self Client secret |
| `ZOHO_REFRESH_TOKEN` | ✅ | — | long-lived refresh token |
| `ZOHO_ORGANIZATION_ID` | recommended | — | auto-added to every call |
| `ZOHO_ALLOW_WRITES` | | `false` | `"true"` permits create/update |
| `ZOHO_ALLOW_DELETE` | | `false` | `"true"` permits DELETE |
| `ZOHO_ACCOUNTS_DOMAIN` | | `https://accounts.zoho.com` | change for non-US DC |
| `ZOHO_API_DOMAIN` | | `https://www.zohoapis.com` | change for non-US DC |
## Testing
```sh
npm install
npm test
```
The smoke test spawns the server with dummy credentials and asserts the tool surface plus that every guard trips **before** any network call — so it never touches Zoho.
## License
[MIT](LICENSE). Provided **as-is, without warranty** — you are responsible for what your agent does to your books. Test with `ZOHO_ALLOW_WRITES=false` first.
MCP Config
Below is the configuration for this MCP Server. You can copy it directly to Cursor or other MCP clients.
mcp.json
Connection Info
You Might Also Like
Vibe-Trading
Vibe-Trading: Your Personal Trading Agent
ai-berkshire
Berkshire in the AI Era: A Value Investment Research Framework Based on...
hexstrike-ai
HexStrike AI is an AI-powered MCP cybersecurity automation platform with 150+ tools.
valuecell
Valuecell is a Python project for efficient data management.
tradingview-mcp
AI-assisted TradingView chart analysis — connect Claude Code to your...
tradingview-mcp
TradingView MCP Server offers real-time market analysis for crypto and stocks.