Corpus-KB
Use Corpus-KB over MCP to ingest, search, organize, query, and version local document/code data.
Ingest individual files, raw text, or directories; list and delete documents.
Search with hybrid vector/full-text/RRF, context-expanded results, similar chunks, and LLM-ready citations.
Build and query a knowledge graph: add entities/relations, search entities, run BFS, and fetch relations.
Run SQL SELECT queries, guarded writes, and inspect table schemas.
Manage tags and metadata, and sync vector-store data into relational tables.
View document/database statistics and manage time-travel versions, tags, branches, checkouts, and restores.
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., "@Corpus-KB search my codebase for examples of error handling"
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.
Corpus-KB
A local NLP engine for files, transcripts, and code. Ingest, search, ask, extract, and code. No cloud.
Corpus-KB serves three kinds of users. Non-coders get a browser UI. Novice coders get a CLI with a guided quickstart. Expert researchers get a qualitative-coding pipeline, an OpenAI-compatible API, and MCP tools for coding agents. Everything runs on your machine. Postgres stores the data by default; DuckDB is the single-file local engine (Storage backends). Ollama or PostgresML generates embeddings. One server exposes the catalogue over MCP stdio, HTTP, and a JSON-RPC socket. An OpenAI-compatible adapter serves /v1 chat and embeddings. See Editor integration.
Two ways to start
Pick the path that fits you. Both end at the same working system.
The novice path: the browser
No terminal work beyond a few commands. First install the package:
pip install -e .Then build the browser app once. You need Node 24 and pnpm for this step. Node is a build tool only. The running server stays pure Python:
pnpm -C ui_src install
pnpm -C ui_src buildThen start the stack. corpus-kb setup starts Postgres and loads the
schema. corpus-kb start starts the server and opens your browser:
corpus-kb setup
corpus-kb startThe browser opens the lifecycle app at http://localhost:8010/app/.
The Setup stage shows the system's health in plain language. The other
stages follow the research flow: Ingest, Review, Scale, Ask, Analyze,
Codebook, Release.
Details: docs/UI.md.
The expert path: terminals and APIs
Every operation is reachable programmatically. One line per surface:
MCP stdio for editor agents:
corpus-kb --transport stdio— docs/API.mdHTTP API with curl examples: docs/API.md
OpenAI-compatible adapter at
/v1(chat completions, embeddings): docs/API.mdJSON-RPC socket for local automation: docs/API.md
CLI:
corpus-kb ingest,corpus-kb search,corpus-kb ask— docs/getting-started.mdTracing with OpenTelemetry spans: docs/OBSERVABILITY.md
Offline evaluation of retrieval and answers: docs/EVALUATION.md
marimo notebooks served at
/notebooks: docs/NOTEBOOKS.mdTopics (offline BERTopic over stored embeddings): docs/ANALYSIS.md
First time on a terminal? corpus-kb quickstart walks you from zero to
a first search. The step-by-step quickstart is in
docs/getting-started.md.
Related MCP server: ragi
What you get
Hybrid search that blends vector similarity, full-text search, and rank fusion, with honest handling of degraded (zero-vector) chunks
Reranking — a local judgment scorer by default, or a true cross-encoder through llama.cpp
Answer generation with per-sentence citations, plus assertion-level verification with honest abstention
Query routing — each question goes to vector search, SQL aggregates, or graph traversal
Self-query filters — natural-language filters parsed into parameterized SQL predicates
Knowledge graph with entities, relations, and BFS traversal
Ontology-aware extraction with configurable entity/relation types and pluggable backends (regex, LangExtract, PostgresML, plus opt-in GLiNER)
Pluggable ingestion strategies — eight built-ins (default, transcript, slides, web, survey, markdown, code, textbook), extensible three ways (docs/INGESTION.md)
Website ingestion — a robots-respecting fetcher with same-domain crawl and heading-section chunks
Late chunking — one model pass per document; chunk vectors pool from their own token spans
Qualitative coding — transcript ingestion, codebooks, deductive and inductive runs, human review, governance reports, and a gated release lifecycle
Topics — offline BERTopic clustering over stored embeddings (docs/ANALYSIS.md)
LlamaIndex RAG backend with PGVectorStore and Ollama for local vector search
Event sourcing for audit trails and rebuildable projections
Multi-tenant Postgres with row-level security on every table
Full-stack installer with hardware detection, profile-based recommendations, and guided setup
MCP, HTTP, socket, and CLI surfaces so any editor or script can talk to it
Browser UI with drag-and-drop ingest, cited answers, and plain-language health checks (docs/UI.md)
Notebooks console — marimo notebooks served by the running server (docs/NOTEBOOKS.md)
Architecture
graph TB
Editor[AI Code Editor] -->|"MCP stdio"| MCP[MCP Server]
Editor -->|HTTP :8010| HTTP[Starlette API]
Editor -->|JSON-RPC socket| Socket[Unix socket / named pipe]
MCP --> Handlers[Command / Query Handlers]
HTTP --> Handlers
Socket --> Handlers
Handlers --> Domain[Domain Layer<br/>Aggregates + Events]
Domain --> ES[Event Store<br/>append-only]
ES --> Projections[Async Projections]
Projections --> PG[Postgres 17]
Projections --> DUCK[DuckDB<br/>local single-user engine]
PG --> VEC[pgvector<br/>vector search]
PG --> FTS[Postgres FTS<br/>to_tsvector]
PG --> AGE[Apache AGE<br/>Cypher graphs]
PG --> RLS[RLS on 12 tables]
Projections --> Ollama[Ollama<br/>embedding service]Ingest a file, directory, or raw text.
The pipeline partitions it into chunks and embeds each chunk through Ollama or PostgresML. It extracts entities and relations, then stores the result.
Commands append events to the event store; async projections write the read models into Postgres or DuckDB.
Your editor queries the read models through search, SQL, or graph traversal.
Event sourcing flow
sequenceDiagram
participant C as Client
participant H as CommandHandler
participant A as Document Aggregate
participant ES as Event Store
participant P as Projection
participant DB as Postgres Tables
C->>H: ingest_file(file_path)
H->>A: create Document
A->>A: apply Ingested event
A->>A: apply ChunksAdded event
H->>ES: app.save(aggregate)
ES-->>H: event_id, version
H-->>C: {status: "success"}
ES->>P: subscribe(ChunksAdded)
P->>P: embed chunks via Ollama
P->>DB: INSERT chunks, chunks_vectors
P->>DB: UPDATE projection_checkpointsEvents are the source of truth. Projections are derived and can be rebuilt by replaying the event log. Vectors live in the chunks_vectors table and are treated as derived data, not event payload.
Documentation
Page | What it covers |
Both paths: the browser for novices, the terminal quickstart for everyone else. Then qualitative coding from zero: the demo, your first transcript, first codebook | |
Full setup from scratch: Postgres, Python, Ollama, schema, first query | |
The lifecycle app: stage-by-stage user guide, config, build, gates, troubleshooting | |
Ingest, search, quality controls, graph, tags, research subsystem, embedding models, MCP tool reference | |
Configuration, schema, multi-tenancy, backups, monitoring, CI/CD | |
DuckDB vs Postgres: quickstart, engine selection, migrate-data, runtime tuning | |
The operator's contract: exit codes, ports, vocabularies, tenancy, the MCP surface | |
HTTP routes, the | |
OpenTelemetry spans, exporters, zero-cost when off | |
Golden sets, recall/MRR/nDCG, answer metrics | |
The marimo console and the three example notebooks | |
Architecture deep dive, testing, PR workflow, conventions | |
Research command reference, config keys, embedding requirements | |
Plain-language guide to every governance-report section | |
Common questions | |
Full pipeline: partition, chunk, embed, extract, store, plus the strategy reference and extension ladders | |
The analysis layer: analyze-run report, labeling, classification, topics, trends, method register | |
GENERATED reference: the 30-method register with required validation and permitted claims | |
Practiced procedure for backing out a bad deployment | |
Archive: playbooks, research math, benchmarks, harness distribution, program plans |
Editor integration
Editors connect over MCP stdio (native, no bridge), the HTTP API, or the JSON-RPC socket:
MCP stdio:
corpus-kb --transport stdio— themcp-configs/files (Claude Code, Cursor, Codex) and the rootopencode.json(OpenCode) launch exactly this command. Generic MCP clients such as Hermes or Cline need no config file: they point at the same stdio server. See docs/research/harness-distribution.md. The server registers every catalogue operation as an MCP tool; see docs/FEATURES.md for the tool reference.HTTP:
corpus-kb start --transport http --port 8010— every catalogue operation has a route, plus the/v1OpenAI adapter (see docs/API.md).JSON-RPC socket: same command, for local automation.
The research and coding surfaces are first-class CLI commands (
corpus-kb research ...andcorpus-kb coding ...). Ingestion has a top-level front door:corpus-kb ingestwith--watchauto-heal, pluscorpus-kb ingest-statusandcorpus-kb ingest-reembed.
Every catalogue operation is reachable on all four surfaces (MCP, CLI,
HTTP, socket) and returns the same envelope on each. The parity gate in
tests/test_surface_parity_deep.py enforces this.
License
MIT License. See pyproject.toml for the full text.
Available Tools
33 toolsadd_entityA
Add an entity to the knowledge graph.
Args: name: Entity name (e.g., "MyClass", "Authentication", "Paris"). type: Entity type (class, function, concept, person, place, etc.). metadata: Optional metadata dict.
Returns: Created entity details including entity_id.
Inputs: name (required), type, metadata.
add_relationA
Add a directed relation between two entities.
Args: source_id: Source entity ID. target_id: Target entity ID. rel_type: Type of relation (CALLS, DEPENDS_ON, CONTAINS, etc.). weight: Relation strength (0.0 to 1.0).
Returns: Created relation details.
Inputs: source_id (required), target_id (required), weight, rel_type.
add_tagC
Create a new tag for categorizing documents.
Inputs: name (required), color, description.
bfsC
BFS traversal from a starting entity.
Args: start_entity_id: Entity ID to start from. max_depth: Max traversal depth (1-10).
Returns: List of (entity_id, name, type, depth) entries.
Inputs: start_entity_id (required), max_depth. Returns: result
checkout_versionC
Check out a specific table version for time-travel queries.
Inputs: version (required).
create_branchC
Create a new branch from an optional version.
Inputs: branch_name (required), from_version.
create_tagB
Tag a specific version for reference.
Args: version: Version number to tag. tag_name: Human-readable tag name (e.g., "v1.0", "before-refactor").
Returns: Confirmation with version and tag name.
Inputs: version (required), tag_name (required).
delete_documentC
Delete an ingested document by its doc_id.
Inputs: doc_id (required).
get_document_tagsA
Get all tags applied to a document.
Inputs: doc_id (required). Returns: result
get_entity_relationsC
Get all relations for an entity (incoming and outgoing).
Args: entity_id: Entity ID.
Returns: List of relations with source/target info.
Inputs: entity_id (required). Returns: result
get_metadataA
Retrieve metadata entries, optionally filtered by key and/or doc_id.
Omit both to get all metadata. Filter by key to find all values for a key. Filter by doc_id to find all metadata for a document.
Inputs: key, doc_id. Returns: result
get_statsC
Get database statistics.
Returns: Stats object with counts and storage info.
Inputs: no named parameters.
ingest_directoryC
Ingest all supported files in a directory.
Supports all extensions in CodeChunker.LANGUAGE_MAP plus .md, .rst, .txt.
Inputs: directory_path (required), recursive. Returns: result
ingest_fileC
Ingest a single file. Auto-detects code/markdown/text.
Inputs: file_path (required).
ingest_textB
Ingest raw text with optional type hint (code/markdown/text).
Inputs: text (required), source, file_type.
list_branchesA
List all branches.
Inputs: no named parameters. Returns: result
list_documentsB
List all ingested documents with their metadata.
Inputs: no named parameters. Returns: result
list_versionsA
List all versions of the chunks table for time-travel.
Returns: List of version entries with version number, timestamp, and tag.
Inputs: no named parameters. Returns: result
query_document_statsB
Get aggregate statistics about the document corpus via SQL.
Returns: total documents, total chunks, docs by type, chunks by type, average chunks per document, date range.
Inputs: no named parameters.
restore_versionC
Restore the table to a specific version.
Inputs: version (required).
retrieve_contextB
Search and return results formatted for LLM context building.
Args: query: Natural language query. k: Number of results (max 20). filters: Optional JSON string of filter conditions.
Returns: Formatted string with source citations.
Inputs: query (required), k, filters. Returns: result
searchA
Hybrid search (vector + full-text + RRF) across all chunks.
Args: query: Natural language query. k: Number of results (max 50). source_type: Optional filter: "code", "markdown", or "text".
Returns: List of search results with text, source, score, and metadata.
Inputs: query (required), k, source_type. Returns: result
search_contextA
Search with parent/sibling/child context expansion.
For each result, includes surrounding chunks (context_chunks before and after) so the LLM has full context.
Args: query: Natural language query. k: Number of primary results (max 20). context_chunks: Number of adjacent chunks to include (0-5). source_type: Optional filter: "code", "markdown", or "text".
Returns: List of results, each with a "context" field containing surrounding chunks.
Inputs: query (required), k, source_type, context_chunks. Returns: result
search_graphA
Search entities in the knowledge graph by name or type.
Args: query: Search term for entity name. type: Optional entity type filter. limit: Max results (max 100).
Returns: List of matching entities.
Inputs: query (required), type, limit. Returns: result
search_similarA
Find chunks similar to a given chunk by ID.
Args: chunk_id: ID of the source chunk. k: Number of similar results to return (max 20).
Returns: List of similar chunks with scores and text snippets.
Inputs: chunk_id (required), k. Returns: result
set_metadataA
Set a metadata key-value pair, optionally scoped to a document.
Overwrites any existing value for the same key+doc_id combination.
Inputs: key (required), value (required), doc_id.
sql_executeA
Execute a write SQL statement (INSERT, UPDATE, DELETE) with safety rails.
Safety rules enforced by the engine:
DELETE/UPDATE without WHERE clause is BLOCKED
DROP TABLE/DATABASE/SCHEMA is BLOCKED
Use parameterized ? placeholders for values to prevent injection. For SELECT queries, use sql_query instead.
Examples: INSERT INTO tags (name, color) VALUES ('urgent', 'red') UPDATE documents SET source_type = 'markdown' WHERE doc_id = 'abc-123' DELETE FROM document_tags WHERE doc_id = 'abc-123' INSERT INTO metadata (key, value, doc_id) VALUES ('reviewer', 'alice', 'abc-123')
Args: statement: SQL write statement (INSERT, UPDATE, DELETE).
Returns: Dict with "affected_rows" count, or "error" if blocked.
Inputs: statement (required).
sql_queryA
Run a SQL SELECT query over relational tables.
Full SQL supported: SELECT, JOIN, CTE, GROUP BY, window functions, subqueries, UNION, etc.
Available tables:
documents: Document-level metadata (doc_id, source, source_type, chunk_count, file_size, file_hash, language, created_at)
chunks: Individual text chunks (chunk_id, doc_id, chunk_index, source_type, chunk_type, entity_name, file_path, start_line, end_line, char_count)
tags: Defined tags (tag_id, name, color, description)
document_tags: Many-to-many mapping (doc_id, tag_id)
metadata: Flexible key-value store (key, value, doc_id)
Examples: SELECT source_type, COUNT(*) as cnt FROM documents GROUP BY source_type SELECT * FROM chunks WHERE source_type = 'code' LIMIT 10 SELECT d.source, COUNT(c.chunk_id) as chunks FROM documents d JOIN chunks c ON d.doc_id = c.doc_id GROUP BY d.source ORDER BY chunks DESC SELECT d.source FROM documents d JOIN document_tags dt ON d.doc_id = dt.doc_id JOIN tags t ON dt.tag_id = t.tag_id WHERE t.name = 'important'
Args: query: SQL SELECT query string. limit: Max rows to return (default 100, max 5000).
Returns: Dict with "columns", "rows", and "row_count".
Inputs: query (required), limit.
sql_tablesA
List all available relational tables with their schema.
Returns table name, column name, column type, and nullability for each column in every user table.
Inputs: no named parameters. Returns: result
switch_branchC
Switch to a specified branch.
Inputs: branch_name (required).
sync_databaseA
Sync data from LanceDB vector store into relational tables.
Call this after ingesting documents to make relational queries up to date. The sync is idempotent — call it anytime.
Inputs: no named parameters.
tag_documentB
Apply a tag to a document. Creates the tag if it doesn't exist.
Inputs: tag (required), doc_id (required).
untag_documentC
Remove a tag from a document.
Inputs: tag (required), doc_id (required).
Tool Schema Changelog
Recent tool additions, removals, and schema changes observed during successful MCP inspections.
Loading tool schema changelog…
TDQS
Scored across 33 tools
Most tools have distinct purposes, but the high number of search tools (search, search_context, search_similar, search_graph) and versioning tools could cause minor ambiguity. However, descriptions are detailed enough to differentiate them.
All tools consistently use snake_case with a verb_noun pattern, e.g., add_entity, create_branch, search_similar. Even 'bfs' is a clear abbreviation following the same style.
33 tools is on the higher side, but each serves a distinct purpose in ingestion, search, graph, versioning, and SQL querying. The count is slightly heavy but still well-scoped for a knowledge base server.
The tool set covers the full lifecycle: ingestion, search with multiple modes, knowledge graph management, version control, SQL querying, and metadata handling. No obvious gaps for the domain.
Maintenance
Related MCP Connectors
Self-hosted AI workspace: semantic RAG document search, long-term memory, and AI chat via MCP.
Cloud or self-hosted knowledge for AI agents: hybrid search, reranking, GraphRAG, scoped MCP tools.
Agent-native MCP server over the public saagarpatel.dev corpus. Read-only, stateless.
Shared memory MCP server for Claude Code, Codex, Gemini CLI and Cursor. Cited, editable.
Related MCP Servers
- FlicenseNot gradedqualityDmaintenanceLocal MCP server that provides semantic search (RAG) over code repositories, enabling AI clients like Claude and Gemini to access project context without manual re-upload.-
- AlicenseAqualityDmaintenanceLocal-first RAG indexing and semantic search MCP server. Enables document retrieval and context-aware queries using local embedding models.314 npmMIT
- AlicenseNot gradedqualityCmaintenanceA local-first RAG engine that ingests documents (PDF, Markdown, images, etc.) and provides hybrid search, reranking, and LLM answer synthesis via MCP for AI agent integration.34 PyPI3MIT
- AlicenseNot gradedqualityBmaintenanceLocal RAG over a directory, served as an MCP tool plus CLI, with incremental indexing, zero-config defaults, and offline CPU-only operation.MIT