Skip to main content
Glama
samusimonfoti-arch

business-ai-bridge

README.md
# business-ai-bridge

**Connect an AI assistant to a business's existing systems — its database, its CRM, and the desktop software that has no API — without letting it guess, overreach, or write anything nobody approved.**

![The demo session: questions answered with sources, a draft order stopped at the gate, the assistant's figures re-checked](docs/demo.gif)

Most "chat with your data" setups let a model write SQL and hope. This toolkit
takes the opposite approach, built from real integration work:

- **Ask:** the assistant gets narrow, typed [MCP](https://modelcontextprotocol.io) tools, one per business question
  (`customer_overview`, `stock_level`, `overdue_invoices`, ...). Every answer says
  which system each figure came from and how old it is, and flags stale data.
- **Change:** writes go through a gate. `propose_order_draft` returns a priced preview
  with stock and credit checks and writes nothing. Only `confirm(token)` writes, once,
  after re-checking that nothing in the preview has changed. Then it reads the result
  back and compares it with what was approved.
- **Check:** `bridge eval` recomputes the figures the assistant stated from the
  source systems, with separate code and no model, and reports mismatches and stale sources.

It connects to SQL databases (SQLite, PostgreSQL), REST APIs, and folders of CSV/Excel
exports. The exports pattern covers desktop software that can only export
reports on a schedule: each export is recorded in a manifest (finish time, row
count, SHA-256), so a half-written file is refused and the data's real age is known.

## Run the demo (one command)

```bash
docker compose run --rm demo
```

This starts a fictional bicycle-parts wholesaler, **Acme Cycle Parts**, with
three systems and synthetic data: an orders database (PostgreSQL), a CRM (REST
API) and a warehouse program that only exports files. Then it plays the
assistant's side of a session through a real MCP client.

Without Docker (Python 3.10+):

```bash
pip install -e '.[demo]'
bridge demo            # SQLite + a local fake CRM; asks before approving the draft
```

A real run (`bridge demo --yes`):

```text
Acme Cycle Parts (fictional) · business-ai-bridge demo
Connected: orders_db (sql), crm (rest), warehouse (files)

[1] "How many 11-speed chains do we have?"
    stock_level(sku="CH-11S")
    -> 142 on hand, 30 reserved, 112 available
       source: warehouse/stock.csv exported 2.0 h ago (fresh)

[2] "How is Bike Shop Alpha doing?"
    customer_overview(customer_id="C-0001")
    -> revenue last 90 days 3,000.80 EUR, 7 open orders, 1 overdue invoice(s) (438.40 EUR, 12 days late)
       credit limit 23,409.75 EUR, used 2,774.80 EUR, left 20,634.95 EUR
       sources: crm/customers (live), orders_db/orders (live), orders_db/invoices (live)

[3] "Order 20 chains for Bike Shop Alpha."
    propose_order_draft(customer_id="C-0001", lines=[{"sku": "CH-11S", "qty": 20}])
    -> PREVIEW, NOT APPLIED: 20 x CH-11S at 18.40 = 368.00 EUR
       checks: stock ok (112 available), credit left 20,634.95 EUR -> 20,266.95 EUR
       warning: warehouse/prices.xlsx, exported 31.0 h ago (STALE, limit 24 h): confirm figures before approving
    Approve this draft? [y/N] y
    confirm(token="y6r1Xz...")
    -> applied: draft order SO-1601 written
       read back from orders_db: SO-1601, status draft, 368.00 EUR (matches the preview)
    confirm(token="y6r1Xz...")
    -> refused: unknown, used or expired token; make a new proposal

[4] "Also 5 disc brake pads." (the warehouse export lands between preview and approval)
    propose_order_draft(customer_id="C-0001", lines=[{"sku": "BP-DISC", "qty": 5}])
    confirm(token="Txpsqy...")
    -> refused: the data changed since the preview (lines.0.available, sources.warehouse/stock.csv.as_of); nothing written, make a new proposal

[5] Checking the assistant's answers against the sources (separate code, no model)
    PASS      Chains 11-speed on hand       claimed        142  source        142
    PASS      Bike Shop Alpha revenue 90 d  claimed   3,000.80  source   3,000.80
    MISMATCH  Bike Shop Alpha open orders   claimed          6  source          7  off by -1
    PASS      Bike Shop Alpha credit limit  claimed  23,409.75  source  23,409.75
    PASS      Chain 11-speed list price     claimed      18.40  source      18.40
    STALE     warehouse/prices.xlsx is 31.0 h old (limit 24 h): answers from it may be out of date
```

The open-orders figure in step 5 is misstated on purpose, to show a mismatch being
caught. The run also writes an HTML version of the check (`eval-report.html`).

## Connect your own systems

One YAML file describes the systems. Secrets come only from environment variables.

```yaml
connectors:
  orders_db: {type: sql, url: "${ORDERS_DB_URL}"}            # opened read-only
  crm:
    type: rest
    base_url: https://crm.internal.example
    api_key_env: CRM_API_KEY                                 # read at request time, never logged
    tables:
      customers:
        path: /customers
        columns: [customer_id, name, city, credit_limit, account_manager, email]
  warehouse: {type: files, folder: /srv/exports, max_age_hours: 24, require_manifest: true}
pack: {type: wholesale, orders: orders_db, crm: crm, warehouse: warehouse}
writes:
  create_order_draft: {url: "${ORDERS_DB_WRITE_URL}"}        # separate writable connection
gate: {store: /var/lib/bridge/gate.db}
```

```bash
bridge serve --config bridge.yaml                    # MCP over stdio (Claude Desktop, IDEs)
bridge serve --config bridge.yaml --transport http   # MCP over streamable HTTP
bridge eval claims.json --config bridge.yaml --html report.html
bridge audit --config bridge.yaml                    # every proposal, confirmation and outcome
```

A full working config is in [`demo/bridge.compose.yaml`](demo/bridge.compose.yaml).

## How it is built

The design decisions and their trade-offs are in **[ARCHITECTURE.md](ARCHITECTURE.md)**. In short:

| Part | What it guarantees |
|---|---|
| Connector contract | Every connector passes the same 15-test conformance suite (CSV, Excel with manifest, SQLite, REST, PostgreSQL in CI). Unknown tables and columns are refused with a clear error, before any query is sent; filters are parameters, never string-formatted. |
| Write gate | Token works once (atomic, across processes), expires, and is refused if anything in the preview changed, including a source going stale; audit is written before the business system; the outcome is decided by read-back (every line and the total), so a timeout after a successful write is not reported as a failure. |
| Evaluation | No model in the loop; exact `Decimal` arithmetic; malformed input is an error, never a pass; a stale source fails the run. |
| Money | Tools compute in `Decimal` (from database NUMERIC, JSON numbers, Excel cells or CSV text) and MCP responses carry amounts as strings, never floats. |

Tests run on Python 3.10 and 3.12 in CI, against PostgreSQL, and CI runs the Docker demo end to end.
Before release, an adversarial review found 12 defects (and some overclaims in these docs). Each defect has a test in
[`tests/test_review_regressions.py`](tests/test_review_regressions.py) that failed before its fix.

```bash
pip install -e '.[test,postgres]'
pytest -q
```

## Limits

- The bridge acts as one service account; per-user permissions are not built.
- The gate can make sure nothing is written without a separate, approved confirmation.
  It cannot prove a human made that call; that relies on the MCP client asking the user
  before running a tool marked destructive. MCP elicitation is the next step.
- Range filters and joins run in tool code after fetching; for large tables, expose a database view.
- The demo data is synthetic and the company is fictional.

## About

Built by Samuel Simon-Fóti, automation developer in The Hague. The patterns come from
connecting AI assistants to real business software, including desktop programs with
no API. This repository is a clean, generic version with a fictional company.

MIT licensed.