wikijs-mcp-rag
Provides tools for managing Wiki.js pages, users, and groups, plus RAG semantic search over wiki content.
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., "@wikijs-mcp-ragsearch the wiki for the RAG architecture overview and summarize it"
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.
wikijs-mcp-rag
MCP + RAG server for a private Wiki.js instance. Exposes the wiki's pages,
users and groups as MCP tools (CRUD) and adds a semantic search (RAG) layer over
the content: chunking + embeddings + vector index in SQLite (sqlite-vec) with
hybrid search (vector + lexical).
MCP transports:
POST /mcp(Streamable HTTP, stateless) andGET /sse+POST /message(SSE legacy).Auth: bearer token (
MCP_TOKEN) on all MCP endpoints.External endpoints: the Wiki.js instance and the embeddings server (llama.cpp) live outside the compose; they are configured via environment variables.
Architecture
┌────────────────────────────────────────────────┐
MCP client │ wikijs-mcp-rag (Fastify) │
(OpenCode, llama.cpp │ │
UI, ...) │ ┌─────────────────────────────────────────┐ │
│ │ │ McpServer (22 tools) │ │
│ HTTP │ │ ┌───────────────┐ ┌────────────┐ │ │
├────────────────►│ │ │ Tools CRUD │ │ Tools RAG │ │ │
│ /mcp (bearer) │ │ │ pages(12) │ │ rag_search │ │ │
│ /sse (bearer) │ │ │ users(4) │ │ rag_get_ │ │ │
│ │ │ │ groups(1) │ │ context │ │ │
│ │ │ │ + ping │ │ rag_index_ │ │ │
│ │ │ └───────┬───────┘ │ status │ │ │
│ │ │ │ │ rag_reindex│ │ │
│ │ │ │ └─────┬──────┘ │ │
│ │ └───────────┼────────────────────┼────────┘ │
│ │ │ │ │
│ │ ┌───────────▼──────────┐ ┌──────▼──────────┐ │
│ │ │ WikiClient │ │ RagDb │ │
│ │ │ (GraphQL, bearer) │ │ SQLite+sqlite-vec │
│ │ └───────────┬──────────┘ └──────┬──────────┘ │
│ │ │ ▲ │
▼ │ │ │ │
┌──────────────┐ │ ┌──────────▼──────────┐ ┌──────┴──────────┐ │
│ Wiki.js │◄────────┘ │ Indexer/Querier │─►│ EmbeddingsClient│ │
│ (external) │ │ chunk+embed+store │ │ (llama.cpp) │ │
└──────────────┘ └─────────────────────┘ └──────┬──────────┘ │
▼ │
┌──────────────────┐ │
│ Embeddings (ext) │ │
└──────────────────┘ │
│
Background sync: CRUD hooks (SyncService) + Poller (5 min) + nightly SchedulerRelated MCP server: WikiJS MCP Server
Available tools (22)
Infrastructure (1)
Tool | Description |
| Health check. Returns |
Pages — CRUD (12)
Tool | Description |
| Metadata of a page by |
| Markdown content of a page by |
| Lists pages ( |
| Full-text search over published pages. |
| Creates and publishes a page in Markdown. |
| Updates fields of an existing page. |
| Soft delete of a page. |
| Publishes a page ( |
| Permanent deletion (purge). Irreversible. |
| Status/metadata of a page. |
| The whole corpus in a single request. |
| Filters unpublished pages (optional |
Users (4)
Tool | Description |
| Lists all users. |
| Searches users by name or email. |
| Creates a local user. |
| Updates fields of a user. |
Groups (1)
Tool | Description |
| Lists all groups. |
RAG (4)
Tool | Description |
| Hybrid semantic search (vector + lexical) over the RAG index. |
| Assembled context block + |
| Index status: pages, chunks, dims and last indexed. |
| Re-indexes a specific page (rechunk + re-embed + store). |
Configuration
All variables are validated in src/config.ts with Zod.
Copy .env.example to .env and fill in the required values.
Variable | Required | Default | Description |
| no |
| Base URL of Wiki.js (actual endpoint |
| no |
| Wiki.js admin token. Optional: empty ⇒ the client omits the |
| no |
| Accepts the proxy's self-signed TLS certificate. |
| no | — | Base URL of the external embeddings server ( |
| no |
| API key (llama.cpp ignores it). |
| no |
| Model name (metadata). |
| no |
| Embedding dimensionality. Changing it = reindex everything. |
| yes* | — | Bearer token for |
| no |
|
|
| no |
| Listen host. |
| no |
| Listen port (published by Docker). |
| no |
| Path of the SQLite file (must be on a volume). |
| no |
| Poller interval (ms). |
| no |
| Local hour of the nightly reindex. |
| no |
| Enables/disables the nightly resync. |
| no |
|
|
| no |
| Target chunk size. |
| no |
| Overlap between chunks. |
| no |
| Default top-K of the RAG search. |
Index integrity: if the SQLite file exists with an
embedding_dimsdifferent fromEMBEDDINGS_DIM, the process does not start and logs it (vector spaces are never mixed). Changing model/dims ⇒ delete the DB or reindex.
How to run
Option A — Published Docker image (GHCR, recommended)
Multi-arch image (linux/amd64, linux/arm64), built on every push to main:
docker pull ghcr.io/ricardoeplaza/wikijs-mcp-rag:latestMinimal one-liner (standalone, no compose):
docker run -d --name wikijs-mcp-rag \
-p 8000:8000 \
-v wikijs-mcp-rag-data:/data \
-e MCP_TOKEN=change-me \
-e WIKIJS_BASE_URL=http://wiki:3000 \
ghcr.io/ricardoeplaza/wikijs-mcp-rag:latest
curl http://localhost:8000/health # => { "status":"ok" }Drop-in service for an existing Wiki.js docker-compose stack (add to the
same services: block; WIKIJS_BASE_URL uses the wiki's service name):
wikijs-mcp-rag:
image: ghcr.io/ricardoeplaza/wikijs-mcp-rag:latest
container_name: wikijs-mcp-rag
environment:
MCP_TOKEN: change-me # required (secure bearer)
WIKIJS_BASE_URL: http://wiki:3000 # service name of your wiki
WIKIJS_TOKEN: "" # only if the instance uses an API key
EMBEDDINGS_BASE_URL: http://embeddings:8071/v1 # optional; RAG tools disabled without it
ports:
- "8000:8000"
volumes:
- wikijs-mcp-rag-data:/data # SQLite persistence (chunks + vectors)
restart: unless-stopped
volumes:
wikijs-mcp-rag-data:Notes:
The container runs as the unprivileged
nodeuser; when it starts as root (default) the entrypoint fixes ownership of the/datavolume for you.Wiki.js and embeddings are external: make sure
WIKIJS_BASE_URLandEMBEDDINGS_BASE_URLpoint to services reachable from the container.The nightly resync uses the container's local time (UTC by default); set
-e TZ=Europe/Madridif you want it in your timezone.
Option B — Docker Compose from source
# 1. Configure the environment
cp .env.example .env
# -> fill in MCP_TOKEN (secure bearer); WIKIJS_TOKEN only if the instance uses an API key
# 2. Start the service (build + start)
docker compose up -d --build
# 3. Check health
curl http://localhost:8000/health # => { "status":"ok" }The SQLite database persists in
./data(volume mounted at/data,RAG_DB_PATH=/data/wikijs-rag.db).
Option C — Local development (no Docker)
npm install
cp .env.example .env # fill in the required values
npm run dev # tsx src/main.ts -> http://localhost:8000Other scripts: npm run build (emits dist/), npm start (node dist/main.js),
npm test, npm run lint, npm run typecheck.
Connecting an MCP client
Streamable HTTP — POST /mcp (bearer)
Example MCP client configuration (mcpServers format):
{
"mcpServers": {
"wikijs": {
"url": "http://localhost:8000/mcp",
"headers": {
"Authorization": "Bearer <MCP_TOKEN>"
}
}
}
}SSE — GET /sse (legacy option)
For clients that only support SSE, use the /sse endpoint. Since EventSource
cannot send headers, the token is accepted as a query parameter:
http://localhost:8000/sse?token=<MCP_TOKEN>(The client POSTs the messages to POST /message?sessionId=... with the same bearer.)
No token or wrong token →
401 { "error": "unauthorized" }.If
MCP_ALLOW_NOAUTH=trueandMCP_TOKENis empty → accepted without auth (development only).
RAG and synchronization
Indexing
Each page is indexed as follows: Markdown content → chunking (chunkMarkdown:
sections by H1/H2, greedy packing, overlap) → one batch call to
embeddings.embed() → storage in SQLite (pages + chunks + vectors in
chunks_vec via sqlite-vec). The hash sha256(content) is stored to skip
unchanged pages.
Hybrid search
rag_search / rag_get_context embed the query, retrieve candidates by KNN
vector search (sqlite-vec) and do a hybrid rerank per candidate:
score = alpha·vecSim + (1−alpha)·lexical, with alpha=0.7 by default. Returns
the top-K by descending score.
Sync mechanisms (3)
CRUD hooks (
SyncService, fire-and-forget): aftercreate_page/update_page/publish_page→reindexPage(id); afterdelete_page/force_delete_page→purgePage(id). They never block or break the tool response.Incremental poller (every
SYNC_POLL_INTERVAL_MS, 5 min by default): reconciles index vs wiki — reindexes pages whose hash changed and purges those that no longer exist.Nightly scheduler (at
NIGHTLY_RESYNC_HOUR, 03:00 by default): fullreindexAll()as a final-consistency safety net.
First-start reindex
On startup, main() calls createApp(config, { startLifecycle: true }), which
triggers poller.start(). This performs an immediate runOnce() (fire-and-forget) before
scheduling the interval. With an empty SQLite database, all wiki pages have a missing
hash → all of them are indexed in that first sweep. In other words, the initial reindex
is automatic: there is no need to invoke reindex_all manually; the poller's first runOnce()
indexes the whole corpus (and logs it). If you later change model/dims,
delete the DB (./data) or reindex with rag_reindex_page / a full reindex.
Tests
npm test # vitest run (unit + integration)
npm run test:watch # watch mode
npx vitest run --coverage # with coverageThe tests do not start timers or real traffic: the app is built with
startLifecycle:false and the dependencies (WikiClient, embeddings, RagDb :memory:) are mocked.
Project structure
src/
├── main.ts # bootstrap Fastify + lifecycle (poller/scheduler) + shutdown
├── config.ts # env variables (Zod) + loadConfig()
├── logger.ts # pino
├── server/
│ ├── mcp-server.ts # McpServer factory + ping + tools wiring
│ ├── http-transport.ts # POST /mcp (Streamable HTTP stateless)
│ ├── sse-transport.ts # GET /sse + POST /message
│ └── auth.ts # bearer middleware
├── tools/
│ ├── index.ts # registerAllTools (17 CRUD)
│ ├── pages.ts # 12 page tools
│ ├── users.ts # 4 user tools
│ ├── groups.ts # 1 group tool
│ └── rag.ts # 4 RAG tools
├── wiki/
│ ├── client.ts # WikiClient (GraphQL)
│ ├── queries.ts # GraphQL operations
│ └── types.ts # Zod schemas
└── rag/
├── db.ts # RagDb (SQLite + sqlite-vec)
├── embeddings.ts # EmbeddingsClient (llama.cpp, OpenAI-compatible)
├── chunker.ts # chunkMarkdown
├── indexer.ts # Indexer (indexPage / reindexAll / purgePage)
├── querier.ts # Querier (hybrid search + indexStatus)
├── sync.ts # SyncService (CRUD hooks fire-and-forget)
├── poller.ts # Poller (incremental resync)
└── scheduler.ts # Scheduler (nightly reindex)This server cannot be deployed
Maintenance
Related MCP Connectors
- hiveWikiOAuthai.hivewiki
Shared project wiki for AI agents: read and write pages, next actions, and activity logs over MCP.
- FlowdexOAuthdk.flowdex
Read and write your team's shared, AI-readable wiki from any MCP client.
Hosted markdown project wikis your team's AI assistants read, search, and update over MCP.
Make your knowledge agent-ready. One MCP endpoint, 5 connectors, 3 search modes.
Related MCP Servers
- FlicenseNot gradedqualityDmaintenanceAn MCP server for Wiki.js projects that enables full-text search, page retrieval, and page management capabilities. It allows LLMs to interact with wiki content through specialized tools for searching, listing, and creating pages.-
- AlicenseNot gradedqualityDmaintenanceAn MCP server that enables full management of WikiJS instances, supporting operations like page creation, searching, and updating. It also provides tools for knowledge graph exploration, content summarization, and retrieval of wiki statistics.9 npmMIT
- AlicenseNot gradedqualityCmaintenanceMCP server for Wiki.js integration, enabling AI assistants to create, read, update, delete, search, and move wiki pages via natural language.1MIT
- AlicenseNot gradedqualityAmaintenanceEnables MCP clients to search, read, list, create, update, and delete Wiki.js pages using a user's own Google Workspace identity, with Wiki.js enforcing all authorization.Apache 2.0