Skip to main content
Glama
AaltoSciComp

SciComp Docs Agent

Official
by AaltoSciComp
README.md
# SciComp Docs Assistant

Search and Q&A over [Aalto scicomp-docs](https://github.com/AaltoSciComp/scicomp-docs) (Triton HPC and related guides). 

Two ways to use this project:

| Use case | What you get | Needs LLM API key? |
|----------|--------------|-------------------|
| **[Web chat UI](#quick-start-web-chat-ui)** | Browser chat UI with tool-calling loop | **Yes** (Aalto LLM gateway + VPN) |
| **[MCP server](#quick-start-mcp-only)** | Docs search tool for AI agents (Codex, Cursor, Claude, etc.) | **No** |

Both paths share the same Python search stack in `app/doc_tools.py`. The web chat drives fine-grained tools via an LLM; MCP exposes a single one-shot `search_scicomp_docs` tool.

## Prerequisites

- **Python 3.12+** or **Docker**
- **Docs snapshot** in `docs-source/` (git submodule — required for both paths)
- **ripgrep** optional for local dev (`brew install ripgrep`); included in the Docker image
- **LLM API key** only for the [web chat UI](#quick-start-web-chat-ui)

## Clone

```bash
git clone --recurse-submodules https://github.com/AaltoSciComp/scicomp-docs-assistant.git
cd scicomp-docs-assistant
```

If you already cloned without submodules:

```bash
git submodule update --init --recursive
```

After a healthy clone, `/api/health` should report `docs_present: true` and `index_pages` in the **hundreds** (typically 300+). If `index_pages` is **0**, the docs submodule is missing — see [Troubleshooting](#troubleshooting).

---

## Quick start: Web chat UI

Browser-based agent that searches the local docs snapshot and cites `https://scicomp.aalto.fi/`.

**Requires:** Aalto VPN + API key from [llm-gateway.k8s.aalto.fi](https://llm-gateway.k8s.aalto.fi/).

### Docker (recommended)

```bash
cp .env.example .env
# Edit .env and set LLM_API_KEY=your-key-here

docker compose up --build
```

Open **http://localhost:8080**.

### Local Python

```bash
python3 -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt
cp .env.example .env   # set LLM_API_KEY

uvicorn app.main:app --reload --port 8080
```

Open **http://localhost:8080**.

### Using another LLM provider

The gateway client is OpenAI-compatible. Set in `.env`:

```bash
LLM_API_KEY=your-provider-key
LLM_BASE_URL=https://your-provider.example.com/v1
LLM_MODEL=your-model-id
```

The web UI and MCP share the same server process; MCP does not use the LLM.

---

## Quick start: MCP only

Expose a **single** docs search tool to any [MCP](https://modelcontextprotocol.io) client (Codex, Cursor, Claude Desktop, custom agents). No LLM key, no VPN.

The tool runs page ranking, keyword search, and excerpt reading internally.

### Option A — HTTP (Docker, recommended)

```bash
cp .env.mcp.example .env   # optional; compose works without .env for MCP-only
docker compose up --build
```

MCP endpoint: **http://localhost:8080/mcp/** (trailing slash required)

Verify:

```bash
curl -s http://localhost:8080/api/health | python3 -m json.tool
# expect: "docs_present": true, "index_pages": 300+ (approx), "llm_configured": false
```

### Option B — HTTP (local Python)

```bash
python3 -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt

uvicorn app.main:app --port 8080
```

MCP endpoint: **http://localhost:8080/mcp/**

### Option C — stdio (local MCP clients)

From the repo root, with dependencies installed:

```bash
source .venv/bin/activate   # after pip install -r requirements.txt
python -m app.mcp_stdio
```

**Cursor / VS Code client config** (use your venv Python path):

```json
{
  "mcpServers": {
    "scicomp-docs": {
      "command": "/path/to/scicomp-docs-assistant/.venv/bin/python",
      "args": ["-m", "app.mcp_stdio"],
      "cwd": "/path/to/scicomp-docs-assistant",
      "env": {
        "DOCS_ROOT": "/path/to/scicomp-docs-assistant/docs-source"
      }
    }
  }
}
```

### MCP tool

| Tool | Description |
|------|-------------|
| `search_scicomp_docs` | One-shot search: ranked pages + keyword hits + excerpts with published URLs |

Optional argument `path` limits the search (e.g. `triton/ref`, `aalto`).

Ask the agent in natural language (e.g. “How do I request a GPU on Triton?”); it should call `search_scicomp_docs` once and answer from the result.

### Connect from any MCP client (HTTP)

```json
{
  "mcpServers": {
    "scicomp-docs": {
      "url": "http://localhost:8080/mcp/"
    }
  }
}
```

Clients that only support stdio (e.g. Claude Desktop) can proxy via [mcp-remote](https://www.npmjs.com/package/mcp-remote):

```json
{
  "mcpServers": {
    "scicomp-docs": {
      "command": "npx",
      "args": ["-y", "mcp-remote", "http://localhost:8080/mcp/"]
    }
  }
}
```

### Connect from Python

Install the MCP client SDK on the **machine running your script** (`pip install "mcp>=1.19,<2"`):

```python
import asyncio
from mcp.client.session import ClientSession
from mcp.client.streamable_http import streamablehttp_client

async def main():
    async with streamablehttp_client("http://localhost:8080/mcp/") as (read, write, _):
        async with ClientSession(read, write) as session:
            await session.initialize()
            result = await session.call_tool("search_scicomp_docs", {"query": "GPU sbatch"})
            print(result.content[0].text)

asyncio.run(main())
```

> **Security:** `/mcp/` has no authentication. Bind to localhost or put a reverse proxy in front if exposing beyond your machine.

---

## How it works (web chat)

1. You ask a question in the browser UI.
2. The LLM receives a **search skill** (`skills/scicomp-docs-search.md`).
3. The model calls tools: `find_pages` → `get_doc_outline` / `search_docs` → `read_doc`.
4. It answers with citations as **published URLs** on https://scicomp.aalto.fi/.

## Updating the documentation snapshot

```bash
git submodule update --remote docs-source
docker compose up --build   # rebuild image so docs-source is refreshed
```

## Configuration

| Variable | Default | Description |
|----------|---------|-------------|
| `LLM_API_KEY` | (empty) | Required for **web chat** only |
| `LLM_BASE_URL` | `https://llm-gateway.k8s.aalto.fi/api/v1` | OpenAI-compatible base URL |
| `LLM_MODEL` | `RedHatAI/gemma-4-31B-it-FP8-Dynamic` | Model id |
| `LLM_TEMPERATURE` | `0.15` | Sampling temperature |
| `LLM_MAX_TOKENS` | `4096` | Max completion tokens |
| `AGENT_MAX_TOOL_ROUNDS` | `18` | Max tool-calling rounds per chat turn |
| `DOCS_ROOT` | `./docs-source` | Path to scicomp-docs tree |
| `DOCS_SITE_BASE_URL` | `https://scicomp.aalto.fi` | Published site for citations |
| `SKILLS_DIR` | `./skills` | Agent skill markdown files (**web chat** only) |
| `HOST` | `0.0.0.0` | Bind address (local uvicorn) |
| `PORT` | `8080` | Listen port (local uvicorn) |

Env templates:

- `.env.example` — full template (web chat + MCP)
- `.env.mcp.example` — minimal MCP-only (`LLM_API_KEY` empty)

## Troubleshooting

| Symptom | Likely cause | Fix |
|---------|--------------|-----|
| `index_pages: 0` or `docs_present: false` | Submodule not initialized | `git submodule update --init --recursive`, then rebuild/restart |
| MCP client can't connect | Wrong port or missing trailing slash | Use `http://localhost:8080/mcp/` (trailing slash required) |
| MCP works but tools return nothing | Empty `docs-source/` | Same as above; check health endpoint |
| Chat returns **503** “LLM_API_KEY is not configured” | Key not set | Add `LLM_API_KEY` to `.env` (not needed for MCP) |
| Chat errors / timeouts from LLM | VPN off or wrong gateway | Connect to Aalto VPN; verify key at llm-gateway.k8s.aalto.fi |
| `docker compose` fails on `.env` | Old Compose version | Upgrade Docker Desktop, or `cp .env.mcp.example .env` |
| Slow local search | ripgrep not installed | `brew install ripgrep` or use Docker |
| stdio MCP: “docs not found” warning | Wrong `cwd` or `DOCS_ROOT` | Run from repo root; set `DOCS_ROOT` in client config |
| Docker build: can't pull `python` image | Docker Hub unreachable or rate-limited | Use a mirror, e.g. `public.ecr.aws/docker/library/python:3.12-slim-bookworm` |

**Health check:**

```bash
curl -s http://localhost:8080/api/health | python3 -m json.tool
```

Healthy MCP + docs: `docs_present: true`, `index_pages` > 0. Web chat additionally needs `llm_configured: true`.