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.