Zoho Inventory Connector
Connects to Zoho Inventory (read-only) to expose a merchant's stock, sales orders and delivery evidence. Provides availability checks for up to 25 SKUs (optionally per warehouse, with alternative-location stock), order and catalogue listing/search/get primitives, and fulfilment evidence lookup returning carrier, AWB, ship/delivery dates and a quotable verdict for dispute handling, with OAuth 2.0 auth, rate limiting, PII masking and audit logging.
Click on "Deploy Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@Zoho Inventory Connector A shopper abandoned ST-TEE-OLV-M and ST-TEE-BLK-M. What can we still pitch?"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
Zoho Inventory connector for Razorpay Agent Studio
A private, read-only connector that gives Agent Studio agents a merchant's stock, orders and proof of delivery from Zoho Inventory. It is an MCP server with OAuth 2.0, list/get/search primitives, three-layer rate limiting and a published tool spec. It runs fully offline in demo mode, so you can try it in two minutes without a Zoho account.
The merchant problem
Saffron Threads is a D2C apparel and home brand. It takes payments on Razorpay and runs stock and fulfilment in Zoho Inventory, with warehouses in Bengaluru and Delhi NCR. It has switched on Agent Studio.
The cart recovery agent keeps calling shoppers about an olive tee that has been out of stock for a week.
The dispute responder can't answer a "never received" chargeback. The carrier, AWB and delivery date live in Zoho, and someone on the ops team copies them out by hand.
The ask was "let the agent read inventory and orders." The real need is "let agents check fulfilment facts before they talk to a customer or answer a dispute."
So on top of the list/get/search primitives, the connector has two tools built for those jobs:
check_availability: up to 25 SKUs in one call, optionally for one warehouse. Answers sellable: yes/no with a reason.get_fulfilment_evidence: looks up an order by its Razorpay order ID. Returns carrier, AWB, ship and delivery dates, a verdict, a sentence the agent can quote, and the gaps that weaken the evidence.
Related MCP server: Zoho Inventory MCP Connector
Try it in 2 minutes (no Zoho account)
Needs Python 3.11+ and uv.
git clone <this repo> && cd zoho-inventory-connector
uv sync
uv run python scripts/demo.py # narrated walkthrough through a real MCP client session
uv run pytest -q # 66 tests, ~10 s, no networkscripts/demo.py connects through the OAuth code path, then runs the cart and dispute scenarios. It also shows PII masking, a prompt-injection record, recovery from Zoho 429s, and the structured error an agent gets when the budget runs out.
Use it from an MCP host (Claude Desktop, Claude Code, MCP Inspector) with fictional demo data:
uv run zoho-connector serve --demo # stdio
npx -y @modelcontextprotocol/inspector uv run zoho-connector serve --demo # click through every toolClaude Code picks up the bundled .mcp.json automatically. For Claude Desktop, add this to claude_desktop_config.json:
{
"mcpServers": {
"zoho-inventory": {
"command": "uv",
"args": ["--directory", "/absolute/path/to/zoho-inventory-connector", "run", "zoho-connector", "serve", "--demo"]
}
}
}Then ask, for example: "A shopper abandoned ST-TEE-OLV-M and ST-TEE-BLK-M. What can we still pitch?" or "Customer disputes Razorpay order order_DemoQ7Hk2pXa01 as not received. What's our evidence?"
Connect a real Zoho Inventory org
Everything below is free: Zoho Inventory's free plan includes API access at 1,000 calls/day.
Org. Sign up at zoho.com/in/inventory (free plan).
OAuth client. At api-console.zoho.in, choose Add Client → Server-based Applications and set:
Homepage URL:
http://localhostAuthorized Redirect URI:
http://localhost:8765/callback
Configure.
cp .env.example .envand setZOHO_CLIENT_ID,ZOHO_CLIENT_SECRETandZOHO_DC(infor zoho.in accounts).Connect.
uv run zoho-connector connect # opens Zoho's consent screen (5 read-only scopes) → pick org → tokens go to the OS keyring uv run zoho-connector status # verifies with one API call, shows budgetOn a server without a browser, create a Self Client in the API console, put its client ID/secret in
.env, generate a code with the same read scopes, and runzoho-connector connect --code 1000.xxxx.Demo data (optional). Seed the same fictional records into your org with
scripts/seed_zoho.py. It uses a separate, short-lived write grant and revokes it at the end, because the connector itself never gets write access.uv run python scripts/seed_zoho.py --dry-run # see the ~55 requests first uv run python scripts/seed_zoho.py --code 1000.xxxx --client-id <self client id> --client-secret <self client secret>Serve.
uv run zoho-connector serve, or use the Claude Desktop config above without--demo. Add--transport http --port 8000to serve over streamable HTTP.Disconnect.
uv run zoho-connector disconnectrevokes the grant at Zoho and deletes the local tokens.
Command | What it does |
| OAuth consent, bind to one organization |
| Connection, scopes, token expiry, per-minute and daily budget |
| Run the MCP server |
| Revoke and forget |
| Mock Zoho on :8900, to try the browser OAuth flow without an account |
| Regenerate |
--connection <name> (or ZOHO_CONNECTION) keeps one named connection per merchant org.
Tools
The full JSON Schemas, including output schemas, are in docs/tools.json. A test keeps that file in sync with the code.
Tool | Use it when | Zoho calls |
| Before pitching or promising a product. ≤25 SKUs, optional warehouse | 1 per SKU, or 1 catalogue page for ≥6 SKUs |
| Disputes, "where is my order". By Razorpay ref, SO number or id | 2 + 1 per package |
| Catalogue, low stock, per-warehouse stock | 1–2 |
| Orders by status, date, ref, customer | 1–2 |
| Which org, scopes, budget left | 0 |
All tools are annotated readOnlyHint: true, destructiveHint: false. Pagination uses opaque next_cursor tokens bound to the original query. Errors are JSON with a stable code, an optional retry_after_s and a hint telling the agent what to do next.
docs/CAPABILITIES.md is the one-page summary of what an agent can and cannot do.
How it handles the hard parts
OAuth. Authorization-code flow with offline access, a loopback redirect, a
statecheck, and validation of Zoho's multi-DCaccounts-server. Token refresh is single-flight, and a 401 triggers one refresh and retry. Tokens are stored in the OS keyring and revoked on disconnect.Rate limits. Zoho enforces 100 requests/min, 5–10 concurrent and 1,000–10,000/day per org. The connector:
keeps a sliding 90/min window and a concurrency semaphore;
persists a daily quota counter;
honours
Retry-Afterand otherwise backs off with jitter, within a bounded total wait;fails fast with
RATE_LIMITEDorDAILY_QUOTA_EXHAUSTEDinstead of hanging an agent;caches reads (items 60s, orders 30s) to save quota.
Safety.
Read-only, enforced by OAuth scope, by the tool surface and by MCP annotations.
Customer email, phone and street are masked unless the operator sets
ZOHO_PII_MODE=full.Merchant free text is returned as
UntrustedText.Every call writes a JSON audit line with arguments (names redacted), status, latency, Zoho calls spent and quota left.
The reasoning behind these choices is in docs/DESIGN.md. How to tell whether the connector is worth deploying is in docs/IMPACT.md.
Tests and evals
uv run pytest -q # 66 tests
uv run python evals/run_eval.py # LLM eval; needs EVAL_API_KEY (free Groq key works; Anthropic/Ollama via env)The tests cover:
the full browser OAuth flow on real sockets;
statemismatch and untrustedaccounts-server;expired-token refresh, including concurrent callers sharing one refresh;
revoked grants;
429 with
Retry-After, the 1070 concurrency error and 5xx retries;a 150-call burst throttled client-side with zero upstream 429s;
the daily quota persisting across restarts and rolling over at IST midnight;
every fulfilment verdict;
cursor misuse, PII masking and the prompt-injection record;
the MCP schema, plus a real stdio subprocess launched the way Claude Desktop launches it.
Eval result (gpt-oss-120b on Groq, demo data): 12/12 scenarios, with tool choice 12/12 and grounded answers 12/12. Groq rejected one malformed tool call (tool_use_failed); the harness resampled it once and counts that in the report. The scenarios cover cart recovery, disputes, order tracking, inventory, prompt injection and write refusal.
The first run scored 10/12, and the misses were useful:
A real product gap. For "is the mug available in Bengaluru?" the tool returned only "out of stock", so the model couldn't offer Delhi.
check_availabilitynow returnsother_locations, and the model answers "not from Bengaluru; Delhi NCR WH has 26".Two harness bugs. The model wrote
SO‑00104with a non-breaking hyphen andcan’twith a curly apostrophe, so the scorer now normalises punctuation. A provider error used to crash the whole run.
On the injection record, the model described the candle and noted that "the description field contains internal notes; no action is required". It listed no customer data and attempted no refund.
Re-run with make eval (free Groq key) after changing any tool description. A scripted oracle in the tests shows every scenario is answerable from the data.
Assumptions
The Razorpay order or payment ID is stored in the Zoho sales order's
reference_number. This is common for storefront syncs. If a merchant uses a custom field, that becomes a per-connection setting (see DESIGN "next steps").One connection = one Zoho organization. Multi-org merchants create one connection each.
Delivery facts are what the merchant recorded in Zoho. The connector does not query courier APIs.
The daily quota resets at midnight IST. Zoho resets per org day, and
ZOHO_QUOTA_UTC_OFFSET_MINUTESadjusts this.
Limitations
Verified against a live Zoho Inventory org (free plan, India DC) seeded with
scripts/seed_zoho.py. Runscripts/verify_live.pyto check yours. That run turned up four differences from Zoho's docs, now handled and covered by tests:contact details live on
contact_person_details;the ship date is
shipping_dateon package and shipment records;customer_nameis exact-match, so search usescustomer_name_contains;the best stock figure is
actual_available_for_sale_stock.
Delivery dates can be missing. When a shipment is marked delivered through the API (or without a date), Zoho leaves
shipment_delivered_dateempty. The connector reports this as an evidence gap rather than guessing a date.Single-warehouse orgs (Zoho's free plan) return no per-warehouse stock, so
location_namechecks explain that and point to total stock.Stock can be stale by up to 60s because of the read cache. Order data can be up to 30s old.
Pull only. No webhooks.
Search is limited to Zoho's exact, contains and starts-with matching.
The daily quota counter is per process host. Two servers on different machines for the same org would each count separately. A shared store such as Redis would fix that in production.
The demo mode is a test double, not a Zoho emulator. It implements only what this connector uses.
Project layout
src/zoho_connector/
config.py settings, data centres, read-only scopes, plan quotas
auth.py OAuth: consent URL, loopback callback, code exchange, single-flight refresh, revoke
store.py token storage: OS keyring / file / memory
ratelimit.py sliding window, daily quota, backoff
client.py Zoho HTTP client: auth, retries, error mapping, cache
models.py agent-facing output models, PII masking, untrusted text
tools.py the 9 capabilities (transport-agnostic)
server.py MCP adapter, tool descriptions, audit log
cli.py connect / status / serve / disconnect / spec / mock-server
mock/ mock Zoho accounts + Inventory API, fictional dataset
docs/ CAPABILITIES.md · DESIGN.md · IMPACT.md · tools.json
evals/ scenarios.json + run_eval.py
scripts/ demo.py · seed_zoho.py
tests/ 66 testsNo real customer data, passwords or keys are included. All demo records are fictional, and emails use the reserved example.com domain.
Available Tools
9 toolscheck_availabilityARead-onlyIdempotent
Can these SKUs be sold right now? Returns sellable yes/no with a reason per SKU (out of stock, insufficient quantity, inactive, unknown SKU). Call this before an agent pitches, recovers or promises any product to a customer.
Inputs: lines (required), location_name. Returns: note, as_of, results, all_sellable
connector_statusARead-onlyIdempotent
Which Zoho organization is connected, granted scopes, and how much of the per-minute and daily API budget is left. Costs no Zoho API calls.
Inputs: no named parameters. Returns: object
get_fulfilment_evidenceARead-onlyIdempotent
Proof-of-fulfilment pack for one order: every shipment with carrier, tracking (AWB), ship and delivery dates and items, a verdict (DELIVERED, PARTIALLY_DELIVERED, SHIPPED_NOT_DELIVERED, NOT_SHIPPED, CANCELLED), a one-line factual summary and the gaps that weaken it. Use for chargebacks/disputes and 'where is my order' questions.
Inputs: salesorder_id, reference_number, salesorder_number. Returns: source, summary, verdict, shipments, +11 more
get_itemARead-onlyIdempotent
Full detail for one item, including stock per warehouse/location. Give exactly one of item_id or sku.
Inputs: sku, item_id. Returns: sku, name, rate, unit, +9 more
get_sales_orderARead-onlyIdempotent
One sales order with line items, shipped quantities, packages and (masked) customer contact. Give exactly one identifier.
Inputs: salesorder_id, reference_number, salesorder_number. Returns: date, notes, total, status, +13 more
list_itemsARead-onlyIdempotent
Browse the merchant's catalogue with stock levels (all warehouses combined). Use for overviews such as 'what is running low?'. For specific SKUs use check_availability.
Inputs: cursor, status, sort_by, page_size, +1 more. Returns: results, has_more, returned, next_cursor
list_sales_ordersARead-onlyIdempotent
Browse sales orders, newest first, optionally by status and order-date range.
Inputs: cursor, status, date_to, date_from, +1 more. Returns: results, has_more, returned, next_cursor
search_itemsARead-onlyIdempotent
Find items by partial name or SKU when you do not know the exact SKU.
Inputs: query (required), field, match, cursor, +1 more. Returns: results, has_more, returned, next_cursor
search_sales_ordersARead-onlyIdempotent
Find sales orders by reference number, order number, customer or text. Give at least one criterion.
Inputs: text, cursor, date_to, date_from, +4 more. Returns: results, has_more, returned, next_cursor
Tool Schema Changelog
Recent tool additions, removals, and schema changes observed during successful MCP inspections.
Loading tool schema changelog…
TDQS
Scored across 9 tools
Each tool targets a distinct resource+action: availability check, catalogue browsing, single-item detail, item search, order listing, order search, single-order fetch, fulfilment evidence, and connector status. The list/search and get/list pairs are cleanly separated by explicit guidance in the descriptions (e.g. 'For specific SKUs use check_availability').
Nearly all tools follow a consistent verb_noun snake_case pattern (check_availability, list_items, get_item, search_sales_orders). The only deviation is the noun-only connector_status, which is a minor and understandable exception for a meta/status tool.
Nine tools is well-scoped for a merchant inventory/sales-order connector, with each tool earning its place and no redundant padding.
Read/lookup coverage is strong (items, availability, orders, fulfilment evidence, status), but the surface is entirely read-only: there are no tools to create or update items, adjust stock, create sales orders, or record shipments. These are notable gaps for a connector whose agents may need to act on inventory or orders, though some may be intentional scoping.
Related MCP Connectors
OAuth-protected product catalog, ratings, reviews, categories and guides for approved agents.
Structured B2B supply search, quoting, sandbox orders and fulfillment status for agents.
Connect AI to store orders, products and inventory with scoped access and human approvals.
Agent-native travel platform: read-only flight, hotel, and brand tools over MCP. OAuth sign-in.
Related MCP Servers
- AlicenseNot gradedqualityCmaintenanceEnables an AI agent to query a warehouse management system for orders, stock, stalled orders, and audit logs, and to make narrowly scoped writes by changing order status with a required reason or appending notes, with all writes recorded in a full audit trail.MIT
- AlicenseNot gradedqualityCmaintenanceEnables AI agents to answer merchant questions about stock levels, reorder alerts, sales orders, and customers through a read-only MCP interface, with OAuth 2.0, rate-limit handling, and a mock backend for testing.MIT
- AlicenseAqualityBmaintenanceEnables AI agents to securely access and reason over WooCommerce orders, products, and inventory through read-only MCP tools, with PII redaction, rate-limit handling, and bounded pagination.5MIT
- AlicenseBqualityCmaintenanceA read-only MCP server that lets an Agent Studio agent query a WooCommerce store's orders, products, inventory and customers, with pagination, filtering, typed errors and automatic rate-limit retry. It never mutates store data, keeping the blast radius of an agent's actions bounded to reads.7MIT