Paparats MCP: Self-Hosted Semantic Code Search

io.github.IBazylchukPublished by GitHub account IBazylchukv2.18.2Updated Nov 4, 2026

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

Overview

AI-generated overview

Self-hosted MCP server for semantic code search across multiple repositories, with AST-aware chunking, a symbol graph and an architectural memory layer.

What you can do
  • Search code semantically across several indexed repositories, with task-prefixed embeddings and confidence scoring
  • Search and read curated documentation through a separate docs-only endpoint
  • Read and maintain architectural memory cards (components, decisions, lessons) that record why the code is the way it is
  • Bootstrap and audit that memory: initialise cards from a repo walk, sweep stale cards, turn corrections into lessons
  • Register projects and update single files through HTTP endpoints, with optional Prometheus metrics and an operator console
When to use it
Useful when an assistant needs to find relevant code in one or more large repositories without pasting files into context, or when you want a local, self-hosted alternative to a hosted code-search service. The docs-only endpoint suits exposing a curated documentation search to an agent in another network.
Requirements
Runs as a local process, installed through the @paparats/cli npm package or the Docker image; the server package itself is not published to npm. Needs a Qdrant instance (QDRANT_URL, optional QDRANT_API_KEY) and an embedding server (EMBED_URL), or OpenAI/Voyage providers. Optional settings include PAPARATS_API_KEY, PAPARATS_UI_BASIC_AUTH, PAPARATS_KNOWLEDGE_TOKENS with PAPARATS_KNOWLEDGE_AUDIENCE, and PAPARATS_CORS_ORIGINS.
Before you install
With PAPARATS_API_KEY unset the server is open to anyone who can reach it, so bind it to localhost or a VPN. The knowledge endpoint requires bearer tokens in PAPARATS_KNOWLEDGE_TOKENS and an audience ceiling; without PAPARATS_KNOWLEDGE_PROJECTS a stray audience label in a repository README can widen what it serves. Indexing sends code and docs to the configured embedding provider, which may be a third party. The server writes to Qdrant collections and its SQLite metadata store.

Installation

In SourceWeft

  1. Open Paparats MCP: Self-Hosted Semantic Code Search in the dashboard and add it to a workspace.
  2. Enable the server for the chats that should use its tools.

Desktop only via STDIO. STDIO servers start a local process, so they need the SourceWeft desktop host.

Other MCP clients

Follow the launch instructions in the repository.

README

@paparats/server

The MCP server for Paparats MCP — semantic code search over multiple repos with AST-aware chunking, a cross-chunk symbol graph, git-history per chunk, and an agent-maintained architectural memory layer.

Not published to npm. This package is consumed as a library by @paparats/indexer and shipped as a Docker image: ibaz/paparats-server. End users install through @paparats/cli.

What this package does

Three jobs in one process:

  • Indexer. AST-aware chunking (tree-sitter, 11 languages), per-chunk symbol extraction, single-parse pipeline, llama-server embedding with SQLite cache, batched Qdrant upsert. Post-index passes attach git history, ticket references, and cross-chunk symbol edges (calls / called_by / references / referenced_by).
  • Searcher. Task-prefixed embedding (nl2code / code2code / techqa), query expansion for the support tools (abbreviations, case variants, plurals, filler removal), in-memory LRU query cache with per-group invalidation, confidence scoring, token-savings telemetry.
  • MCP server. Three endpoints — /mcp (coding), /support/mcp (support) and the opt-in, docs-only /knowledge/mcp (knowledge) — each with its own tool set and system instructions. Streamable HTTP for every client, with legacy SSE routes kept. Plus an agent-maintained architectural memory layer.

Endpoints

http://<host>:9876/health                  – liveness probehttp://<host>:9876/metrics                 – Prometheus (PAPARATS_METRICS=true)http://<host>:9876/ui                      – operator consolehttp://<host>:9876/api/analytics/...       – analytics REST API (UI backend)
http://<host>:9876/mcp                     – MCP, coding mode (Streamable HTTP)http://<host>:9876/sse                     – MCP, coding mode (legacy SSE)http://<host>:9876/messages                – MCP, coding mode (SSE message channel)
http://<host>:9876/support/mcp             – MCP, support modehttp://<host>:9876/support/sse             – MCP, support mode (SSE)http://<host>:9876/support/messages        – MCP, support mode (SSE message channel)
http://<host>:9876/knowledge/mcp           – MCP, knowledge mode (stateless, bearer auth,                                             POST only; mounted only when configured)

POST /api/index registers a project; POST /api/file-changed and POST /api/file-deleted update single files. See docs/tools.md for the full tool inventory per endpoint.

Knowledge mode

A docs-only MCP endpoint for a consumer in another network — an agent backend behind a load balancer that may search curated documentation and nothing else. Off unless PAPARATS_KNOWLEDGE_TOKENS and PAPARATS_KNOWLEDGE_AUDIENCE are both set; one without the other stops the server at startup.

  • Tools: search_docs and get_document only. No glossary block, no resources, no workflow prompts. search_docs returns structuredContent with an outputSchema, so a wrapping service can build citations without parsing markdown.
  • Auth: Authorization: Bearer <token>, compared in constant time, checked before the request body is read. 401 + WWW-Authenticate: Bearer otherwise.
  • Scope: documents labelled with an audience in PAPARATS_KNOWLEDGE_AUDIENCE (never internal), in the projects of PAPARATS_KNOWLEDGE_PROJECTS when set. Both are intersected with PAPARATS_DOCS_AUDIENCE / PAPARATS_PROJECTS when those are set. Out-of-scope documents read exactly like missing ones.
  • Transport: stateless Streamable HTTP — a fresh server and transport per POST, no session id; GET/DELETE answer 405. 64 kB body limit, 1000-character query cap.

Documents get their audience at index time: frontmatter audience: first, then docs.audience from .paparats.yml / projects.yml, otherwise internal. An invalid frontmatter value is logged and stored as internal. See the documentation search guide.

Set PAPARATS_KNOWLEDGE_PROJECTS — otherwise a stray audience: in some code repository's README is enough to widen what the endpoint serves.

Expose only that path, POST only. For example with nginx:

nginx
location = /knowledge/mcp {  limit_except POST { deny all; }  client_max_body_size 64k;  proxy_pass http://paparats:9876;  proxy_http_version 1.1;  proxy_buffering off; # responses stream as text/event-stream}location / {  return 404;}

Architectural memory

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

Three card kinds in a separate Qdrant collection per group (paparats_<group>_arch), embedded with qwen3-embedding-0.6b (1024d, last-pooled):

KindFieldsIdempotency
Componentname, summary (markdown: Does / Owns / Does not / Touched when), files, neighbours, anchorsBy name (structural)
Decisiontitle, context, decision, alternatives_rejected, consequences, scope, supersedesServer-side similarity gate
Lessonrule, why, when, scope, severity, evidenceServer-side similarity gate

Similarity gate (cosine on qwen3-embedding-0.6b):

  • >= 0.85 → duplicate. Decisions are refused so the agent must reconcile or supersede. Lessons bump updatedAt on the existing card (Reflexion "rule confirmed").
  • 0.70 – 0.85 → similar. Nothing is written; the agent should refine wording or pass supersedes.
  • < 0.70 → accepted as a new card.

supersedes bypasses the gate and marks the prior decision status=superseded so it disappears from default search but stays in history.

Reading via arch_context accepts min_score (default 0.45). Each returned card carries an updated N ago stamp and the cosine score so the agent can detect stale or low-confidence hits.

Bootstrap with init_arch_memory — a workflow prompt analogous to /init in Claude Code. Walks the repo, identifies 8-20 components by domain boundary, writes the cards. Followed by audit_architecture (sweep stale cards) and record_lesson_from_correction (turn user corrections into structured lessons).

MCP resources:

  • arch://schema — full card-schema reference.
  • arch://stats/{group} — live counts (total / by kind / by status) and oldest/newest updatedAt.

Prometheus metrics (PAPARATS_METRICS=true):

  • paparats_arch_context_calls_total{group} — counter
  • paparats_arch_write_total{kind, status} — counter labelled by gate outcome
  • paparats_arch_search_score — histogram of cosine scores from arch_context
  • paparats_arch_collection_size{group, kind, status} — gauge

arch_context is read-only on both MCP endpoints. arch_record_* is support-only — recording belongs to the architectural-review workflow.

Module map

ModuleResponsibility
types.tsShared interfaces
lib.tsPublic library entry point (re-exports for programmatic use)
config.ts.paparats.yml reader, 11 language profiles, autoProjectConfig()
app.tsExpress app factory, HTTP API routes, request wiring
index.tsServer bootstrap — starts HTTP server, wires components, graceful shutdown
ast-chunker.tsAST-based chunking via tree-sitter
chunker.tsRegex chunking fallback (4 strategies)
ast-symbol-extractor.tsAST-based per-chunk symbol extraction
ast-queries.tsTree-sitter S-expression queries per language
tree-sitter-parser.tsWASM tree-sitter manager — lazy grammar loading
symbol-graph.tsCross-chunk symbol edges
embeddings.tsllama-server provider + SQLite cache + cached wrapper
indexer.tsGroup-aware Qdrant indexing — createQdrantClient(), toCollectionName(), single-parse chunkFile()
searcher.tsVector search, query expansion (support tools only), cache, metrics
query-expansion.tsAbbreviation / case / plural / filler expansion
task-prefixes.tsQuery-type detection + bge-code-v1 / Qwen3 query instructions
query-cache.tsIn-memory LRU cache with TTL and per-group invalidation
metrics.tsPrometheus metrics (prom-client) + NoOpMetrics fallback
metadata.tsTag resolution + auto-detection from directory structure
metadata-db.tsSQLite store for git commits, tickets, symbol edges
git-metadata.tsGit history extraction — diff-hunk mapping to chunks
ticket-extractor.tsJira / GitHub / custom ticket parsing
mcp-handler.tsMCP protocol — coding, support and knowledge endpoints with isolated tool sets and instructions
knowledge-mode.tsKnowledge mode — env parsing, bearer auth, body limit, scope intersection
arch/types.tsArchitectural-memory types
arch/collection.tsPer-group paparats_<group>_arch collection lifecycle
arch/text-embeddings.tsqwen3-embedding-0.6b text embedder via llama-server
arch/store.tsCRUD + server-side similarity gate + stats() aggregation
arch/context.tsarch_context query — top-N across kinds with min_score and age stamps

Configuration (env vars)

VariableDefaultNotes
PORT9876HTTP port
QDRANT_URLhttp://localhost:6333Qdrant endpoint (https → port 443, http → 6333)
QDRANT_API_KEY—Qdrant Cloud API key
EMBED_URLhttp://127.0.0.1:18434Embed server endpoint (llama-swap; internal port 8080, host port 18434 in the generated stack)
EMBED_BATCH_SIZE—Embeddings per llama-server call
EMBED_TTL0Idle seconds before a model unloads; 0 = never (default)
EMBEDDING_PROVIDERllamaCode embedding provider: llama | openai | voyage
EMBEDDING_MODELbge-code-v1Code embedding model
EMBEDDING_DIMENSIONS1536Code embedding dimensions
TEXT_EMBEDDING_PROVIDERllamallama, openai or voyage for the arch layer
TEXT_EMBEDDING_MODELqwen3-embedding-0.6bArch-layer text embedding model
TEXT_EMBEDDING_DIMENSIONS1024Arch-layer text embedding dimensions
PAPARATS_METRICSfalseSet to true to expose /metrics
PAPARATS_PROJECTS—Comma-separated allow-list of project names
PAPARATS_DOCS_AUDIENCE—Comma-separated audience ceiling for search_docs / get_document on the coding and support endpoints
PAPARATS_KNOWLEDGE_TOKENS—Comma-separated bearer tokens (≥ 16 characters) for /knowledge/mcp. Set together with PAPARATS_KNOWLEDGE_AUDIENCE, or neither
PAPARATS_KNOWLEDGE_AUDIENCE—Audience ceiling of /knowledge/mcp; internal is refused
PAPARATS_KNOWLEDGE_PROJECTS—Optional project allowlist of /knowledge/mcp — recommended
PAPARATS_API_KEY—Optional: comma-separated keys (≥ 16 characters); every route but /health and /knowledge/mcp then wants Authorization: Bearer <key>. Unset, the server is open (localhost, VPN). A malformed key stops startup
PAPARATS_UI_BASIC_AUTH—user:pass for /ui, /api/analytics/... and /api/module-map/... (else they take the API key as a basic-auth password); a malformed value stops startup
PAPARATS_CORS_ORIGINS—Comma-separated exact origins (e.g. https://app.example.com) browsers may call the server from. Unset = no CORS headers, and any request whose Origin is neither this server nor listed gets 403. Requests without Origin (MCP clients, curl) are unaffected
OTEL_*—Standard OpenTelemetry env vars (Tempo / Honeycomb / Datadog / Elastic APM)

Programmatic use (library)

The package.json exports map points at dist/lib.js, not the server bootstrap. Importing @paparats/server does not start the HTTP server — @paparats/indexer uses this to embed the Indexer class as a library:

ts
import { Indexer, createQdrantClient } from '@paparats/server';
const qdrant = createQdrantClient({ url: process.env.QDRANT_URL! });const indexer = new Indexer({ qdrant, /* ... */ });await indexer.indexProject(group, project);

Docker

bash
docker pull ibaz/paparats-server:latestdocker run --rm -p 9876:9876 \  -e QDRANT_URL=http://qdrant:6333 \  -e EMBED_URL=http://embed:8080 \  ibaz/paparats-server:latest

See examples/docker-compose for a complete stack (Qdrant + embed server + server + indexer).

Development

bash
yarn installyarn workspace @paparats/server buildyarn workspace @paparats/server testyarn workspace @paparats/server typecheck

Related packages

PackagePurpose
@paparats/cliOne-command installer, project management, search
@paparats/indexerAutomated multi-repo indexer (uses this as a library)
@paparats/sharedShared utilities (path validation, gitignore, excludes)
ibaz/paparats-embedllama.cpp llama-server + llama-swap with pre-baked bge-code-v1 and qwen3-embedding-0.6b

License

MIT

Source: packages/server/README.md at commit 393a5cb

Tools

0
SourceWeft started this package once in an isolated sandbox, without credentials, to list its tools. That failed: the package type cannot run in the sandbox.

Version history

15
  1. v2.18.2LatestNov 4, 2026
  2. v2.18.1Nov 3, 2026
  3. v2.17.0Nov 1, 2026
  4. v2.16.9Oct 30, 2026
  5. v2.16.8Oct 30, 2026
  6. v2.16.7Oct 30, 2026
  7. v2.16.6Oct 29, 2026
  8. v2.15.0Oct 26, 2026
  9. v2.14.1Oct 26, 2026
  10. v2.12.1Oct 26, 2026
  11. v2.11.3Oct 25, 2026
  12. v2.11.2Oct 25, 2026
  13. v2.9.0Oct 13, 2026
  14. v2.8.0Oct 7, 2026
  15. v2.7.0Sep 16, 2026

Score

50% of the applicable checks met

How complete this listing is, computed from the registry entry, the endpoint and public sources. Not a rating of the server and not a statement that it is safe or working today: reachability is a dated check, and no installs or reviews go into the score.

  • Reached in the last 30 days25 pts

    Keep the endpoint answering the MCP handshake; SourceWeft probes it every 30 days.

  • Auth declared10 pts

    Declare the credential headers in the registry entry, or keep the endpoint answering the probe so its auth can be classified.

  • Tools listed15 pts

    Answer tools/list without a credential, or ship a package that starts in a sandbox without one.

  • DescriptionRequired15 pts
  • Logo10 pts
  • Documentation15 pts
  • License10 pts