Skip to main content
Glama

Guaipeca 🧠

Lightweight configurable RAG MCP server — FAISS + fastembed + markitdown

Why "Guaipeca"? — Guaipeca is southern Brazilian slang (Tupi-Guarani origin) for a scrappy mutt — a cusco, no pedigree, no frills, but loyal and gets the job done. Seemed fitting for a lightweight RAG server with no API keys, no GPU, and no cloud dependencies.

Guaipeca is a self-hosted retrieval-augmented generation (RAG) server that exposes semantic search over your document corpora via the Model Context Protocol (MCP). It uses a lightweight tech stack — FAISS indexes, fastembed (ONNX Runtime) embeddings, structure-aware chunking — and is fully configurable and decoupled from any specific memory system.

Features

  • Multi-corpus — define multiple document folders, each with its own weight and topic label

  • MCP-native — exposes search, get_chunk, get_document, list_documents, index, status, list_folders, and upload tools via MCP

  • Dual transport — stdio (for local LLM integration) + HTTP (for remote/network access)

  • Hybrid search — optional BM25 sparse keyword search fused with FAISS dense vectors via Reciprocal Rank Fusion (RRF)

  • Any document format — pymupdf (PDFs with font-based heading detection) + markitdown (DOCX, PPTX, XLSX, HTML → markdown)

  • Structure-aware chunking — splits on headings, treats tables as atomic units, zero LLM calls

  • Local embeddings — fastembed (ONNX Runtime) on CPU, no GPU or API keys needed

  • Incremental indexing — SHA256 file hashing, only re-indexes changed files

  • FAISS backup + recovery — automatic .fai.bak backup before writes, fallback chain on load (primary → backup → fresh)

  • Embedding cache (LRU + TTL) — 10K-entry LRU cache with 1hr TTL, memory-pressure aware eviction via psutil

  • Content preprocessing — heading + first 500 chars sent to embedder for better semantic signal; full text stored in metadata

  • Concurrent — read-write lock allows multiple simultaneous searches, exclusive indexing

  • Runs on Pi 5 — ARM64, CPU-only, ~90MB model + ~20MB FAISS

  • Optional auth — Bearer token authentication for HTTP transport

Related MCP server: ragi

Quick Start

# Clone and install
git clone https://github.com/LuisEduardoAvila/guaipeca.git
cd guaipeca
pip install -e ".[all]"
# pymupdf is included as a core dependency for PDF heading detection

# Create config
cp config/guaipeca.yaml ~/.guaipeca/guaipeca.yaml
# Edit config to point to your document folders

# Check/download embedding model
guaipeca --check-model

# Index your corpora
guaipeca index

# Search from CLI
guaipeca search "HFM data audit"

# Start MCP server (stdio + HTTP on port 8090)
guaipeca serve

Configuration

Create ~/.guaipeca/guaipeca.yaml:

corpora:
  - name: epm-docs
    path: /path/to/your/docs
    topic: "EPM & Oracle HFM/ARCS"
    weight: 1.5
    extensions: [.md, .txt, .pdf, .docx]

  - name: finance-reference
    path: /path/to/your/finance-docs
    topic: "Finance & Accounting Standards"
    weight: 1.3
    extensions: [.md, .txt, .pdf]

embedding:
  model: all-MiniLM-L6-v2
  dimensions: 384
  preprocess: true              # heading + first 500 chars for better embeddings
  cache_ttl_seconds: 3600      # embedding cache entry TTL (1 hour)
  cache_max_entries: 10000     # max cached embeddings (LRU eviction)

chunking:
  max_size: 2000
  overlap: 200
  table_aware: true
  split_on_headings: true

server:
  transport: both  # stdio | http | both
  port: 8090
  host: 127.0.0.1  # Use 0.0.0.0 for network access
  # auth_token: "your-secret-token"  # Optional: require Bearer token auth

upload:
  allow: ["epm-docs"]  # corpora that accept file uploads
  max_file_size: 52428800  # 50MB
  allowed_extensions: [.md, .txt, .pdf, .docx]
  auto_index: true  # index after upload

# Search configuration (optional — all values have safe defaults)
search:
  hybrid: false              # Enable BM25 + FAISS hybrid search (default: false)
  bm25_weight: 1.0           # BM25 contribution weight in RRF fusion
  dense_weight: 1.0          # Dense (FAISS) contribution weight in RRF fusion
  rrf_k: 60                  # RRF constant (higher = smoother ranking)
  max_chars: 8000            # Threshold for auto return_mode (total result chars)

Embedding Models

Guaipeca uses FastEmbed (ONNX Runtime) for local embeddings — no torch, no GPU, no API keys. Models are downloaded automatically on first use and cached locally.

Content preprocessing is enabled by default (embedding.preprocess: true): before embedding, each chunk is preprocessed to its heading + first 500 characters, giving the model better semantic signal. The full text is still stored in metadata for retrieval. Disable with preprocess: false if you want raw chunk text sent to the embedder.

Choosing a Model

Pick based on your use case and hardware:

Speed-optimized (384-dim, <100MB) — Pi 5, resource-constrained

Model

Dimensions

Size

Best for

all-MiniLM-L6-v2

384

90MB

General use, fast (default)

BAAI/bge-small-en-v1.5

384

67MB

Better quality, same speed

snowflake/arctic-embed-xs

384

90MB

General use, compact

Balanced (512–768 dim, 120–520MB) — desktop, small VM

Model

Dimensions

Size

Best for

jinaai/jina-embeddings-v2-small-en

512

120MB

Long documents (8192 tokens)

BAAI/bge-base-en-v1.5

768

210MB

Higher quality, moderate speed

nomic-ai/nomic-embed-text-v1.5-Q

768

130MB

Quantized, good balance

snowflake/arctic-embed-s

384

130MB

Good quality, compact

Quality-optimized (768–1024 dim, 420MB+) — server, dedicated hardware

Model

Dimensions

Size

Best for

BAAI/bge-large-en-v1.5

1024

1.2GB

Best quality, slowest

mixedbread-ai/mxbai-embed-large-v1

1024

640MB

High quality

jinaai/jina-embeddings-v2-base-en

768

520MB

Long context (8192 tokens)

Multilingual

Model

Dimensions

Size

Best for

paraphrase-multilingual-MiniLM-L12-v2

384

220MB

~50 languages, fast

paraphrase-multilingual-mpnet-base-v2

768

1.0GB

~50 languages, better quality

intfloat/multilingual-e5-large

1024

2.2GB

~100 languages, best quality

Code

Model

Dimensions

Size

Best for

jinaai/jina-embeddings-v2-base-code

768

640MB

Code + docs, 30+ programming languages

Switching Models

  1. Update embedding.model and embedding.dimensions in your config

  2. Reindex with --force (FAISS vectors must match the new dimensions):

guaipeca index --force

āš ļø Changing dimensions (e.g. 384 → 768) requires a full reindex. The old index is incompatible with the new model's vectors.

Recommendations

Use case

Recommended model

Why

Pi 5 / ARM64 SBC

all-MiniLM-L6-v2

Fast, 90MB, good enough quality

Small VM (2GB RAM)

BAAI/bge-small-en-v1.5

Better quality, still 384-dim

Dedicated server

BAAI/bge-base-en-v1.5

768-dim, noticeable quality gain

Long documents

jinaai/jina-embeddings-v2-base-en

8192 token context

Multilingual

paraphrase-multilingual-MiniLM-L12-v2

50+ languages, 384-dim

Code search

jinaai/jina-embeddings-v2-base-code

Code + docs, 30+ languages

MCP Tools

Search across indexed corpora. Returns summary + location + score.

search(query="HFM data audit", top_k=5, corpora=["epm-docs"], hybrid=true, return_mode="chunks")

return_mode controls result granularity:

  • chunks (default) — returns individual chunks with summary, location, and score

  • documents — returns full source documents, deduplicated by path, with best chunk score as document score

  • auto — returns chunks if total result size < search.max_chars (default 8000), otherwise collapses to documents

get_chunk

Retrieve full chunk text by chunk_id from search results.

get_chunk(chunk_id="epm-docs:a1b2c3d4e5f67890")

get_document

Retrieve the full converted text (markdown) of a document by corpus and source_path. Returns text content plus metadata (filename, file size, chunk count).

get_document(corpus="epm-docs", source_path="/path/to/docs/report.pdf")

list_documents

List all indexed documents in a corpus (or all corpora if no corpus specified). Returns source_path, filename, chunk count, file size, and last_indexed timestamp.

list_documents(corpus="epm-docs")
list_documents()  # all corpora

index

Trigger indexing for a corpus or all corpora.

index(corpus="all", force=false)

status

Get corpus statistics: chunk counts, indexed files.

status()

list_folders

List corpora that accept file uploads via the upload tool. Returns corpus name, path, topic, and allowed extensions.

list_folders()

upload

Upload a file to a corpus for conversion and indexing. File content must be base64-encoded. Only corpora listed in upload.allow can receive files.

upload(corpus="epm-docs", filename="report.pdf", content="<base64>", index=true)

CLI

guaipeca --check-model                 # Check/download embedding model
guaipeca index [corpus] [--force]      # Index corpora
guaipeca search "query" [--top-k N]    # Search
guaipeca search "query" --hybrid       # Hybrid search (BM25 + FAISS)
guaipeca status                        # Show stats
guaipeca serve [--transport both] [--port 8090]  # Start MCP server

Running as a systemd Service

For always-on deployment on Linux (e.g. Pi 5), install Guaipeca as a systemd service.

1. Create the service file

# Copy the service file (or create manually — see below)
sudo cp guaipeca.service /etc/systemd/system/guaipeca.service

Or create it manually at /etc/systemd/system/guaipeca.service:

[Unit]
Description=Guaipeca RAG MCP Server
After=network.target

[Service]
Type=simple
User=<your-user>
Group=<your-group>
WorkingDirectory=/path/to/guaipeca
ExecStart=/path/to/guaipeca --config /home/<user>/.guaipeca/guaipeca.yaml serve --transport both --port 8090
Restart=on-failure
RestartSec=10
Environment=PYTHONUNBUFFERED=1
Environment=HOME=/home/<user>

# Optional resource limits (prevent starving other services)
MemoryMax=1G
CPUQuota=200%

[Install]
WantedBy=multi-user.target

2. Enable and start

sudo systemctl daemon-reload
sudo systemctl enable guaipeca   # start on boot
sudo systemctl start guaipeca    # start now

3. Verify

systemctl status guaipeca
curl http://127.0.0.1:8090/health
# → {"status": "ok", "server": "guaipeca", "version": "0.1.0"}

4. Check logs

journalctl -u guaipeca -f          # follow logs
journalctl -u guaipeca --since "1 hour ago"

Management

sudo systemctl stop guaipeca       # stop
sudo systemctl restart guaipeca    # restart
sudo systemctl disable guaipeca    # stop starting on boot

Notes

  • The service runs guaipeca serve with --transport both (stdio + HTTP/SSE)

  • Port 8090 is bound to 127.0.0.1 by default (config server.host). Use 0.0.0.0 for network access

  • Resource limits (MemoryMax, CPUQuota) prevent Guaipeca from starving other services on resource-constrained hosts like Pi 5

  • The embedding model loads at startup (~1s with fastembed/ONNX). First search/index may take slightly longer if the model needs to download

  • Config changes require a restart: sudo systemctl restart guaipeca

  • Re-index after adding files: guaipeca --config ~/.guaipeca/guaipeca.yaml index

Architecture

guaipeca.yaml (config)
       │
  Config Loader
       │
  ā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”¬ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”
  │            │            │
Indexer     Searcher     MCP Server
  │            │            │
  │            │            ā”œā”€ā”€ stdio transport
  │            │            └── HTTP/SSE transport (port 8090)
  │            │
  │            └── FAISS per-corpus + weighted merge
  │            
  └── pymupdf (PDF) ─┐
  └── markitdown (other) ─┤
          ↓                 │
       chunk → embed → FAISS
       (read-write lock: concurrent readers, exclusive writer)

Data Layout

~/.guaipeca/
ā”œā”€ā”€ guaipeca.yaml          # Config
ā”œā”€ā”€ data/
│   ā”œā”€ā”€ corpora/
│   │   ā”œā”€ā”€ epm-docs/
│   │   │   ā”œā”€ā”€ faiss_index.fai
│   │   │   ā”œā”€ā”€ faiss_index.fai.bak  # backup (auto-generated)
│   │   │   ā”œā”€ā”€ metadata.json
│   │   │   ā”œā”€ā”€ file_hashes.json
│   │   │   └── bm25_index/       # BM25 sparse keyword index
│   │   └── finance-reference/
│   │       ā”œā”€ā”€ faiss_index.fai
│   │       ā”œā”€ā”€ faiss_index.fai.bak  # backup (auto-generated)
│   │       ā”œā”€ā”€ metadata.json
│   │       ā”œā”€ā”€ file_hashes.json
│   │       └── bm25_index/       # BM25 sparse keyword index
│   └── converted/          # conversion cache (pymupdf + markitdown)
└── models/
    └── all-MiniLM-L6-v2/   # cached embedding model

Dependencies

Package

Purpose

Size

fastembed

Local embeddings (ONNX Runtime)

~46MB (onnxruntime) + ~90MB (model)

faiss-cpu

Vector index

~20MB

pymupdf

PDF heading detection

~25MB

markitdown

Document conversion (DOCX, PPTX, XLSX, HTML)

~30MB

bm25s

BM25 sparse keyword search

<1MB

pyyaml

Config parsing

<1MB

numpy

Array operations

~15MB

ARM64 / Pi 5 System Dependencies

markitdown relies on libraries that require system-level packages on ARM64 (Pi 5). Install these before pip install:

# Debian/Ubuntu/Raspberry Pi OS
sudo apt install libxml2-dev libxslt-dev libffi-dev libjpeg-dev

# If using PDF conversion:
sudo apt install poppler-utils

# If using DOCX conversion:
sudo apt install antiword

If you encounter build errors with lxml or charset-normalizer on ARM64, ensure you have build-essential and python3-dev installed:

sudo apt install build-essential python3-dev

Container Deployment (Docker)

Guaipeca can run in a Docker container for VM deployment (ARM64 or x86_64). The Pi 5 stays bare-metal; containers are for VMs.

Quick Start

# 1. Prepare data directory
mkdir -p data/{config,corpora,index,models}
cp config/guaipeca.container.yaml data/config/guaipeca.yaml
# Edit data/config/guaipeca.yaml as needed
# Copy your documents to data/corpora/

# 2. Start the container
docker compose up -d

# 3. Check health
curl http://localhost:8090/health

# 4. Index corpora (first time)
docker compose exec guaipeca guaipeca --config /data/config/guaipeca.yaml index

Building from Source

docker build -t guaipeca .
docker run -d --name guaipeca \
  -p 8090:8090 \
  -v ./data:/data \
  --restart unless-stopped \
  guaipeca

Note: Pre-built images will be available on ghcr.io after the first push to main triggers the CI/CD pipeline. Until then, build from source using the command above.

Container Data Layout

data/              → /data (mounted volume)
ā”œā”€ā”€ config/        → /data/config     (guaipeca.yaml)
ā”œā”€ā”€ corpora/       → /data/corpora    (source documents)
ā”œā”€ā”€ index/         → /data/index      (FAISS + BM25 + metadata)
└── models/        → /data/models     (embedding model cache)

The embedding model (~90MB) downloads to the volume on first run. Subsequent starts load from cache — no network needed.

Automated Rebuilds

Push to main triggers a GitHub Actions workflow that:

  • Builds multi-arch images (arm64 + amd64)

  • Tags with latest and commit SHA

  • Pushes to ghcr.io/luis-eduardo-avila/guaipeca

See docs/container-deployment.md for detailed VM deployment instructions.

Security

  • Default binding: The HTTP server binds to 127.0.0.1 (localhost) by default. Change host: 0.0.0.0 in config only if you need network access.

  • Authentication: Set auth_token in the server config to require Authorization: Bearer <token> headers on HTTP requests. When auth is enabled, CORS is restricted (no wildcard origin).

  • HTTP endpoints:

    • GET /sse — SSE stream for MCP transport

    • POST /messages — JSON-RPC messages

    • GET /health — health check

    • GET /tools — list available tools

    • GET /download/{corpus}/{filename} — download original file (requires auth, path traversal protected)

  • Error messages: Client error responses are sanitized to avoid leaking internal file paths or library details.

License

MIT

Related MCP Connectors

Related MCP Servers

  • A
    license
    B
    quality
    D
    maintenance
    A complete MCP server for Retrieval-Augmented Generation with file management and vector memory for agents. Supports multiple document formats (PDF, DOCX, TXT, MD, CSV, JSON) with semantic search using Hugging Face embeddings and ChromaDB for efficient vector storage.
    11
    6 npm
    1
    MIT
  • A
    license
    A
    quality
    D
    maintenance
    Local-first RAG indexing and semantic search MCP server. Enables document retrieval and context-aware queries using local embedding models.
    3
    6 npm
    MIT
  • A
    license
    Not graded
    quality
    A
    maintenance
    MCP server for local RAG over personal notes, PDFs, and documents, enabling plain-English querying and hybrid search with multi-hop context expansion.
    MIT