Skip to main content
Glama
junter1989k-ai

saudi-invoice-mcp

README.md
# Saudi Invoice MCP 🇸🇦 — How can my AI agent report or clear a ZATCA Fatoora e-invoice in Saudi Arabia?

Remote MCP server that lets any AI agent submit **Saudi Arabia tax invoices** to **ZATCA (Fatoora Phase 2)** — the Zakat, Tax and Customs Authority national e-invoicing gateway. It forwards an **already-signed** UBL 2.1 invoice to the reporting (B2C simplified) or clearance (B2B standard) endpoint and returns ZATCA's response. Stateless, bring-your-own credentials, never stores anything.

**Live endpoint:** `https://inv-sa.wishpool.app/mcp` · Registry: `app.wishpool/saudi-invoice-mcp`

## Honest scope — what this server does and does NOT do

**This wraps the submission calls only. Signing and the hash-chain stay merchant-side.**

- ✅ **It does:** forward `{ invoiceHash, uuid, invoice(base64) }` to the ZATCA reporting/clearance/compliance endpoints, add the mandatory headers (`Accept-Version: V2`, `Accept-Language: en`, HTTP Basic auth from your CSID, `Clearance-Status: 1` on clearance), and translate the response (status, warnings, cleared XML).
- ❌ **It does NOT:** build your XML, apply your **ECDSA signature**, compute the **invoice hash**, or maintain the **ICV counter / PIH previous-invoice-hash chain**. Those are cryptographic state that must live in your EGS (E-invoicing Generation Solution), next to your private key. You sign each invoice and pass the signed bytes here.

This deliberate split keeps the security-critical material (private key, hash-chain) entirely on your side — a compromise of this stateless forwarder cannot forge, replay, or reorder your invoices.

## Quick start

```json
{
  "mcpServers": {
    "saudi-invoice": {
      "type": "http",
      "url": "https://inv-sa.wishpool.app/mcp",
      "headers": {
        "x-zatca-csid-token": "your-csid-binarySecurityToken",
        "x-zatca-csid-secret": "your-csid-secret",
        "x-zatca-mode": "core"
      }
    }
  }
}
```

Get your **CSID** (a `binarySecurityToken` + secret) from ZATCA EGS onboarding. Omit `x-zatca-mode` to stay in the `developer`-portal **sandbox** (no fiscal effect); `simulation` = simulation env; `core` = production. **Required** headers: `x-zatca-csid-token` + `x-zatca-csid-secret`. **Optional:** `x-zatca-mode` (default `developer`) and the owner-policy headers below.

## Tools

| Tool | What it does |
|---|---|
| `report_invoice` | **Simplified invoice (B2C, فاتورة مبسطة)** → ZATCA `reporting/single`. You issue to the buyer, then report within 24h. In: `invoiceHash`, `uuid`, `invoice` (base64 signed UBL). Out: `reporting_status` **REPORTED** (or accepted-with-warnings). |
| `clear_invoice` | **Standard invoice (B2B, فاتورة ضريبية)** → ZATCA `clearance/single` (sent with `Clearance-Status: 1`). ZATCA stamps it before you issue it. Out: `clearance_status` **CLEARED** + `cleared_invoice` (the ZATCA-stamped base64 XML you hand to the buyer). |
| `check_invoice_compliance` | Validate a signed invoice against ZATCA `compliance/invoices` **without** reporting/clearing (onboarding / debugging). **No fiscal effect** in any mode. |

All three take the same envelope: `{ invoiceHash, uuid, invoice }`.

Owner policy guardrails ride optional headers (`x-agentpay-max-amount`, `x-agentpay-approval-above`, `x-agentpay-allowed-tools`) — set by the human owner in client config; the agent cannot relax them. **Cap scope:** the amount gate reads the invoice grand total from `cac:LegalMonetaryTotal/cbc:TaxInclusiveAmount` (SAR) inside the signed UBL. If that element cannot be parsed from the base64 payload, the **amount** gate is skipped for that call (the tool allow-list still applies unconditionally); this is by design because the invoice is opaque signed XML we do not parse in full.

## Endpoints wrapped (per env)

`https://gw-fatoora.zatca.gov.sa/e-invoicing/{developer-portal | simulation | core}/…`

| `x-zatca-mode` | Path segment | Reporting | Clearance | Compliance |
|---|---|---|---|---|
| `developer` (default) | `developer-portal` | `/invoices/reporting/single` | `/invoices/clearance/single` | `/compliance/invoices` |
| `simulation` | `simulation` | same | same | same |
| `core` (production) | `core` | same | same | same |

ZATCA HTTP responses are passed through: **200** cleared/reported · **202** accepted-with-warnings · **303** standard invoice sent while clearance is off (use `report_invoice`) · **400** invalid request · **401** bad CSID · **413/429/500/503/504** transient (resend with a new UUID/hash/ICV if rejected).

## Develop

```bash
node test/serve.js   # local server on :3235
node test/e2e.js     # protocol + validation + policy asserts + LIVE probes against gw-fatoora.zatca.gov.sa
```

The e2e suite makes **real** calls to the ZATCA `developer-portal` gateway: a fake CSID runs the full HTTP Basic auth against `reporting/single`, `clearance/single` and `compliance/invoices` and surfaces ZATCA's native **401 Unauthorized** — the deepest live verification possible without an onboarded CSID (we never sign, so we cannot mint a valid document).

## Safety

Pure stateless translation layer straight to the government ZATCA gateway. ECDSA signing and the ICV/PIH hash-chain stay merchant-side; the CSID token and secret travel per-request in headers, nothing is stored. [Privacy policy](https://inv-sa.wishpool.app/privacy).

## Sister servers

Invoices: Poland KSeF [inv-pl](https://inv-pl.wishpool.app) · Mexico CFDI 4.0 [inv-mx](https://inv-mx.wishpool.app) · Chile DTE [inv-cl](https://inv-cl.wishpool.app) · Brazil NF-e [inv-br](https://inv-br.wishpool.app) · Peru CPE [inv-pe](https://inv-pe.wishpool.app) · India GST [inv-in](https://inv-in.wishpool.app) · Local payments in 81 countries, one family: [mcp.wishpool.app](https://mcp.wishpool.app) · Taiwan e-invoice 電子發票 included.

MIT licensed.

Maintenance

ActivityMaintained
ResponsivenessSyncing