Skip to content

Repository files navigation

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 TypeScript Node License: MIT

MCP (Model Context Protocol) 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 — 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 Result types instead of throwing; inputs are validated with zod.

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.

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")]
Loading
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

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

{
  "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

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

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

Roadmap

  • MCP server over stdio + HTTP
  • Sandboxed file / web / task tools
  • SQLite FTS5 corpus with trigger-synced index
  • Unit tests for the security-critical paths + CI
  • Shared tool registry between the two transports
  • Vector search wired in: hybrid mode with Reciprocal Rank Fusion
  • @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

About

MCP server with sandboxed file/web/task tools and SQLite RAG — stdio + HTTP transports

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages