Guaipeca
Click on "Deploy Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@Guaipecasearch my epm-docs corpus for HFM data audit findings"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
Guaipeca š§
Self-hosted RAG MCP server that runs where others can't
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.
Why Guaipeca?
Most RAG solutions assume you have a GPU, a cloud budget, or at least a beefy server. Guaipeca doesn't.
No API keys ā runs entirely locally, no OpenAI/Anthropic/Google calls
No GPU ā ONNX Runtime on CPU, works on ARM64, x86_64, anything Linux
No cloud ā your documents never leave your machine
No torch ā fastembed (~46MB) instead of sentence-transformers (~959MB)
Under 500MB total ā embedding model + FAISS index + dependencies
Deploy in minutes ā
pip install, one YAML config,guaipeca serve
Proven on a Raspberry Pi 5 (8GB RAM, ARM64) alongside other services. If it runs there, it runs anywhere.
Related MCP server: ragi
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,upload,delete, andget_toctools via MCPMulti-transport ā stdio (for local LLM integration) + HTTP: Streamable HTTP (
POST /mcp) and legacy SSE (GET /sse+POST /messages) for remote/network accessHybrid 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 + line-wrap stitching) + 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.bakbackup 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 anywhere ā ARM64 (Pi 5, ARM servers), x86_64 (laptops, VMs, dedicated servers), any Linux box with Python 3.12+. Proven on Pi 5 with ~90MB model + ~20MB FAISS
Optional auth ā Bearer token authentication for HTTP transport
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 serveConfiguration
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:
Ultra-light (384-dim, <100MB) ā edge devices, SBCs, 2GB RAM
Model | Dimensions | Size | Best for |
| 384 | 90MB | General use, fast (default) |
| 384 | 67MB | Better quality, same speed |
| 384 | 90MB | General use, compact |
Balanced (512ā768 dim, 120ā520MB) ā desktops, small VMs, laptops
Model | Dimensions | Size | Best for |
| 512 | 120MB | Long documents (8192 tokens) |
| 768 | 210MB | Higher quality, moderate speed |
| 768 | 130MB | Quantized, good balance |
| 384 | 130MB | Good quality, compact |
Quality-optimized (768ā1024 dim, 420MB+) ā dedicated servers, workstations
Model | Dimensions | Size | Best for |
| 1024 | 1.2GB | Best quality, slowest |
| 1024 | 640MB | High quality |
| 768 | 520MB | Long context (8192 tokens) |
Multilingual
Model | Dimensions | Size | Best for |
| 384 | 220MB | ~50 languages, fast |
| 768 | 1.0GB | ~50 languages, better quality |
| 1024 | 2.2GB | ~100 languages, best quality |
Code
Model | Dimensions | Size | Best for |
| 768 | 640MB | Code + docs, 30+ programming languages |
Switching Models
Update
embedding.modelandembedding.dimensionsin your configReindex 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 |
Edge / SBC (Pi 5, ARM64, 2GB RAM) |
| Fast, 90MB, good enough quality |
Small VM / laptop (4GB RAM) |
| Better quality, still 384-dim |
Dedicated server / workstation |
| 768-dim, noticeable quality gain |
Long documents |
| 8192 token context |
Multilingual |
| 50+ languages, 384-dim |
Code search |
| Code + docs, 30+ languages |
MCP Tools
search
Search across indexed corpora. Returns summary + location + score.
search(query="HFM data audit", top_k=5, corpora=["epm-docs"], hybrid=true, return_mode="chunks")
search(query="consolidation", section_mode=true)
search(query="revenue", section_filter="Financial Statements")section_mode groups results by document section, returning full section text instead of individual chunks. section_filter restricts search to a specific section (by section ID or heading path prefix).
return_mode controls result granularity:
chunks(default) ā returns individual chunks with summary, location, and scoredocumentsā returns full source documents, deduplicated by path, with best chunk score as document scoreautoā returns chunks if total result size <search.max_chars(default 8000), otherwise collapses to documents
Note:
dedup_sectionsis not an MCP tool argument. It is an option on the internalSearcher.search()method (dedup_sections=true), available when using Guaipeca as a Python library ā it collapses results sharing asection_id, keeping only the best-scoring chunk per section. It is not exposed over MCP, and thesearchtool does not accept it.
Note on table formatting: Raw chunk output (
return_mode="chunks") may show markdown tables with rows run-together (no blank lines between rows). This is expected ā the chunker treats tables as atomic units and preserves row adjacency. For properly formatted tables, useget_documentorget_chunkto retrieve the full text with original formatting.
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 corporaindex
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)get_toc
Get the table of contents for a corpus or a specific document. Returns a nested tree of headings with chunk counts, or a list of all documents with their top-level headings. Non-structured documents return a structured: false flag.
get_toc(corpus="epm-docs")
get_toc(corpus="epm-docs", document="/path/to/report.md")delete
Delete a file from a corpus that accepts uploads. Only corpora listed in upload.allow can have files deleted. The corpus is re-indexed after deletion to remove stale chunks.
delete(corpus="epm-docs", filename="report.pdf", index=true)ToC-Aware Search Features
Guaipeca supports three STAIR-inspired features that leverage document structure:
Section mode (
section_mode=true): Groups search results by their document section, concatenating all chunks in each section. Useful for retrieving complete sections rather than fragments.Section filter (
section_filter="heading text"): Restricts search to chunks within a specific section. Useful for searching within a chapter or section.ToC re-ranking (
search.toc_rerank: truein config): Boosts results whose heading path contains query terms. Non-structured documents are not re-ranked.
Non-structured documents (no heading markers) degrade gracefully: heading_path is empty, section_id uses "unstructured:{hash}", get_toc returns structured: false, section_filter returns all chunks from that document, and toc_rerank skips re-ranking.
heading_path semantics (v0.3.4+): heading_path contains only the document title (#, level 1) and the enclosing section heading (##, level 2). Deep headings (###, ####, etc.) are part of the section content but do NOT appear in heading_path. This ensures section_id is always derived from the ## section heading, so chunks within the same section share the same section_id, and chunks from different sections never collide.
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 serverRunning as a systemd Service
For always-on deployment on any Linux system (Pi 5, VM, laptop, server), 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.serviceOr 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.target2. Enable and start
sudo systemctl daemon-reload
sudo systemctl enable guaipeca # start on boot
sudo systemctl start guaipeca # start now3. Verify
systemctl status guaipeca
curl http://127.0.0.1:8090/health
# ā {"status": "ok", "server": "guaipeca", "version": "0.3.3"}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 bootNotes
The service runs
guaipeca servewith--transport both(stdio + HTTP/SSE)Port 8090 is bound to
127.0.0.1by default (configserver.host). Use0.0.0.0for network accessResource limits (
MemoryMax,CPUQuota) prevent Guaipeca from starving other services on shared or low-resource hostsThe 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 guaipecaRe-index after adding files:
guaipeca --config ~/.guaipeca/guaipeca.yaml index
Architecture
guaipeca.yaml (config)
ā
Config Loader
ā
āāāāāāāāāāāāāā¬āāāāāāāāāāāāā
ā ā ā
Indexer Searcher MCP Server
ā ā ā
ā ā āāā stdio transport
ā ā āāā HTTP: Streamable (/mcp) + SSE (/sse, /messages) (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 modelDependencies
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 / Low-Resource System Dependencies
markitdown relies on libraries that require system-level packages on ARM64 and some minimal Linux installations.
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 antiwordIf 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-devContainer Deployment (Docker)
Guaipeca ships with a Docker container for easy deployment on any platform (ARM64 or x86_64). Run it on a VM, a cloud instance, or alongside other containers ā the image is multi-arch and ~643MB before model download.
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 indexBuilding from Source
docker build -t guaipeca .
docker run -d --name guaipeca \
-p 8090:8090 \
-v ./data:/data \
--restart unless-stopped \
guaipecaNote: Pre-built images will be available on ghcr.io after the first push to
maintriggers 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
latestand commit SHAPushes 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. Changehost: 0.0.0.0in config only if you need network access.Authentication: Set
auth_tokenin the server config to requireAuthorization: Bearer <token>headers on HTTP requests. When auth is enabled, CORS is restricted (no wildcard origin).HTTP endpoints:
POST /mcpā Streamable HTTP transport (MCP protocol version2025-06-18); JSON-RPC request in, JSON-RPC response inline.initializeissues anMcp-Session-Idheader used on subsequent requests. Modern clients should prefer this endpoint.DELETE /mcpā terminate a Streamable HTTP session (send theMcp-Session-Idheader)GET /mcpā returns405 Method Not Allowed(Guaipeca has no server-initiated messages; usePOST /mcp)GET /sseā SSE stream for MCP transport (legacy, still supported)POST /messagesā JSON-RPC messages (legacy, still supported)GET /healthā health checkGET /toolsā list available toolsGET /download/{corpus}/{filename}ā download original file (requires auth, path traversal protected)DELETE /documents?corpus=<name>&filename=<name>ā delete a file from an upload-enabled corpus (requires auth)
Error messages: Client error responses are sanitized to avoid leaking internal file paths or library details.
License
MIT
This server cannot be deployed
Maintenance
Related MCP Connectors
Cloud or self-hosted knowledge for AI agents: hybrid search, reranking, GraphRAG, scoped MCP tools.
Personal knowledge base MCP server with semantic search, auto-categorization, metadata extraction
DocBase MCP server for AI agents
Related MCP Servers
- AlicenseBqualityDmaintenanceA 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.119 npm1MIT
- AlicenseAqualityDmaintenanceLocal-first RAG indexing and semantic search MCP server. Enables document retrieval and context-aware queries using local embedding models.35 npmMIT
- FlicenseAqualityDmaintenanceMCP server that enables semantic search over local PDF collections using local RAG, with automatic indexing of new documents.5-
- AlicenseNot gradedqualityAmaintenanceMCP server for local RAG over personal notes, PDFs, and documents, enabling plain-English querying and hybrid search with multi-hop context expansion.MIT