Back to Browse

Paparats MCP Server

Developer ToolsLow Risk9.9MCP RegistryLocal
Free

Server data from the Official MCP Registry

Self-hosted semantic code search MCP for Claude Code & Cursor. Multi-repo, AST-aware, stays local.

About

Self-hosted semantic code search MCP for Claude Code & Cursor. Multi-repo, AST-aware, stays local.

Security Report

9.9
Low Risk9.9Low Risk

Valid MCP server (2 strong, 2 medium validity signals). No known CVEs in dependencies. ⚠️ Package registry links to a different repository than scanned source. Imported from the Official MCP Registry. Trust signals: 3 highly-trusted packages. 1 finding(s) downgraded by scanner intelligence.

12 files analyzed · 1 issue found

Security scores are indicators to help you make informed decisions, not guarantees. Always review permissions before connecting any MCP server.

Permissions Required

This plugin requests these system permissions. Most are normal for its category.

file_system

Check that this permission is expected for this type of plugin.

env_vars

Check that this permission is expected for this type of plugin.

database

Check that this permission is expected for this type of plugin.

How to Install

Add this to your MCP configuration file:

{
  "mcpServers": {
    "io-github-ibazylchuk-paparats-mcp": {
      "args": [
        "-y",
        "@paparats/cli"
      ],
      "command": "npx"
    }
  }
}

Documentation

View on GitHub

From the project's GitHub README.

Paparats MCP: self-hosted semantic code search for AI coding agents

npm version License: MIT PulseMCP MCP Badge

Paparats: Local Semantic Code Search MCP server – quality and maintenance score on Glama

Open in GitHub Codespaces  ← try the full stack in your own Codespace, no local install (details)

Paparats MCP is an open-source, self-hosted MCP server that gives Claude Code, Cursor, Codex, GitHub Copilot and any other MCP client semantic code search across many repositories, without your code leaving your machine. It indexes each repo with tree-sitter AST chunking, builds a cross-chunk symbol graph (find_usages: calls, callers, references), attaches git history and ticket references to every chunk, and stores vectors in Qdrant using local llama.cpp embeddings (bge-code-v1 for code, Qwen3-Embedding-0.6B for docs). Install with npm install -g @paparats/cli && paparats install. It runs on your own machine or server in Docker; there is no hosted SaaS.

Use it when grep-and-read loops burn your agent's context on a large or multi-repo codebase, or when several people and agents should share one index. Skip it when you have one small repo, or when you need precise refactors — that is a job for an LSP-based tool such as Serena, which pairs well with Paparats.

Paparats-kvetka — a magical flower from Slavic folklore that blooms on Kupala Night and grants whoever finds it the power to see hidden things. Likewise, Paparats MCP helps your agent see the right code across a sea of repositories.

📊 The built-in /ui operator console — ROI, query quality, cross-project usage, per-user activity, indexer health. Screenshot uses synthetic data (?demo=1) — no real queries, users, or project names.

  • ⚡ One install, one config. paparats install → paparats add ~/code/repo → done.
  • 🌳 AST-aware chunking and symbol extraction. Tree-sitter parses every supported file once and feeds both chunking and the cross-chunk symbol graph (calls / called_by / references / referenced_by) — 11 languages: TypeScript (incl. TSX), JavaScript, Python, Go, Rust, Java, Ruby, C, C++, C# and Terraform/HCL.
  • 🧠 Architectural memory that the agent maintains itself. A second vector store per group holds components, decisions (ADRs) and lessons learned — your coding agent writes them as it works and reads them before cross-component changes. Bootstrap on day one with the init_arch_memory MCP prompt (the /init of architectural memory). Server-side similarity gate prevents duplicates, supersedes links replace stale decisions, a min_score threshold gates low-confidence reads, every card carries an "updated N ago" stamp, and Prometheus metrics tell you whether your memory is actually being used.
  • 🔄 Keeps itself fresh. The indexer checks every repo for changes every 10 minutes (a git ls-remote for remote repos, a file mtime/size fingerprint for local ones), reindexes only what changed, and runs a full safety-net pass every few hours. A POST /trigger reindexes on demand, e.g. from CI.
  • 💸 Saves tokens. Returns only the chunks that matter, with token-savings telemetry to estimate it (per-query, per-user, per-anchor-project).
  • 🔭 Production-ready observability. Prometheus /metrics, OpenTelemetry traces (Tempo, Jaeger, Honeycomb, Datadog, Grafana Cloud, Elastic APM), local SQLite analytics, and a built-in /ui operator console that visualises ROI, query quality, cross-project usage and indexer health in one screen.
  • 🏠 100% local by default. Qdrant + a local embed server (llama.cpp llama-server + llama-swap) on your machine. No cloud, no API keys, no telemetry leaving the box. Bring your own Qdrant Cloud / embed server URL, or a cloud embedding provider, if you want.

📏 Benchmarked, not assumed: bge-code-v1 reaches Recall@5 0.480 vs 0.408 for jina-code-embeddings-1.5b on CoIR CosQA (20,604 code docs, 500 queries) — see the methodology.


Table of Contents


Why Paparats?

AI coding assistants are smart, but they can only see files you open. They don't know your codebase structure, where the authentication logic lives, or how services connect. Paparats fixes that.

What you get

  • Semantic code search — ask "where is the rate limiting logic?" and get exact code ranked by meaning, not grep matches
  • Automatic freshness — the indexer notices changed repos within about 10 minutes and reindexes only the changed files; trigger a reindex over HTTP when you need it sooner
  • Cross-chunk symbol graph — find_usages walks AST-derived edges (calls, called_by, references, referenced_by) so the agent can trace dependencies without re-grepping
  • Token savings — return only relevant chunks instead of full files to reduce context size
  • Multi-project workspaces — search across backend, frontend, infra repos in one query
  • 100% local & private — Qdrant vector database + local llama-server embeddings. Nothing leaves your laptop
  • AST-aware chunking — code split by AST nodes (functions/classes) via tree-sitter, not arbitrary character counts (TypeScript/TSX, JavaScript, Python, Go, Rust, Java, Ruby, C, C++, C#, Terraform/HCL; regex fallback for other file types)
  • Rich metadata — each chunk knows its symbol name (from tree-sitter AST), service, domain context, and tags from directory structure
  • Git history per chunk — see who last modified a chunk, when, and which tickets (Jira, GitHub) are linked to it
  • Architectural memory — a living knowledge base of components, decisions (ADRs) and lessons learned, written by the coding agent as it learns, deduplicated server-side by vector similarity, and consulted before cross-component changes so the agent stays consistent across sessions
  • Docs and glossary — optional hybrid search over your markdown documentation, and a shared glossary of team vocabulary that agents can read and extend

Who benefits

Use CaseHow Paparats Helps
Solo developersQuickly navigate unfamiliar codebases, find examples of patterns, reduce context-switching
Multi-repo teamsCross-project search (backend + frontend + infra), consistent patterns, faster onboarding
AI agentsFoundation for product support bots, QA automation, dev assistants — any agent that needs code context
Legacy modernizationFind all usages of deprecated APIs, identify migration patterns, discover hidden dependencies
Contractors/consultantsAccelerate ramp-up on client codebases, reduce "where is X?" questions

Quick start: install semantic code search locally

Try it in the browser (no install)

Open in GitHub Codespaces

Spin up a full Qdrant + embed server + paparats stack in your own Codespace (this is not a hosted Paparats service — it runs in your GitHub account). A small slice of the repo (packages/shared/src) is auto-indexed on first start so you can run

paparats search -g demo 'gitignore filter'

within a few minutes. Codespace forwards port 9876 for MCP — point Cursor/Claude Code at it via the URL VS Code shows in the Ports panel.

Note: Codespaces is for demo only. With CPU embedding the full repo would take 15+ minutes and can hit batch timeouts on large files. For real workloads run locally — or set OPENAI_API_KEY (or VOYAGE_API_KEY) as a Codespaces user secret and indexing drops to a couple of seconds; see Embedding Model Setup.

Run locally

You need Node.js 24+, Docker and Docker Compose v2. On macOS, also install the embed server natively — running it inside Docker on macOS is significantly slower because the Docker VM cannot use Apple Silicon GPU (Metal) acceleration.

# 1. Install the CLI.
npm install -g @paparats/cli

# 2. macOS only — install the native embed server (Linux uses the Docker embed
#    image by default). Metal-accelerated.
brew install llama.cpp mostlygeek/llama-swap/llama-swap

# 3. One-time bootstrap. Generates ~/.paparats/{docker-compose.yml,projects.yml},
#    starts the stack, downloads the embedding models (~2 GB), wires Cursor MCP.
paparats install

# 4. Add the projects you want indexed. Local paths bind-mount read-only into the
#    indexer; GitHub repos (URL or owner/repo shorthand) get cloned.
paparats add ~/code/my-project
paparats add git@github.com:acme/billing.git
paparats add acme/widgets

# 5. Watch it work.
paparats list

That's it. When Cursor is installed (~/.cursor exists), it is already wired (~/.cursor/mcp.json) to http://localhost:9876/mcp; for Claude Code run claude mcp add --scope user --transport http paparats http://localhost:9876/mcp (see connecting MCP clients). Open Cursor or Claude Code and ask:

"Search this workspace for the auth middleware and show me everything that calls it."

Existing v1 user?

Just run paparats install again. The installer detects the legacy per-project compose, asks once before swapping it for the new global setup, and preserves your indexed data (Qdrant collections, SQLite metadata, embedding cache). Your in-repo .paparats.yml files keep working as the base config for each project.


How the install works

paparats install is the only setup command. It creates a single global home at ~/.paparats/, brings up a Docker stack, and wires your MCP clients. Re-run it any time to reconfigure — it diffs the existing compose and asks before overwriting hand edits.

~/.paparats/
├── docker-compose.yml          generated; hand-editable; install asks before overwriting
├── projects.yml                project list (CLI rewrites it; see the note in the file about comments)
├── install.json                install flags persisted so add/remove can regenerate compose; chmod 600
├── .env                        secrets and overrides — Qdrant API key, GitHub token; chmod 600
├── models/                     bge-code-v1 + qwen3-embedding-0.6b GGUF (native embed mode)
└── llama-swap.yaml, llama-swap.log   native embed server config and log (native embed mode)

Indexed data does not live in this directory. It is kept in named Docker volumes created by the compose file: qdrant_data (vector index), paparats_data (metadata, embedding cache and analytics SQLite files) and indexer_repos (cloned remote projects). The whole ~/.paparats directory is bind-mounted read-only into the indexer at /config, which is how it sees projects.yml.

Inside the Docker stack:

ServiceImageHost portRole
paparatsibaz/paparats-server:latest9876MCP HTTP endpoints, search, metadata API
paparats-indexeribaz/paparats-indexer:latest9877Scheduled + on-demand indexing, hot-reload of project list
qdrantqdrant/qdrant:latest6333Vector DB (skipped when you pass --qdrant-url)
embedibaz/paparats-embed:latest18434Embed server — llama-server + llama-swap, bge-code-v1 + qwen3-embedding-0.6b pre-baked. Docker embed mode only (Linux default); on macOS the native embed server listens on the same port. llama-swap listens on 8080 inside the container

Host ports can be changed with PAPARATS_PORT, INDEXER_PORT, QDRANT_PORT and EMBED_PORT in ~/.paparats/.env. The embed port is 18434 rather than Ollama's 11434 so the two can run side by side.

The indexer hot-reloads projects.yml. Edits that change project metadata only (group, language, indexing tweaks) reindex in place; when the group changes, the project's data in the old group is purged. Deleting an entry purges its chunks, metadata and docs. Edits that add or remove local-path projects require a stack restart so Docker picks up the new bind-mount — the CLI does this for you on paparats add and paparats remove.


Install variants

Default (recommended)

paparats install

On macOS prefers the native embed server and dockerized Qdrant. On Linux defaults to Docker for both.

Bring your own Qdrant

paparats install --qdrant-url https://qdrant.example.com
# Asks for an API key after; stored in ~/.paparats/.env as QDRANT_API_KEY.

When --qdrant-url is set the Qdrant container is omitted from the stack entirely.

Bring your own embed server

paparats install --embed-url http://10.0.0.5:18434

Skips both the native and Docker embed server.

The remote endpoint must serve the bge-code-v1 model (and qwen3-embedding-0.6b for the arch-memory and docs layers) over an OpenAI-style /v1/embeddings API. The installer will not touch a remote instance. The simplest way is to run the pre-baked image on that host — no model registration needed, llama-swap loads GGUF by name on first request:

docker run -d -p 18434:8080 ibaz/paparats-embed:latest

Then paparats install --embed-url http://that-host:18434 and Paparats will use it.

Use a cloud embedding provider

paparats install --embeddings openai --embedding-api-key sk-...
paparats install --embeddings voyage --embedding-api-key pa-...

Skips the local embed service for code embeddings. Code chunks and queries are then sent to that provider — see Embedding Model Setup and PRIVACY.md.

Force Docker embed server on macOS

paparats install --embed-mode docker

Slower on Apple Silicon (no Metal GPU), but useful for parity testing or laptops without brew.

Scripted / CI

paparats install --non-interactive --force

Fails on any prompt; --force answers Y to compose-overwrite and migration prompts.


Migrating from a v1 install

When paparats install finds a legacy ~/.paparats/docker-compose.yml (the one from the old per-project flow with no paparats-indexer service), it prints a one-screen migration notice and asks before tearing the legacy stack down.

What survives: Qdrant collections, SQLite metadata, indexer repos, and any .paparats.yml files inside your repos (they stay the base config for each project; group, docs and indexing overrides from projects.yml are layered on top).

What's deleted: the legacy docker-compose.yml and .env. They are regenerated on the spot under the new schema.

No re-indexing needed — the data volumes are referenced by the same names in the new compose. Add your projects with paparats add and they re-appear in paparats list with their existing chunks.

If your install predates the paparats-indexer.yml → projects.yml rename, the installer migrates the file in place on first run and prints a one-line notice. The indexer also reads the legacy name as a fallback, so nothing breaks if you roll out the indexer before re-running paparats install.

Pass --force to skip the migration prompt in scripts.


Support agent setup

For bots and support teams that consume an existing Paparats server — no Docker, no embed server needed on this side.

# Connect to a running server (default: localhost:9876)
paparats install --mode support

# Connect to a remote server
paparats install --mode support --server http://paparats.example.com:9876

The installer verifies the server is reachable, then wires Cursor MCP (~/.cursor/mcp.json) and Claude Code (user scope, via claude mcp add; when the claude CLI isn't on your PATH it prints the command to run instead) to the support endpoint. Tools available on /support/mcp: search_code, get_chunk, find_usages, list_projects, health_check, get_chunk_meta, search_changes, explain_feature, recent_changes, impact_analysis, search_docs, get_document, term_search, term_list, plus the six analytics tools described in Monitoring and observability. The support endpoint is read-only — it writes nothing to the index, glossary or architectural memory — see the MCP Tools Reference.


How It Works

Your projects                   Paparats                       AI assistant
                                                               (Claude Code / Cursor)
  backend/                 ┌──────────────────────┐
    .paparats.yml ────────►│  Indexer              │
  frontend/                │   - chunks code       │          ┌──────────────┐
    .paparats.yml ────────►│   - embeds via llama  │─────────►│ MCP search   │
  infra/                   │   - stores in Qdrant  │          │ tool call    │
    .paparats.yml ────────►│   - re-checks on cron │          └──────────────┘
                           └──────────────────────┘

Indexing Pipeline

During each indexer cycle (the scheduled change check or full pass, paparats add, a projects.yml edit, or a POST /trigger), every file in scope flows through this pipeline:

 Source file
     │
     ▼
 ┌─────────────────┐
 │ 1. File discovery│  Collect files from indexing.paths, apply
 │    & filtering   │  gitignore + exclude patterns, skip binary
 └────────┬────────┘
          ▼
 ┌─────────────────┐
 │ 2. Content hash  │  SHA-256 of file content → compare with
 │    check         │  existing Qdrant chunks → skip unchanged
 └────────┬────────┘
          ▼
 ┌─────────────────┐
 │ 3. AST parsing   │  tree-sitter parses the file once (WASM)
 │    (single pass) │  → reused for chunking AND symbol extraction
 └────────┬────────┘
          ▼
 ┌─────────────────┐
 │ 4. Chunking      │  AST nodes → chunks at function/class
 │                  │  boundaries. Regex fallback for unsupported
 │                  │  languages (brace/indent/block strategies)
 └────────┬────────┘
          ▼
 ┌─────────────────┐
 │ 5. Symbol        │  AST queries extract module-level defines
 │    extraction    │  (function/class/variable names) and uses
 │                  │  (calls, references) per chunk. 11 languages
 └────────┬────────┘
          ▼
 ┌─────────────────┐
 │ 6. Metadata      │  Service name, bounded_context, tags from
 │    enrichment    │  config + auto-detected directory tags
 └────────┬────────┘
          ▼
 ┌─────────────────┐
 │ 7. Embedding     │  bge-code-v1 (code, 1536d) via llama-server
 │                  │  SQLite cache (content-hash key) → skip
 │                  │  already-embedded content
 └────────┬────────┘
          ▼
 ┌─────────────────┐
 │ 8. Qdrant upsert │  Vectors + payload (content, file, lines,
 │                  │  symbols, metadata) → batched upsert
 └────────┬────────┘
          ▼
 ┌─────────────────┐
 │ 9. Git history   │  git log per file → diff hunks → map
 │    (post-index)  │  commits to chunks by line overlap →
 │                  │  extract ticket refs → store in SQLite
 └────────┬────────┘
          ▼
 ┌─────────────────┐
 │10. Symbol graph  │  Cross-chunk edges: calls ↔ called_by,
 │    (post-index)  │  references ↔ referenced_by → SQLite
 └─────────────────┘

Step 5's symbol extractor only emits module-level definitions — locals declared inside function bodies, callback args, and hook closures stay out of the graph because they're not addressable from another chunk anyway. The chunking design is explained in docs/chunking-strategy.md.

When INDEX_DOCS=true is set on the indexer, each repo's markdown also goes through a separate docs pass into the documentation layer (see Documentation search).

Search Flow

AI assistant queries via MCP → server detects query type (natural language → code, code → code, technical question) → wraps the query in the matching bge-code-v1 instruction → expands query (abbreviations, case variants, plurals) → all variants searched in parallel against Qdrant → results merged by max score → only relevant chunks returned with confidence scores and symbol info.

Keeping the index fresh

The indexer container decides when to reindex; the MCP server never watches your files. Three mechanisms keep the index in step with your code:

  • Fast change check (CRON_FAST, default */10 * * * * — every 10 minutes). For each repo the indexer takes a fingerprint — git ls-remote <url> HEAD for remote repos, a hash of (path, mtime, size) over the project's file set for bind-mounted local paths — and reindexes the repo only when it differs from the last successful run. Unchanged files are skipped by content hash and are never re-embedded.
  • Full safety-net pass (CRON). Reindexes every repo regardless of the fingerprint. The indexer's built-in default is every 3 hours (0 */3 * * *); the compose file that paparats install generates sets every 6 hours (0 */6 * * *).
  • On demand. paparats add reindexes the project it adds, saving paparats edit projects triggers a full indexing cycle (unchanged files are skipped by content hash), and POST http://localhost:9877/trigger reindexes one, several or all repos (see CI/CD reindex on push).

Both schedules can be set in projects.yml (defaults.cron_fast, defaults.cron) or in the indexer environment. CHANGE_DETECTION=false turns the fast check off and leaves only the full pass. The server caches search results for up to 5 minutes, so a repeated identical query can return the previous answer until its cache entry expires.

The indexer also hot-reloads ~/.paparats/projects.yml itself: metadata-only edits reindex in place; add/remove of local-path projects triggers a stack restart through the CLI.


Key Features

Better Search Quality

Task-specific embeddings — bge-code-v1 is instruction-tuned, so each query is wrapped in the instruction its model card gives for the detected query type (documents are embedded without one). The docs and architecture layers use Qwen3-Embedding's single retrieval instruction:

  • "find authentication middleware" → natural language → code (CosQA instruction)
  • "function validateUser(req, res)" → code → similar code (CodeTrans-DL instruction)
  • "how does OAuth work in this app?" → technical question (StackOverflow-QA instruction)

Query expansion — every search generates 2-3 variations server-side:

  • Abbreviations: auth ↔ authentication, db ↔ database
  • Case variants: userAuth → user_auth → UserAuth
  • Plurals: users → user, dependencies → dependency
  • Filler removal: "how does auth work" → "auth"

All variants searched in parallel, results merged by max score.

Confidence scores — each result includes a percentage score (≥60% high, 40–60% partial, <40% low) to guide AI next steps.

Performance

Embedding cache — SQLite cache with content-hash keys + Float32 vectors. Unchanged code never re-embedded. LRU cleanup at 100k entries.

AST-aware chunking — tree-sitter AST nodes define natural chunk boundaries for 11 languages. Falls back to regex strategies (block-based, brace-based, indent-based, fixed-size) for other file types and for files where AST chunking yields nothing.

Change detection — a cheap fingerprint per repo every 10 minutes means an unchanged repo costs one git ls-remote or one directory stat, not a reindex. See Keeping the index fresh.

Cross-chunk symbol graph

The post-index pass walks every chunk's defines_symbols / uses_symbols lists and materializes edges into SQLite — calls, called_by, references, referenced_by. find_usages returns those edges grouped by direction so the agent can traverse the graph without re-searching. Because extraction is AST-driven, function locals don't pollute the graph.

Architectural memory (agent-maintained ADRs, components, lessons)

Code search tells the agent what the code does. Architectural memory tells it why — and the agent maintains that knowledge itself, across sessions, without you authoring a single doc.

Three card kinds, structured by design:

KindCapturesFields
ComponentA unit with a clear responsibility (service, module, subsystem)name, summary with Does / Owns / Does not / Touched when
DecisionAn architectural choice (ADR-style)title, context, decision, alternatives_rejected, consequences
LessonA rule learned from an incident, a code review, a bug, or a user correction (Reflexion-style)rule, why, when

The agent reads them via arch_context before any architectural answer, and writes them via arch_record_component, arch_record_decision, and arch_record_lesson whenever it discovers something new or learns from a correction. arch_list enumerates every card (for audits), arch_suggest_components proposes the most-called files not yet covered by a component card, and arch_delete removes obsolete cards. Each card carries an updated N ago stamp in the read tool so the agent can spot stale memory and verify against current code.

Server-side similarity gate (cosine over Qwen3-Embedding-0.6B text embeddings, 1024d):

  • ≥ 0.85 is a duplicate — decisions are refused (the agent must reconcile or supersede); lessons bump updatedAt (Reflexion-style "rule confirmed").
  • 0.70 – 0.85 is similar — surfaced to the agent so it can refine the wording or chain a supersede.
  • < 0.70 is new — accepted as a fresh card.

supersedes links bypass the gate and mark the prior decision as status=superseded so it disappears from default search but remains in history.

Why this matters:

  • 🧠 Cross-session continuity — what the agent learned last week, today's agent still knows.
  • 📝 ADRs without the ceremony — no markdown files to maintain, no review process, no doc drift. The agent writes when it learns.
  • 🔄 Reflexion built in — corrections become lessons; repeated mistakes get caught.
  • 🚦 No memory rot — similarity gate kills duplicates, supersedes link replaces stale decisions, age stamps trigger verification against code.

Lives in a separate Qdrant collection per group (paparats_<group>_arch). The layer is coding-only, for reads and writes alike: every arch_* tool is on the coding endpoint (/mcp), where the agent can verify a card against the code. The support endpoint has no arch tools — a rationale it cannot check against code is not something support should quote, so support routes "why is it built this way?" questions to an engineer.

arch_context accepts a min_score parameter (default 0.45, cosine over Qwen3-Embedding-0.6B). Lower it to broaden recall on a sparse arch memory; raise it to demand only high-confidence cards. The tool also emits an explicit low-confidence hint when the question matched nothing above the threshold, so the agent knows to either rephrase or lower min_score instead of inventing context.

Initialise the arch layer on day one. Three purpose-built MCP workflow prompts on the coding endpoint make the boring scaffolding work disappear:

  • init_arch_memory — the /init of architectural memory. Walks the repo, identifies 8-20 components by domain boundary, writes them, and captures any obvious decisions inferable from comments or README. Run it once per group, right after installing.
  • audit_architecture — sweeps the memory of one group, flags cards older than 90 days, verifies anchors against the live code, and surfaces a punch list of updates / supersedes for your approval.
  • record_lesson_from_correction — converts a user correction into a structured lesson card (rule / why / when) without overrecording typos.

MCP resources for live introspection (coding and support endpoints):

  • context://project-overview — indexed groups and projects with chunk counts.
  • arch://schema — the full card-schema reference (fields, similarity-gate thresholds, write semantics). Cite it from the agent when explaining the model.
  • arch://stats/{group} — live counts (total / by kind / by status) and the oldest/newest updatedAt per group. The same numbers are also pushed to Prometheus.

Observability built in. When PAPARATS_METRICS=true, every read/write hits a counter and the cosine score of returned cards lands in a histogram:

  • paparats_arch_context_calls_total{group} — calls per group
  • paparats_arch_write_total{kind, status} — writes by card kind and gate outcome
  • paparats_arch_search_score — histogram of cosine scores in arch_context results (post min_score)
  • paparats_arch_collection_size{group, kind, status} — gauge updated whenever arch://stats/{group} is read

These let you spot a memory that's not being written to, a similarity gate that's too aggressive, or a sparse group where every query returns low-confidence hits.

Documentation search (opt-in)

With INDEX_DOCS=true on the indexer, each repo's markdown is indexed into a separate collection per group (paparats_<group>_docs) with Qwen3-Embedding-0.6B. search_docs runs a hybrid search — dense vectors plus BM25 keywords, fused with reciprocal rank fusion (RRF) — and returns excerpts with their heading path and source link; get_document (support and knowledge endpoints) returns a whole document section by section. Every document carries an audience label that the endpoints can filter on.

INDEX_DOCS is off by default and the generated compose does not set it, so on a default install search_docs, get_document and the knowledge endpoint return nothing. To turn it on, add INDEX_DOCS: 'true' to the environment: block of the paparats-indexer service in ~/.paparats/docker-compose.yml (paparats edit compose) and run paparats restart — and see the note on hand edits being regenerated.

Glossary

A per-group glossary (paparats_<group>_terms) holds team vocabulary — abbreviations, product names, domain terms — with a definition and aliases. term_search and term_list read it on both the coding and support endpoints; term_record and term_delete are coding-only, since support is read-only (it hands new vocabulary to an engineer). Like decisions, new terms pass a duplicate/similar gate, so a near-identical term is returned instead of being written twice.

Workflow prompts per endpoint

EndpointPrompts
Coding (/mcp)find_implementation, trace_callers, onboard_to_project, init_arch_memory, audit_architecture, record_lesson_from_correction
Support (/support/mcp)triage_incident, prepare_release_notes, assess_change_impact, onboard_to_project
Knowledge (/knowledge/mcp)none

Use Cases

For Developers (Coding)

Connect via the coding endpoint (/mcp):

Use CaseHow
Navigate unfamiliar codesearch_code "authentication middleware" → exact locations
Find similar patternssearch_code "retry with exponential backoff" → examples
Trace dependenciesfind_usages {chunk_id, direction: "incoming"} → callers via the graph
Explore contextget_chunk <chunk_id> --radius_lines 50 → expand around
Manage projectslist_projects and delete_project for index hygiene
Architectural Q&Aarch_context "why X" → components / decisions / lessons (with age)
Capture decisions & lessonsarch_record_decision / arch_record_lesson — agent writes as it learns, server-side dedup

For Support Teams

Connect via the support endpoint (/support/mcp):

Use CaseHow
Explain a featureexplain_feature "rate limiting" → code locations + changes
Recent changesrecent_changes "auth" --since 2024-01-01 → timeline with tickets
Trace usagesfind_usages {chunk_id} → who calls/references this chunk
Change historyget_chunk_meta <chunk_id> → authors, dates, linked tickets
Blast radiusimpact_analysis "payment retry" → dependents grouped by service
Look up team vocabularyterm_search "abbreviation" → definition and aliases
Read the docssearch_docs "refund policy" → excerpts; get_document → full page

Support chatbot example:

User: "How do I configure rate limiting?"

Bot workflow (via /support/mcp):
1. explain_feature("rate limiting", group="my-app")
   → returns code locations + recent changes + related modules
2. get_chunk_meta(<chunk_id>)
   → returns who last modified it, when, linked tickets
3. Bot synthesizes response in plain language with ticket references

Configuration

Paparats uses two config files. Both are optional — defaults work for the common case.

~/.paparats/projects.yml — global project list

Lives outside your repos. Edited by paparats add / paparats remove or by hand via paparats edit projects. Every entry has either path: (local bind-mount) or url: (remote git, cloned by the indexer), never both.

defaults:
  cron: '0 */6 * * *' # full safety-net pass
  cron_fast: '*/10 * * * *' # change check (fingerprint per repo)
  group: workspace # default group when an entry doesn't specify one

repos:
  - path: /Users/alice/code/billing # local bind-mount
    group: dev
    language: typescript

  - url: org/widgets # remote git, cloned by the indexer
    group: prod
    language: ruby

  - url: acme/billing-service # always owner/repo on github.com
    name: billing # override the auto-derived name
    group: prod

The indexer hot-reloads this file. Adding/removing local-path entries causes the CLI to restart the stack so Docker picks up the new bind-mount; metadata-only edits reindex in place, and removed entries are purged from the index. Remote entries are GitHub repositories written as owner/repo; paparats add rejects other hosts.

.paparats.yml in your repo — per-project config

The base config for one repo, committed with it. projects.yml can layer group, docs and indexing overrides on top (see below).

group: my-app
language: typescript

# Indexing tuning (all optional)
indexing:
  paths: [src, packages] # restrict to these subdirectories
  exclude: [node_modules, dist, '**/*.test.ts']
  exclude_extra: ['**/__fixtures__/**'] # added on top of language defaults
  chunkSize: 1500 # characters per chunk, 128–8192 (default: 1024)
  overlap: 100 # chunk overlap (default: 128)
  concurrency: 4 # parallel embedding requests (default: 2)
  batchSize: 8 # embeddings per llama-server call (default: 50)

# Metadata
metadata:
  service: billing
  bounded_context: payments
  tags: [backend, critical]
  directory_tags:
    src/api: [public-api]
    src/internal: [internal]

  # Git history per chunk (Jira / GitHub ticket extraction included)
  git:
    enabled: true
    maxCommitsPerFile: 50
    ticketPatterns:
      - '\b([A-Z]+-\d+)\b' # Jira-style PROJ-123
      - '#(\d+)' # GitHub-style #123

# Documentation layer (the repo's markdown, served by search_docs; needs INDEX_DOCS=true)
docs:
  kind: prose # prose | code — omit to auto-detect
  audience: client # default visibility label for this repo's docs — omit = internal

Documentation truncated — see the full README on GitHub.

Reviews

No reviews yet

Be the first to review this server!