MCP-RAGAnything

Multi-modal RAG service exposing a REST API and MCP server for document indexing and knowledge-base querying, powered by RAGAnything and LightRAG. Two retrieval pathways are available: a graph-based LightRAG pipeline and a classical RAG pipeline using multi-query generation with LLM-as-judge scoring. Files are retrieved from MinIO object storage and indexed into a PostgreSQL-backed knowledge graph. Each project is isolated via its own working_dir.

The service also hosts the MCP server registry — a CRUD API for registering external MCP servers and generating new MCP servers on the fly from OpenAPI/Swagger documents. The registry is persisted in PostgreSQL and rehydrated at startup, so composable-agents and other clients can discover all available MCP servers from a single endpoint. See MCP Server Registry.

Branch feat/dual-auth-rls-llm-peruser introduces a new security model: dual authentication (JWT/OIDC via Logto or per-user API keys shared with composable-agents), Row-Level Security on mcp_servers scoped by user_id, per-user LLM credentials (decrypted from the shared user_llm_settings table), and per-user RAG isolation (metadata-level user_id filter on chunks). See Authentication, Configuration, and Database Schema for the details. The legacy master API_KEY and OPEN_ROUTER_API_KEY for chat/embeddings are deprecated (see Breaking changes).

Architecture

                            Clients
                     (REST / MCP / Claude)
                               |
                 +-------------+-------------+
                 |          FastAPI App        |
                 +-------------+-------------+
                               |
               +---------------+---------------+
               |                               |
        Application Layer            MCP Servers (FastMCP)
        +------------------------------+       |
        | api/                         |   +---+--------+  +--+-----------+  +--+-------------+  +--+----------+
        |   indexing_routes.py         |   | RAGAnything |  | RAGAnything |  | RAGAnything    |  | RAGAnything |
        |   query_routes.py            |   | Query       |  | Files       |  | Classical      |  | Bricks     |
        |   file_routes.py             |   |  /rag/mcp   |  |  /files/mcp |  |  /classical/mcp|  | /bricks/mcp|
        |   health_routes.py           |   +---+--------+  +--+-----------+  +--+-------------+  +--+----------+
        |   classical_indexing_routes   |       |               |                 |                  |
        |   classical_query_routes      |       |               |         classical_index_file   list_bricks_documents
        | use_cases/                   |       |               |         classical_index_folder  read_bricks_document
        |   IndexFileUseCase           |       |               |         classical_query         publish_section_version
        |   IndexFolderUseCase         |
        |   QueryUseCase               |
        |   ClassicalIndexFileUseCase   |
        |   ClassicalIndexFolderUseCase |
        |   ClassicalQueryUseCase       |
         |   ListFilesUseCase           |
         |   ListFoldersUseCase         |
         |   ReadFileUseCase            |
         |   UploadFileUseCase          |
         |   CreateFolderUseCase        |
         |   DeleteFileUseCase          |
         |   DeleteFolderUseCase        |
         |   ListBricksDocumentsUseCase |
        |   ReadBricksDocumentUseCase  |
        |   PublishSectionVersionUseCase|
        | requests/ responses/         |
        +------------------------------+
                 |         |          |
                 v         v          v
      Domain Layer (ports)
      +----------------------------------------------------------+
      | RAGEnginePort  StoragePort  BM25EnginePort              |
      | DocumentReaderPort  VectorStorePort  LLMPort            |
      | BricksApiPort                                            |
      +----------------------------------------------------------+
               |         |          |            |      |
               v         v          v            v      v
      Infrastructure Layer (adapters)
      +----------------------------------------------------------+
       | LightRAGAdapter       MinioAdapter                        |
       | (RAGAnything/         (minio-py)                          |
       |  KreuzbergParser)                                         |
      |                                                            |
      | PostgresBM25Adapter       RRFCombiner                      |
      | (pg_textsearch)            (hybrid+ fusion)                |
      |                                                            |
      | KreuzbergAdapter          LangchainPgvectorAdapter         |
      | (kreuzberg - 91 formats) (langchain-postgres PGVector)    |
      |                                                            |
      | LangchainOpenAIAdapter    BricksApiAdapter                 |
      | (langchain-openai ChatOpenAI)  (httpx, Bricks REST API)   |
      +----------------------------------------------------------+
               |         |          |            |      |
               v         v          v            v      v
         PostgreSQL        MinIO       Kreuzberg    OpenAI-compatible    Bricks API
         (pgvector +     (object     (document     (LLM API)          (analyse.bricks.co
          Apache AGE      storage)    extraction)                      + section-versions)
          pg_textsearch)

Prerequisites

  • Python 3.13+
  • Docker and Docker Compose
  • A Logto instance (for JWT/OIDC authentication) — optional if you only use per-user API keys
  • Per-user LLM credentials configured in composable-agents (table user_llm_settings), or an OpenRouter API key as fallback for local dev / Kreuzberg VLM
  • The soludev-compose-apps/bricks/ stack for production deployment (provides PostgreSQL, MinIO, and this service)

Quick Start

Production runs from the shared compose stack at soludev-compose-apps/bricks/. The docker-compose.yml in this repository is for local development only.

Local development

# 1. Install dependencies
uv sync

# 2. Start PostgreSQL and MinIO (docker-compose.yml provides Postgres;
#    MinIO must be available separately or added to the compose file)
docker compose up -d postgres

# 3. Configure environment
cp .env.example .env
# Edit .env: set LOGTO_URL + JWT_AUDIENCE for JWT auth, SECRET_ENCRYPTION_KEY
# (shared with composable-agents), and adjust MINIO_HOST / POSTGRES_HOST.
# OPEN_ROUTER_API_KEY is only needed for the VLM and as a fallback when auth
# is disabled (per-user LLM credentials take precedence on authenticated requests).

# 4. Run the server
uv run python src/main.py

The API is available at http://localhost:8000. Swagger UI at http://localhost:8000/docs.

Production (soludev-compose-apps)

cd soludev-compose-apps/bricks/
docker compose up -d

This starts all brick services including raganything-api, postgres, and minio.

Authentication

The service supports dual authentication on every protected REST and MCP endpoint:

  1. JWT (OIDC)Authorization: Bearer <jwt> issued by a Logto instance. The token is validated against the Logto JWKS, the aud claim is checked against JWT_AUDIENCE, and the user identity is extracted from the JWT claims.
  2. Per-user API keyX-API-Key: <user-api-key> stored in the shared api_keys table (owned by composable-agents, see Shared tables). The key is looked up, the bound user_id becomes the request identity.

The middleware accepts either header on the same request. If both are present, JWT takes precedence. The resolved user_id is stored in a request-scoped contextvar (current_user_id) and is used for:

Enabling authentication

Both modes are active as soon as the relevant env vars are set:

LOGTO_URL=https://logto.example.com
JWT_AUDIENCE=https://raganything.soludev.tech
SECRET_ENCRYPTION_KEY=<fernet-key-shared-with-composable-agents>

SECRET_ENCRYPTION_KEY must be the same Fernet key as composable-agents, because the api_keys and user_llm_settings tables store encrypted values that this service decrypts (see Shared tables).

Disabling authentication

Leave LOGTO_URL and JWT_AUDIENCE empty to disable JWT validation. Per-user API keys still work if rows exist in api_keys. For local development with no auth at all, leave everything empty — health endpoints are always public.

REST usage

# JWT (Logto OIDC)
curl -H "Authorization: Bearer ${JWT}" \
     -H "Content-Type: application/json" \
     -X POST http://localhost:8000/api/v1/classical/query \
     -d '{"working_dir": "project-alpha", "query": "test"}'

# Per-user API key
curl -H "X-API-Key: ${USER_API_KEY}" \
     -H "Content-Type: application/json" \
     -X POST http://localhost:8000/api/v1/classical/query \
     -d '{"working_dir": "project-alpha", "query": "test"}'

Health endpoints (/api/v1/health, /api/v1/health/live) remain public regardless of the auth configuration.

MCP usage

The McpApiKeyMiddleware accepts both X-API-Key and Authorization: Bearer via FastMCP's get_http_headers. MCP clients must send one of them in their HTTP transport configuration. For composable-agents, add the header to the headers field of each MCP server config:

mcp_servers:
  - name: bricks
    transport: http
    url: https://raganything.soludev.tech/bricks/mcp
    headers:
      Authorization: "Bearer ${LOGTO_USER_TOKEN}"   # or
      # X-API-Key: "${MCP_RAGANYTHING_API_KEY}"
  - name: files
    transport: http
    url: https://raganything.soludev.tech/files/mcp
    headers:
      Authorization: "Bearer ${LOGTO_USER_TOKEN}"
  - name: classical
    transport: http
    url: https://raganything.soludev.tech/classical/mcp
    headers:
      Authorization: "Bearer ${LOGTO_USER_TOKEN}"

Protected endpoints

Layer Protected Public
REST /api/v1/files/*, /api/v1/files, /api/v1/classical/*, /api/v1/file/*, /api/v1/folder/*, /api/v1/query, /api/v1/mcp/servers* /api/v1/health, /api/v1/health/live
MCP tools/call, tools/list (all 4 servers + generated servers) initialize

Per-user LLM credentials

When the current_user_id contextvar is set (i.e. the request was authenticated), the LLM/embeddings factories resolve credentials from the shared user_llm_settings table (owned by composable-agents) instead of the static LLMConfig env vars:

  • get_chat_llm_for_user(current_user_id) — returns a ChatOpenAI configured with the user's decrypted api_key, base_url, and model.
  • get_embedding_for_user(current_user_id) — returns an OpenAIEmbeddings configured with the user's credentials.
  • get_vector_store_for_user(current_user_id, working_dir) — returns a PGVectorStore bound to the user's embedding model + dimension.

Decryption is done by AsyncpgUserLlmReader using the existing FernetSecretCipher and the shared SECRET_ENCRYPTION_KEY. If the user has no row in user_llm_settings, the request returns 422 LlmNotConfiguredError with a message telling the user to configure their LLM credentials in composable-agents.

When current_user_id is None (e.g. local dev with auth disabled), the factories fall back to the static LLMConfig env vars (OPEN_ROUTER_API_KEY, CHAT_MODEL, EMBEDDING_MODEL, etc.). This preserves the legacy local-dev workflow.

Per-user RAG isolation

Classical RAG chunks are tagged with user_id in their langchain_metadata at index time. At query time, the langchain-postgres metadata filter {"user_id": current_user_id} is applied so a user only retrieves their own chunks. This is an application-level filter — RLS is not applied on the dynamic classical_rag_* tables because PGVectorStore uses its own connection pool and the app.user_id GUC is not propagated there (same conclusion as the LangGraph store in composable-agents).

Shared tables

Two tables are owned by composable-agents (created by its Alembic migrations) and read by mcp-raganything:

Table Owner Purpose mcp-raganything access
api_keys composable-agents Per-user API keys (hashed) + user_id binding Read (auth lookup) with SET LOCAL row_security = off
user_llm_settings composable-agents Per-user LLM credentials (Fernet-encrypted) Read (credential resolution) with SET LOCAL row_security = off

mcp-raganything reads these tables as a privileged auth/credential-resolution operation: the asyncpg connection sets SET LOCAL row_security = off for the duration of the lookup, then the connection is returned to the pool. RLS on these tables is enforced by composable-agents for its own writes.

The two services share the same PostgreSQL database (raganything) but use separate Alembic version tables: composable-agents uses alembic_version, mcp-raganything uses raganything_alembic_version (see Database Schema). This lets both services evolve their schemas independently without colliding.

Breaking changes

  • API_KEY (master key) deprecated. The single shared X-API-Key: <master> mode is no longer the primary auth path. The env var is still read for backward compatibility but per-user API keys (from the shared api_keys table) are the recommended replacement. Migrate by creating per-user keys in composable-agents.
  • OPEN_ROUTER_API_KEY deprecated for chat + embeddings. When a request is authenticated (current_user_id set), the per-user user_llm_settings row is used instead. OPEN_ROUTER_API_KEY is still used for the Kreuzberg VLM (vision model OCR in the LightRAG pipeline) because the VLM is not user-scoped — this is a documented limitation. To fully deprecate OPEN_ROUTER_API_KEY, the VLM must be made user-scoped too (future work).
  • mcp_servers is now per-user. A user can only see, create, update, and delete their own MCP servers. Cross-user create with the same name returns 409 Conflict (BUG-001 fix: ON CONFLICT DO UPDATE is scoped by user_id, so a same-name server from another user is not silently overwritten).

API Reference

Base path: /api/v1

Health

# Health check
curl http://localhost:8000/api/v1/health

Response:

{"message": "RAG Anything API is running"}

Indexing

Both indexing endpoints accept JSON bodies and run processing in the background. Files are downloaded from MinIO, not uploaded directly.

Index a single file

Downloads the file identified by file_name from the configured MinIO bucket, then indexes it into the RAG knowledge graph scoped to working_dir.

curl -X POST http://localhost:8000/api/v1/file/index \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer ${JWT}" \
  -d '{
    "file_name": "project-alpha/report.pdf",
    "working_dir": "project-alpha"
  }'

Response (202 Accepted):

{"status": "accepted", "message": "File indexing started in background"}
Field Type Required Description
file_name string yes Object path in the MinIO bucket
working_dir string yes RAG workspace directory (project isolation)

Index a folder

Lists all objects under the working_dir prefix in MinIO, downloads them, then indexes the entire folder.

curl -X POST http://localhost:8000/api/v1/folder/index \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer ${JWT}" \
  -d '{
    "working_dir": "project-alpha",
    "recursive": true,
    "file_extensions": [".pdf", ".docx", ".txt"]
  }'

Response (202 Accepted):

{"status": "accepted", "message": "Folder indexing started in background"}
Field Type Required Default Description
working_dir string yes -- RAG workspace directory, also used as the MinIO prefix
recursive boolean no true Process subdirectories recursively
file_extensions list[string] no null (all files) Filter by extensions, e.g. [".pdf", ".docx", ".txt"]

Supported Document Formats

The service automatically detects and processes the following document formats through the RAGAnything parser:

Format Extensions Notes
PDF .pdf Includes OCR support (English + French via Tesseract)
Microsoft Word .docx
Microsoft PowerPoint .pptx
Microsoft Excel .xlsx
HTML .html, .htm
Plain Text .txt, .text, .md UTF-8, UTF-16, ASCII supported; converted to PDF via ReportLab
Quarto Markdown .qmd Quarto documents
R Markdown .Rmd, .rmd R Markdown files
Images .png, .jpg, .jpeg, .gif, .webp, .bmp, .tiff, .tif Vision model processing (if enabled)

Note: File format detection is automatic. No configuration is required to specify the document type. The service will process any supported format when indexed. All document and image formats are supported out-of-the-box when installed with raganything[all].

File Browsing & Reading

Browse and read files directly from MinIO without indexing them into the RAG knowledge base. Powered by Kreuzberg for document text extraction (91 file formats).

List files

# List all files in the bucket
curl -H "Authorization: Bearer ${JWT}" \
     http://localhost:8000/api/v1/files/list

# List files under a specific prefix
curl -H "Authorization: Bearer ${JWT}" \
     "http://localhost:8000/api/v1/files/list?prefix=documents/&recursive=true"

Response (200 OK):

[
  {"object_name": "documents/report.pdf", "size": 1024, "last_modified": "2026-01-01 00:00:00+00:00"},
  {"object_name": "documents/notes.txt", "size": 512, "last_modified": "2026-01-02 00:00:00+00:00"}
]
Parameter Type Default Description
prefix string "" MinIO prefix to filter files by
recursive boolean true List files in subdirectories

Upload a file

Uploads a file directly to the MinIO bucket. The file is stored at {prefix}{filename}. This endpoint does not index the file — use the POST /file/index endpoint after uploading to add it to the RAG knowledge base.

Allowed file types: .pdf, .txt, .docx, .xlsx, .pptx, .md, .csv, .png, .jpg, .jpeg, .gif, .webp, .svg, .bmp, .html, .xml, .json, .rtf, .odt, .ods Maximum file size: 50 MB

curl -X POST http://localhost:8000/api/v1/files/upload \
  -H "Authorization: Bearer ${JWT}" \
  -F "[email protected]" \
  -F "prefix=documents/"

Response (201 Created):

{"object_name": "documents/report.pdf", "size": 2048, "message": "File uploaded successfully"}
Field Type Required Default Description
file file yes -- The file to upload (multipart form)
prefix string no "" MinIO prefix (folder path). Must be a relative path

Error responses:

Status Condition
413 File exceeds 50 MB limit
422 Invalid prefix (path traversal/absolute), disallowed file type, or missing file

Read a file

Downloads the file from MinIO, extracts its text content using Kreuzberg, and returns the result. Supports 91 file formats including PDF, Office documents, images, and HTML.

curl -X POST http://localhost:8000/api/v1/files/read \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer ${JWT}" \
  -d '{"file_path": "documents/report.pdf"}'

Response (200 OK):

{
  "content": "Extracted text from the document...",
  "metadata": {"format_type": "pdf", "mime_type": "application/pdf"},
  "tables": [{"markdown": "| Header | Value |\n|---|---|\n| A | 1 |"}]
}
Field Type Description
file_path string Required. File path in the MinIO bucket (relative, no .. or absolute paths)

Error responses:

Status Condition
404 File not found in MinIO
422 Unsupported file format or invalid path (path traversal, absolute path)

Create a folder

Creates a folder marker in the MinIO bucket by writing a 0-byte object whose object name ends with a trailing /. The folder is purely a prefix — MinIO does not have a real folder concept. This endpoint does not index anything.

curl -X POST http://localhost:8000/api/v1/files/folders \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer ${JWT}" \
  -d '{"prefix": "documents/reports/"}'

Response (201 Created):

{"message": "Folder created", "prefix": "documents/reports/"}
Field Type Required Default Description
prefix string yes -- Folder prefix to create. Must be a relative path; a trailing / is preserved

Error responses:

Status Condition
422 Missing, absolute, or path-traversing prefix

Delete a file (cascade)

Deletes a single object from the MinIO bucket and removes its corresponding vectors from the pgvector store. The object_name and working_dir are both required query parameters.

Deletion is performed in two steps, strictly in order:

  1. MinIO first — the object is removed from the storage bucket.
  2. pgvector second — vectors associated with the file (scoped to working_dir) are deleted from the vector store.

If the MinIO deletion fails, the pgvector store is not touched, ensuring no orphaned vectors are created from a failed storage operation.

curl -X DELETE "http://localhost:8000/api/v1/files?object_name=documents/report.pdf&working_dir=project-alpha" \
  -H "Authorization: Bearer ${JWT}"

Response (200 OK):

{"message": "File deleted", "object_name": "documents/report.pdf"}
Parameter Type Required Default Description
object_name string yes -- Object path to delete. Must be a relative path within the bucket
working_dir string yes -- RAG workspace directory (project isolation). Used to scope the pgvector deletion

Error responses:

Status Condition
422 Missing, absolute, or path-traversing object_name or working_dir

Delete a folder (recursive, cascade)

Deletes all objects whose object names start with prefix from the MinIO bucket and removes all corresponding vectors from the pgvector store. The deletion is recursive and permanent — there is no confirmation beyond the API call. A trailing / is preserved so that a prefix like documents/ does not match documents-archive/.

The prefix is automatically used as the working_dir for the pgvector deletion — no separate working_dir parameter is needed on this endpoint.

Deletion is performed in two steps, strictly in order:

  1. MinIO first — all objects under the prefix are removed from the storage bucket.
  2. pgvector second — all vectors matching the prefix are deleted from the vector store.

If the MinIO deletion fails, the pgvector store is not touched.

curl -X DELETE "http://localhost:8000/api/v1/files/folders?prefix=documents/reports/" \
  -H "Authorization: Bearer ${JWT}"

Response (200 OK):

{"message": "Folder deleted", "prefix": "documents/reports/"}
Parameter Type Required Default Description
prefix string yes -- Folder prefix to delete. Must be a relative path; trailing / is preserved. Also used as the working_dir for pgvector cleanup

Error responses:

Status Condition
422 Missing, absolute, or path-traversing prefix

Path traversal protection

All delete routes (DELETE /files, DELETE /files/folders) and the create-folder route (POST /files/folders) validate the supplied path before touching storage. The validation rules are shared with the existing read/upload/list endpoints:

  • The value must not be empty.
  • It must be a relative path (no leading /, no drive prefixes).
  • After normalization it must not resolve to ., .., start with ../, or contain a /../ segment.

Any violation returns 422 Unprocessable Entity with a FileValidationError body. The object_name / prefix is normalized (backslashes converted to /, trailing slashes preserved) before being forwarded to the StoragePort.

Query

Query the indexed knowledge base. The RAG engine is initialized for the given working_dir before executing the query.

curl -X POST http://localhost:8000/api/v1/query \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer ${JWT}" \
  -d '{
    "working_dir": "project-alpha",
    "query": "What are the main findings of the report?",
    "mode": "naive",
    "top_k": 10
  }'

Response (200 OK):

{
  "status": "success",
  "message": "",
  "data": {
    "entities": [],
    "relationships": [],
    "chunks": [
      {
        "reference_id": "...",
        "content": "...",
        "file_path": "...",
        "chunk_id": "..."
      }
    ],
    "references": []
  },
  "metadata": {
    "query_mode": "naive",
    "keywords": null,
    "processing_info": null
  }
}
Field Type Required Default Description
working_dir string yes -- RAG workspace directory for this project
query string yes -- The search query
mode string no "naive" Search mode: naive, local, global, hybrid, hybrid+, mix, bm25, bypass

BM25 query mode

Returns results ranked by PostgreSQL full-text search using pg_textsearch. Each chunk includes a score field with the BM25 relevance score.

curl -X POST http://localhost:8000/api/v1/query \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer ${JWT}" \
  -d '{
    "working_dir": "project-alpha",
    "query": "quarterly revenue growth",
    "mode": "bm25",
    "top_k": 10
  }'

Response (200 OK):

{
  "status": "success",
  "message": "",
  "data": {
    "entities": [],
    "relationships": [],
    "chunks": [
      {
        "chunk_id": "abc123",
        "content": "Quarterly revenue grew 12% year-over-year...",
        "file_path": "reports/financials-q4.pdf",
        "score": 3.456,
        "metadata": {}
      }
    ],
    "references": []
  },
  "metadata": {
    "query_mode": "bm25",
    "total_results": 10
  }
}

Hybrid+ query mode

Runs BM25 and vector search in parallel, then merges results using Reciprocal Rank Fusion (RRF). Each chunk includes bm25_rank, vector_rank, and combined_score fields.

curl -X POST http://localhost:8000/api/v1/query \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer ${JWT}" \
  -d '{
    "working_dir": "project-alpha",
    "query": "quarterly revenue growth",
    "mode": "hybrid+",
    "top_k": 10
  }'

Response (200 OK):

{
  "status": "success",
  "message": "",
  "data": {
    "entities": [],
    "relationships": [],
    "chunks": [
      {
        "chunk_id": "abc123",
        "content": "Quarterly revenue grew 12% year-over-year...",
        "file_path": "reports/financials-q4.pdf",
        "score": 0.0328,
        "bm25_rank": 1,
        "vector_rank": 3,
        "combined_score": 0.0328,
        "metadata": {}
      }
    ],
    "references": []
  },
  "metadata": {
    "query_mode": "hybrid+",
    "total_results": 10,
    "rrf_k": 60
  }
}

The combined_score is the sum of bm25_score and vector_score, each computed as 1 / (k + rank). Results are sorted by combined_score descending. A chunk that appears in both result sets will have a higher combined score than one that appears in only one.


Classical RAG Pipeline

A second retrieval pathway alongside the graph-based LightRAG. Classical RAG uses a straightforward chunk → embed → retrieve flow with two quality-enhancing techniques: multi-query generation and LLM-as-judge relevance scoring. It stores chunks in dedicated PGVector tables (one per working_dir) and does not build a knowledge graph.

How it works

  1. Indexing — A file is downloaded from MinIO, text is extracted via Kreuzberg (with chunking), and each chunk is embedded and stored in a PGVector table.
  2. Querying — The LLM generates N alternative phrasings of the user query (multi-query), similarity search runs for each variation, results are deduplicated by chunk_id, then an LLM judge scores each chunk's relevance on a 0–10 scale. Chunks below the relevance threshold are discarded; the rest are returned sorted by score.

Classical Indexing

Both classical indexing endpoints accept JSON bodies and run processing in the background.

Index a single file (classical)

Downloads the file from MinIO, extracts text with Kreuzberg chunking, and embeds the chunks into a PGVector table scoped to working_dir.

curl -X POST http://localhost:8000/api/v1/classical/file/index \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer ${JWT}" \
  -d '{
    "file_name": "project-alpha/report.pdf",
    "working_dir": "project-alpha",
    "chunk_size": 1000,
    "chunk_overlap": 200
  }'

Response (202 Accepted):

{"status": "accepted", "message": "File indexing started in background"}
Field Type Required Default Description
file_name string yes -- Object path in the MinIO bucket
working_dir string yes -- RAG workspace directory (project isolation)
chunk_size integer no 1000 Max characters per chunk (100–10000)
chunk_overlap integer no 200 Overlap characters between chunks (0–2000)

Index a folder (classical)

Lists all objects under the working_dir prefix in MinIO, downloads them, and indexes each file into the PGVector table.

curl -X POST http://localhost:8000/api/v1/classical/folder/index \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer ${JWT}" \
  -d '{
    "working_dir": "project-alpha",
    "recursive": true,
    "file_extensions": [".pdf", ".docx", ".txt"],
    "chunk_size": 1000,
    "chunk_overlap": 200
  }'

Response (202 Accepted):

{"status": "accepted", "message": "Folder indexing started in background"}
Field Type Required Default Description
working_dir string yes -- RAG workspace directory, also used as the MinIO prefix
recursive boolean no true Process subdirectories recursively
file_extensions list[string] no null (all files) Filter by extensions, e.g. [".pdf", ".docx", ".txt"]
chunk_size integer no 1000 Max characters per chunk (100–10000)
chunk_overlap integer no 200 Overlap characters between chunks (0–2000)

Classical Query

Query the classical RAG pipeline. Supports two modes: vector (default) and hybrid (BM25 + vector via Reciprocal Rank Fusion).

Vector mode (default)

The LLM generates query variations, runs vector similarity search for each, deduplicates results, then scores and filters them with an LLM judge.

curl -X POST http://localhost:8000/api/v1/classical/query \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer ${JWT}" \
  -d '{
    "working_dir": "project-alpha",
    "query": "What are the main findings of the report?",
    "top_k": 10,
    "num_variations": 3,
    "relevance_threshold": 5.0,
    "mode": "vector"
  }'

Hybrid mode

Runs BM25 full-text search and multi-query vector search in parallel, merges results using Reciprocal Rank Fusion (RRF), then scores with an LLM judge. Chunks include bm25_score, vector_score, and combined_score fields.

curl -X POST http://localhost:8000/api/v1/classical/query \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer ${JWT}" \
  -d '{
    "working_dir": "project-alpha",
    "query": "What are the main findings of the report?",
    "mode": "hybrid"
  }'

Response (200 OK):

{
  "status": "success",
  "message": "",
  "queries": [
    "What are the main findings of the report?",
    "What key results does the report present?",
    "Summarize the primary conclusions from the report"
  ],
  "chunks": [
    {
      "chunk_id": "a1b2c3d4-...",
      "content": "The primary finding indicates that...",
      "file_path": "project-alpha/report.pdf",
      "relevance_score": 8.5,
      "metadata": {"chunk_index": 0},
      "bm25_score": 0.0164,
      "vector_score": 0.0164,
      "combined_score": 0.0328
    }
  ],
  "mode": "hybrid"
}

If BM25 is unavailable (BM25_ENABLED=false or pg_textsearch extension missing), hybrid mode falls back to vector mode and logs a warning.

Field Type Required Default Description
working_dir string yes -- RAG workspace directory for this project
query string yes -- The search query
top_k integer no 10 Maximum chunks to retrieve per query variation (1–100)
num_variations integer no 3 Number of LLM-generated query variations (1–10)
relevance_threshold float no 5.0 Minimum LLM judge score (0–10) to include a chunk
mode string no "vector" Query mode: vector (vector-only) or hybrid (BM25+vector RRF)

LightRAG vs Classical RAG

Aspect LightRAG (graph-based) Classical RAG
Storage Apache AGE knowledge graph + pgvector PGVector tables only
Indexing Builds entity/relationship graph Chunk + embed only
Query modes naive, local, global, hybrid, hybrid+, mix, bm25, bypass vector (multi-query + LLM judge), hybrid (BM25+vector RRF)
Project isolation Shared graph per working_dir Separate PG table per working_dir
Best for Complex reasoning, relationship traversal Straightforward document Q&A, simpler setup

MCP Servers

The service exposes four MCP servers, all using streamable HTTP transport:

RAGAnythingQuery — /rag/mcp

Query-focused tools for searching the indexed knowledge base.

Tool: query_knowledge_base

Parameter Type Default Description
working_dir string required RAG workspace directory for this project
query string required The search query
mode string "hybrid" Search mode: naive, local, global, hybrid, hybrid+, mix, bm25, bypass
top_k integer 5 Number of chunks to retrieve

Tool: query_knowledge_base_multimodal

Parameter Type Default Description
working_dir string required RAG workspace directory for this project
query string required The search query
multimodal_content list required List of multimodal content items
mode string "hybrid" Search mode
top_k integer 5 Number of chunks to retrieve

RAGAnythingFiles — /files/mcp

File browsing tools for listing and reading files from MinIO storage.

Tool: list_files

Parameter Type Default Description
prefix string "" MinIO prefix to filter files by
recursive boolean true List files in subdirectories

Tool: read_file

Parameter Type Default Description
file_path string required File path in MinIO bucket (e.g. documents/report.pdf)

Downloads the file from MinIO, extracts its text content using Kreuzberg, and returns the extracted text along with metadata and any detected tables.

RAGAnythingBricks — /bricks/mcp

Bricks integration tools for accessing project documents from the Bricks platform and publishing structured section versions.

Tool: list_bricks_documents

Parameter Type Default Description
project_unique_id string required Bricks project unique identifier

Returns a list of documents for the specified Bricks project, including metadata like file name, MIME type, size, status, and presigned download URLs.

Tool: read_bricks_document

Parameter Type Default Description
file_url string required Presigned S3 URL from list_bricks_documents

Downloads the document from the presigned S3 URL, extracts its text content using Kreuzberg, and returns the extracted text, metadata, and detected tables. No authentication is required — the URL is already signed.

Tool: publish_section_version

Parameter Type Default Description
project_unique_id string required Bricks project unique identifier
section_key string required Section key to publish (e.g. "summary", "analysis")
content dict required Structured content for the section
workflow_id string "agent-haiku-files-v1" Workflow identifier
workflow_name string "haiku-files" Workflow display name
workflow_metadata dict null Additional workflow metadata

Publishes a structured section version back to the Bricks platform. When BRICKS_PUBLISH_DRY_RUN=true (default), the tool returns a preview of the payload without making an API call. Set BRICKS_PUBLISH_DRY_RUN=false to enable real publishing.

Dry-run response example:

{
  "success": true,
  "message": "DRY RUN — no API call made",
  "dry_run": true,
  "payload_preview": {
    "project_unique_id": "abc-123",
    "section_key": "summary",
    "content": {"title": "Analysis Summary"},
    "workflow_id": "agent-haiku-files-v1",
    "workflow_name": "haiku-files",
    "workflow_metadata": {}
  }
}

RAGAnythingClassical — /classical/mcp

Classical RAG tools for indexing and querying without a knowledge graph.

Tool: classical_index_file

Parameter Type Default Description
file_name string required Object path in the MinIO bucket
working_dir string required RAG workspace directory (project isolation)
chunk_size integer `10