Skip to main content
Glama
README.md
# AI Ops Hub

**An MCP server that gives AI assistants safe, sandboxed hands on your machine** — notes, tasks, web pages, and hybrid search (FTS5 + embeddings) over a personal document corpus. Built with TypeScript, SQLite, and a security-first design.

[![CI](https://github.com/Galiusbro/ai-ops-hub/actions/workflows/ci.yml/badge.svg)](https://github.com/Galiusbro/ai-ops-hub/actions/workflows/ci.yml)
[![TypeScript](https://img.shields.io/badge/TypeScript-strict-3178C6.svg)](tsconfig.json)
[![Node](https://img.shields.io/badge/Node-%E2%89%A520-339933.svg)](package.json)
[![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE)

[MCP (Model Context Protocol)](https://modelcontextprotocol.io) is the open standard that lets AI clients like Claude Desktop call external tools. This server implements it **twice from one codebase**: over stdio for local clients and over HTTP for remote access.

## What it looks like in practice

Once connected to Claude Desktop, conversations like this just work:

> **You:** Find my notes about the Postgres migration and add a task to finish it by Friday.
>
> **Claude:** → `rag_search("postgres migration")` — 3 matching chunks from your corpus
> → `task_create("Finish Postgres migration", due: "2026-07-31")`
> *"Found your migration notes — the remaining step was the index rebuild. Task created for Friday."*

Every step happens inside the sandbox you configured: Claude can only touch the notes directory you allowed, only fetch from hosts you allowlisted, and only through the tools below.

## Tools

| Tool | What it does |
|---|---|
| `rag_search` | Search the corpus — `keyword` (FTS5), `vector` (embeddings), or `hybrid` (both, fused with Reciprocal Rank Fusion) |
| `rag_add_document` | Add or update a document: auto-chunked, FTS-indexed, embedded when vector search is configured |
| `rag_stats` | Corpus statistics: documents, chunks, embeddings, backend availability |
| `file_read` / `file_write` / `file_list` | Notes access — sandboxed to `NOTES_DIR`, allowlisted extensions only |
| `web_fetch` | Fetch a page from **allowlisted hosts only**, stripped to clean text (cheerio) |
| `task_create` / `task_list` / `task_complete` | Tasks stored as plain, human-editable markdown |

**Hybrid search** is the default when `OPENAI_API_KEY` is set: FTS5 and cosine-similarity results are merged with [Reciprocal Rank Fusion](https://plg.uwaterloo.ca/~gvcormac/cormacksigir09-rrf.pdf) — rank-based fusion that needs no score normalization between bm25 and cosine scales. If the vector backend fails mid-query, hybrid degrades gracefully to keyword results.

## Security model

Local tool access for an LLM is a security problem before it is anything else. The interesting engineering here:

- **Path sandboxing that survives the classic bypasses.** Every path resolves against `NOTES_DIR`; absolute paths, `../` traversal, and the sibling-prefix bypass (`notes` vs `notes-evil` — a bug most naive `startsWith` checks have) are rejected. Extension allowlist is enforced on both read and write.
- **Web fetching is deny-by-default.** `web_fetch` refuses any host not in `WEB_ALLOWED_HOSTS`. Subdomains of allowed hosts pass; lookalikes (`example.com.evil.com`) do not. HTTP(S) only.
- **The protocol channel stays clean.** All logging goes to stderr — on a stdio MCP server, stdout belongs to JSON-RPC and a single stray `console.log` corrupts the stream.
- **Typed failure paths.** The persistence layer returns [neverthrow](https://github.com/supermacro/neverthrow) `Result` types instead of throwing; inputs are validated with [zod](https://zod.dev).

All of this is pinned down by **45 unit tests** targeting exactly these properties — traversal attempts, prefix bypasses, lookalike domains, protocol filtering, rank fusion, and registry dispatch — running in CI on Node 20 and 22.

## Architecture

Both transports consume one `ToolRegistry` — a single source of truth for tool definitions and dispatch, so the stdio and HTTP surfaces can never drift apart.

```mermaid
flowchart LR
    CD[Claude Desktop] -- "stdio (JSON-RPC)" --> REG[ToolRegistry<br/>definitions + dispatch]
    RC[Remote client] -- "HTTP :3333" --> REG
    REG --> FS["FileService<br/>sandboxed notes"]
    REG --> WS["WebService<br/>allowlisted fetch"]
    REG --> TS["TaskService<br/>markdown store"]
    REG --> RAG["RAGService<br/>keyword | vector | hybrid"]
    RAG -- "FTS5 (bm25)" --> POOL["ConnectionPool"]
    RAG -- "embeddings + cosine" --> VEC["VectorRAGService"]
    VEC --> POOL
    RAG -- "RRF fusion" --> RAG
    POOL --> DB[("SQLite<br/>docs + chunks<br/>chunks_fts + chunk_vecs")]
```

```
src/
  server.ts               MCP entrypoint (SDK 1.x): wires services into the registry
  tools/
    registry.ts           single source of truth: tool schemas + dispatch
  transports/
    http-transport.ts     thin HTTP facade over the registry: /health, /tools, /call, /status
  connectors/
    file-service.ts       sandboxed file access
    web-service.ts        allowlisted web fetching + HTML cleaning
    task-service.ts       markdown-backed task store
  rag/
    rag-service.ts        search facade: keyword / vector / hybrid modes
    fusion.ts             Reciprocal Rank Fusion (pure, unit-tested)
    sqlite-client.ts      SQLite persistence: FTS5, chunking, migrations (neverthrow API)
    vector-rag-service.ts vector search with OpenAI embeddings
    embedding-service.ts  embedding generation (text-embedding-3-small)
  db/
    connection-pool.ts    SQLite connection pooling
```

## Quick start

```bash
git clone https://github.com/Galiusbro/ai-ops-hub.git && cd ai-ops-hub
npm install
cp .env.example .env    # adjust paths and allowlist
npm run build

npm start               # stdio only (for Claude Desktop)
npm run start:http      # stdio + HTTP facade on :3333
```

The HTTP facade is opt-in (`--http` flag or `HTTP_ENABLED=1`) so that MCP clients can spawn multiple server instances without port clashes.

### Connect to Claude Desktop

```json
{
  "mcpServers": {
    "ai-ops-hub": {
      "command": "node",
      "args": ["/absolute/path/to/dist/server.js"],
      "env": {
        "NOTES_DIR": "/path/to/your/notes",
        "RAG_DB_PATH": "/path/to/your/rag.db"
      }
    }
  }
}
```

### Or talk to it over HTTP

```bash
curl http://localhost:3333/health
curl http://localhost:3333/tools
curl -X POST http://localhost:3333/call \
  -H "Content-Type: application/json" \
  -d '{"name":"rag_search","arguments":{"query":"postgres migration"}}'
```

### Configuration

| Variable | Default | Purpose |
|---|---|---|
| `NOTES_DIR` | `./notes` | Directory the file tools are sandboxed to |
| `TASKS_FILE` | `./tasks.md` | Markdown file behind the task tools |
| `RAG_DB_PATH` | `./data/rag.db` | SQLite database for the corpus |
| `WEB_ALLOWED_HOSTS` | `example.com,developer.mozilla.org` | Comma-separated allowlist for `web_fetch` |
| `HTTP_PORT` | `3333` | HTTP transport port |
| `OPENAI_API_KEY` | — | Enables vector + hybrid search (embeddings) |

## Development

```bash
npm run dev          # run from source (tsx)
npm test             # vitest unit suite
npm run type-check   # tsc --noEmit
npm run lint
```

## Roadmap

- [x] MCP server over stdio + HTTP
- [x] Sandboxed file / web / task tools
- [x] SQLite FTS5 corpus with trigger-synced index
- [x] Unit tests for the security-critical paths + CI
- [x] Shared tool registry between the two transports
- [x] Vector search wired in: hybrid mode with Reciprocal Rank Fusion
- [x] `@modelcontextprotocol/sdk` 1.x
- [ ] Streamable HTTP transport from the SDK (replace the custom REST facade)
- [ ] Audit logging
- [ ] Local embedding backend as an alternative to OpenAI

## Why this exists

I built this to understand MCP from the inside — the protocol, the transports, and what it actually takes to hand an LLM safe access to a real machine. It grew into a working local-first assistant backend: the FTS5 corpus, the sandboxing, and the test suite are the parts I'd reuse in production.

## License

[MIT](LICENSE)

TDQS

B3/5.0

Scored across 7 tools

Disambiguation5/5

Each tool has a clearly distinct purpose with no overlap: file operations, RAG operations, task management, and web fetching are all separate domains. The descriptions clearly differentiate them, making misselection unlikely.

Naming Consistency4/5

Most tools follow a consistent verb_noun pattern (e.g., file_read, task_create, web_fetch), but 'rag_add_document' and 'rag_search' deviate slightly by using 'add' and 'search' as verbs instead of a uniform verb style. The naming is still readable and mostly predictable.

Tool Count5/5

With 7 tools, this server is well-scoped for an AI Ops Hub, covering key areas like file handling, RAG, task management, and web operations. Each tool earns its place without feeling excessive or insufficient.

Completeness4/5

The tool surface covers core operations for file I/O, RAG, tasks, and web fetching, but there are minor gaps such as missing update/delete for tasks or document management in RAG. Agents can likely work around these with the provided tools.

Maintenance

ActivityStale
ResponsivenessNo issues