Skip to main content
Glama
shatrunjaysingh

indicus-ai-mcp-tools

README.md
# indicus-ai-mcp-tools

Demo services for the IndicusAI platform, served two ways at once:

- **REST**, at `/soc`, `/utility`, `/payer`, `/iam` — registered as built-in
  custom HTTP tools, one tool per endpoint
- **MCP**, at `/mcp` — one connector carrying every tool on the server

Both surfaces call the same functions over the same fixtures. That is the
point: a demo comparing built-in tools against MCP proves nothing if the two
run on different data, because any difference could be blamed on the data.

## Run it

```bash
python3 -m venv .venv && .venv/bin/pip install -e .
.venv/bin/uvicorn mcp_server:app --port 8304
curl -s localhost:8304/health | python3 -m json.tool
```

Or `docker compose up -d`, which joins the platform's network so the platform
reaches it as `http://mcp-tools:8304`.

## Adding a tool

One edit:

```python
@app.get("/widgets/{widget_id}", operation_id="getWidget",
         summary="Fetch one widget")
def get_widget(widget_id: str) -> dict:
    """What the model reads to decide whether to call this."""
    ...
```

It is now a REST endpoint **and** an MCP tool called `soc_getWidget`. Nothing
else to register — `mcp_server` walks the routes and every one carrying an
`operation_id` becomes a tool. A route without one is skipped, which is how the
audio download stays out of the tool list.

Three things the wrapper handles, each learned by hitting it:

- **Names are `{service}_{operationId}`.** MCP names are flat across a server
  while operation_ids are unique only per service. SOC and IAM both define
  `getIdentity`; unprefixed, the second silently replaced the first and an
  agent asking for a SOC identity got IAM's answer.
- **`HTTPException` becomes a value, not a protocol error.** Over REST a 404
  *is* the response; over MCP there are no status codes, so an uncaught one
  reads as a broken server. `{"error": "No alert ALT-9999.", "status": 404}` is
  a fact the model can act on.
- **Annotations are resolved eagerly.** `from __future__ import annotations`
  makes them strings, which pydantic resolves through the handler's own module
  globals — not something a wrapper can inherit.

### A new service

Write the FastAPI app in `services/`, then add it to `SERVICES` in
`mcp_server.py`. That is the whole procedure.

## Onboarding these tools into IndicusAI

What a tool is on that platform, the three ways one gets there, and how to
build both demos' agents and pipelines in the UI:
[demo-data-setup.md](https://github.com/shatrunjaysingh/IndicusAI/blob/main/demo-data-setup.md), in the platform
repository — that side of it is all IndicusAI, and this repository only
supplies the endpoints.

## Testing the DISCOM agents

[discom-agent-tests.md](https://github.com/shatrunjaysingh/IndicusAI/blob/main/discom-agent-tests.md), in the platform
repository — the walkthrough is done in that UI, and sits with the other
platform documents. It takes all ten through the browser one input at a time,
with what a correct answer contains and the wrong answer each case is designed
to catch.

The expected figures in it come from `services/discom_data.py` here, so a
change to a fixture means a change to that document.

## Seeding a demo

`seeds/` builds the workspace, skills, agents and pipeline in the platform.
They log in as `demo@example.com`, so `scripts/seed.py` in the platform repo
runs first.

**On a deployment**, run them inside the container — it already has the code
and its dependencies, and the compose file sets both hostnames:

```bash
docker compose exec mcp-tools python seeds/soc.py
docker compose exec mcp-tools python seeds/utility.py
```

**On a laptop**, where the platform and these services are both on localhost:

```bash
.venv/bin/python seeds/soc.py
.venv/bin/python seeds/utility.py
```

Two hostnames decide where a seed points, and both default to `127.0.0.1`,
which is correct only on a laptop — inside a container it *is* the container:

- `PLATFORM_API_URL` — the platform being seeded, `http://api:8000/api/v1`
  over the compose network
- `DEMO_HOST` — what goes into the registered tool URLs and their
  `allowed_hosts` pin, `mcp-tools` over the compose network

## The recordings

`services/generate_visit_recordings.py` uses macOS `say` and **cannot run on
Linux**. Generate on a Mac, copy `data/recordings/` to the deployment. `data/`
is gitignored — the fixtures are derived, not source.

Only the utility demo needs them, and only to run a visit end to end; seeding
works without.


## Sample client back offices: /shop, /miso, /power, /service

Three pretend businesses for the chat bot's **data connectors**: an online store
(Kettle & Co), an electricity utility (Brightline Power), and an appliance and
battery service company (FixRight Service). A connector is built and tested
against these exactly as against a real client's API, so no client is needed,
and no customer data is ever uploaded to IndicusAI.

| Business | Reads | Changes |
|---|---|---|
| `/shop` | customer by mobile, orders, one order | cancel, return, change address (refused in the wrong state) |
| `/miso` | the same API as `/shop`, with Miso's 300 orders (`IDS-240001` … `IDS-240300`, on `+919000…` test numbers) | the same |
| `/power` | accounts by mobile, bills, payment link, outage at the account's address, complaints | raise a complaint |
| `/service` | products with warranty, warranty by serial, service requests | book a visit, move a visit |

- **Auth:** `X-Api-Key: sample-client-key` (set `SAMPLE_CLIENT_API_KEY`). It is declared
  as an API-key security scheme, so an OpenAPI import treats it as a stored credential.
- **Specs:** `/shop/openapi.json`, `/miso/openapi.json`, `/power/openapi.json`, `/service/openapi.json`.
- **Test people** (the same in all three): `+919000000999` Asha Rao (Bengaluru),
  `+919000000111` Ravi Sen (Kolkata, which has a power cut today), `+919000000222` Meera Iyer
  (Chennai), `+919000000333` Arjun Mehta (Mumbai).
- **Data** is synthetic, in memory, and dated relative to today. `/power` and `/service`
  changes last until a restart.
- **Changing the stores' data for a test** (`/shop`, `/miso`): on the Swagger page
  (`/miso/docs`, `/shop/docs`), **Authorize** with the key, then under **Test data**:
  `PATCH /orders/{order_id}` changes any field you give — a new `phone` / `customer_name`
  moves the order to that customer; `POST /orders` adds one; `POST /reset` undoes every
  change. Changes (and the bot's cancels, returns and address changes) are saved in
  `state/<store>.json` and survive a restart until a reset. On the server the port is only
  on the box: `ssh -N -L 8304:127.0.0.1:8304 <server>` and open `localhost:8304/miso/docs`.

```bash
curl -s -H "X-Api-Key: sample-client-key" "localhost:8304/power/accounts?phone=9000000999"
```

Maintenance

ActivityMaintained
ResponsivenessNo issues