A self-hosted team wiki with a Rust backend, a real-time editor and first-class Confluence migration. Built for teams that need a new home when Confluence Data Center reaches its end of life (2029-03-28).
Writing and collaboration
- Real-time co-editing with live cursors (Yjs CRDT; the server speaks the y-websocket protocol via
yrs) - Rich editor: slash commands, drag handles, tables (merge, colours, sort, numbered rows, normal / wide / full width), task lists with assignees, due dates and reminders, callouts with custom icons, expand blocks, columns, text colours and highlights, code blocks with syntax highlighting, Mermaid diagrams, math, mentions, status lozenges, dates, decisions, table of contents, child pages, includes, embeds (YouTube, Vimeo, Loom, Figma, Miro, Google Docs) and link previews, file previews, links to any block, find & replace
- Drawings: Excalidraw sketches and draw.io diagrams stored in the page; image annotation, text wrap around images
- Paste anything: Markdown becomes formatted content, spreadsheet cells become tables, images upload on paste or drop
- Drafts and history: private drafts until published, scheduled publishing, minor changes, version history with word-by-word comparison and restore, "changed since your last visit" highlights
- Templates and blueprints: built-in and custom templates with variables, a default template per space or for the pages below a page, blueprints that file pages under an index page
- Comments: page and inline comments, threads, images, resolve/reopen, mentions, likes
Organising and finding
- Spaces: page trees with folders and links, trash, archive, copy and move, blog with Atom feeds, space templates, categories and a space directory, personal spaces, shortcuts, an accent colour per space, key renames that keep old links working
- Labels on pages, blog posts and files; page properties (typed fields) with properties reports; live reports for content by label, recently updated pages, tasks and decisions
- Search: full text with snippets, inside attachments (PDF, Office, text), filters, saved searches, a query language (CQL:
space = ENG and label = runbook and lastModified > now("-30d")) and browser search integration - People: profiles with photos, a people directory, following people's activity, who viewed a page
- Notifications: watch pages, page trees or spaces; in-app and e-mail digests; "My tasks"
Permissions and security
- Workspace roles, space roles with Confluence's permission set (add/delete pages, blog posts, comments and files, restrictions, export, archive, delete own content) per user, group and the space default
- Page view/edit restrictions (view restrictions are inherited, as in Confluence), Inspect permissions, access requests, anonymous (public) spaces, guests, public share links with expiry, password and subtree
- Sign-in: local accounts, OpenID Connect (Microsoft Entra ID, Google, GitLab, Okta, Keycloak, Authentik, …), GitHub, LDAP / Active Directory and SAML 2.0, several at once, with group sync; two-factor authentication (TOTP and recovery codes, can be required)
- Audit log, personal API tokens (read-only or read/write, expiring), virus scanning with ClamAV, strict CSP and HSTS
Moving in and out
- Confluence import: classic XML exports and Cloud CSV exports with history, attachments, comments (incl. inline), labels, restrictions and space permissions, authors, statuses, drafts and trash, watches and favourites, 50+ macros mapped, links rewritten, a dry-run report, and update imports that bring later changes across. Old Confluence URLs (
/display/KEY/Title,viewpage.action?pageId=, Cloud/wiki/spaces/…/pages/ID,/x/tiny links) redirect to the imported pages - Other imports: Markdown / Obsidian, Notion (databases become page properties), HTML (incl. Confluence's HTML export) and Word (.docx)
- Export: pages and page trees as PDF (typeset on the server: cover, contents, headers, page numbers), Word, Markdown or HTML; whole spaces as a browsable static site, a Markdown folder tree or a full backup that restores into a new space
Integrations and AI
- REST API with an OpenAPI 3.1 description, webhooks (JSON, Slack / Mattermost, Microsoft Teams, Google Chat)
- Built-in MCP server: Claude & co. search, query, read and write pages with the user's permissions
- Retrieval API for RAG: the passages that best answer a question (sections of pages and attached files, cut along headings), hybrid keyword + semantic ranking, permission-filtered, each with a link to the exact block; semantic part optional with any OpenAI-compatible embeddings API (OpenAI, Ollama, vLLM, Voyage, …) and pgvector
- Ask the wiki: answers from the pages a person can see, with sources (optional, needs an Anthropic API key)
Interface and administration
- English, German and French (translating); light, dark and high-contrast themes; mobile layout; checked against WCAG 2.2 AA
- Workspace name, logo, accent colour, date format, time zone and default language; announcement banner; feature switches; attachment limits and blocked file types; usage overview; space administration
Documentation: user guide · admin guide · leaving Confluence Data Center · translating · overview deck (PowerPoint)
export WIKICRAFT_DB_PASSWORD=$(openssl rand -hex 16) # else the database password is "change-me"
docker compose up -d # builds the image on first start (takes a while)
open http://localhost:8787 # the first visit runs the setup wizardWikiCraft runs as a single stateless binary. Pick the mode that fits:
| Single instance (default) | Scaled (several replicas) | |
|---|---|---|
| Start with | docker compose up -d |
deploy/kubernetes (kubectl apply -k) or deploy/compose/docker-compose.scale.yml |
| Database | PostgreSQL | PostgreSQL (managed/HA recommended) |
| Attachments & import uploads | local volume (/data) |
S3-compatible: WIKICRAFT_S3_BUCKET, _ENDPOINT, _REGION, _ACCESS_KEY_ID, _SECRET_ACCESS_KEY |
| Search | WIKICRAFT_SEARCH=tantivy (embedded) |
WIKICRAFT_SEARCH=postgres (shared full-text index) |
| Live collaboration | in-process | WIKICRAFT_CLUSTER=postgres (replicas relay edits via LISTEN/NOTIFY; no sticky sessions needed) |
| Background jobs | Postgres queue | same queue; any replica claims jobs, heartbeats, crashed jobs are detected |
| Sessions, SSO state, login throttling | PostgreSQL | PostgreSQL |
Nothing else is kept on local disk in scaled mode: /data only holds import scratch space
(an emptyDir on Kubernetes), and the container runs as non-root with a read-only root filesystem.
Operations
- Logs: stdout only;
WIKICRAFT_LOG_FORMAT=jsonfor log shippers (Loki, ELK, Datadog). Every request gets anx-request-id(accepted from your proxy or generated), included in the logs. Levels viaRUST_LOG(e.g.info,wikicraft=debug). - Probes: liveness
GET /api/health, readinessGET /api/ready(503 while shutting down or when the database is unreachable);wikicraft healthcheckfor containerHEALTHCHECKs. - Metrics: Prometheus text format at
GET /api/metrics(HTTP responses, latency, WebSocket connections, live rooms, DB pool, jobs); protect withWIKICRAFT_METRICS_TOKEN. - Shutdown: SIGTERM marks the pod not-ready, drains and flushes live documents.
- Migrations run automatically on startup and are serialized by a Postgres advisory lock, so rolling updates with several pods are safe.
- Fonts for PDF export: Noto Sans is built in; for other scripts (CJK, Arabic, …) install fonts
in the image or put
.ttf/.otffiles in<data dir>/fonts.
Requirements: Rust ≥ 1.94, Node 20.19+ or 22.12+, PostgreSQL ≥ 15 (or Docker).
# Postgres for development (port 55432)
docker run -d --name wikicraft-pg -p 127.0.0.1:55432:5432 \
-e POSTGRES_USER=wikicraft -e POSTGRES_PASSWORD=wikicraft -e POSTGRES_DB=wikicraft pgvector/pgvector:pg17
# frontend
cd web && npm install && npm run build && cd ..
# backend (serves web/dist, applies migrations automatically)
cargo run -p wikicraft -- serve # http://127.0.0.1:8787
# a single binary: release builds embed web/dist as it is at build time
(cd web && npm run build) && cargo build --release -p wikicraft
./target/release/wikicraft serve # no web/ directory needed next to it
# frontend dev server with hot reload (proxies /api to the backend)
cd web && npm run dev # http://127.0.0.1:5199Interface texts go through t() with the English text as the key; node web/scripts/i18n.mjs check de fr lists translations that are missing (see translating).
cargo test # unit + integration tests (needs the dev Postgres)
./research/confluence-import/fetch_xwiki_corpus.sh # XWiki's Confluence test corpus (pinned), for the importer regression tests
./research/confluence-import/fetch_fixtures.sh # optional: real Confluence exports
cargo test -p wikicraft-confluence --release --test real_exports -- --nocaptureIntegration tests create a throwaway database per test, boot the full app on an ephemeral port and exercise it over HTTP and WebSockets (auth/CSRF, permissions, collaboration between two Yjs peers, both Confluence export formats end-to-end, exports, security regressions).
e2e/ drives Chromium through one journey on a fresh database (24 steps): setup, rich content,
two people editing live, comments and search, public links, exports, two-factor sign-in, templates,
draw.io, history, page properties and reports, the workspace look, and accessibility checks
(axe, WCAG 2.2 AA; the high-contrast theme at 7:1).
cd web && npm run build && cd ../e2e
npm ci && npx playwright install chromium
npx playwright test # starts the server on :8799 with a fresh database (scripts/e2e-server.sh)
E2E_BASE_URL=http://127.0.0.1:8799 npx playwright test # against a server you started yourselfwikicraft serve # web server (default)
wikicraft migrate # apply migrations
wikicraft analyze-confluence export.zip # dry run: JSON report, nothing written
wikicraft import-confluence export.zip --as-user admin@example.com [--space-key KEY] [--skip-history] [--skip-drafts] [--skip-trash] [--update [--overwrite-local-changes]]
wikicraft reindex # rebuild the search index
wikicraft backup wikicraft.zip # whole installation: database snapshot + files
wikicraft restore wikicraft.zip # into an empty database (also to move servers / to S3)
wikicraft healthcheck # probe a running server's /api/healthAll settings are flags or environment variables:
| Variable | Default | Meaning |
|---|---|---|
WIKICRAFT_DATABASE_URL |
postgres://wikicraft:wikicraft@127.0.0.1:55432/wikicraft |
PostgreSQL |
WIKICRAFT_BIND |
127.0.0.1:8787 |
listen address |
WIKICRAFT_BASE_URL |
http://127.0.0.1:8787 |
public URL |
WIKICRAFT_DATA_DIR |
./data |
search index, uploads, local attachment storage, extra fonts (fonts/) |
WIKICRAFT_STATIC_DIR |
./web/dist |
built frontend; when it holds no build, the copy embedded in the binary is served |
WIKICRAFT_S3_BUCKET / WIKICRAFT_S3_ENDPOINT |
— | store attachments and uploads in S3/MinIO/R2/Ceph |
WIKICRAFT_S3_REGION / WIKICRAFT_S3_ACCESS_KEY_ID / WIKICRAFT_S3_SECRET_ACCESS_KEY |
AWS_* env |
S3 credentials |
WIKICRAFT_SEARCH |
tantivy |
tantivy (embedded) or postgres (shared, for several replicas) |
WIKICRAFT_SEARCH_LANGUAGE |
auto |
language whose word forms search matches (auto: the workspace's default language; en, de, fr, es, it, nl, pt, … or none); tantivy only |
WIKICRAFT_CLUSTER |
none |
postgres to run several replicas (live-edit relay) |
WIKICRAFT_LOG_FORMAT |
text |
json for structured logs |
WIKICRAFT_METRICS_TOKEN |
— | bearer token for /api/metrics |
WIKICRAFT_MAX_UPLOAD_MB |
2048 |
max upload (attachments, imports) |
WIKICRAFT_CLAMAV |
— | virus scanning with ClamAV: tcp://clamd:3310 or unix:///run/clamav/clamd.ctl; infected uploads are refused, imports leave infected files out |
WIKICRAFT_CLAMAV_FAIL_OPEN |
false |
accept files when clamd can't be reached (default: refuse them) |
WIKICRAFT_SECURE_COOKIES |
false |
set true behind HTTPS (an https:// base URL also turns on HSTS) |
WIKICRAFT_SESSION_DAYS |
30 |
session lifetime |
WIKICRAFT_SESSION_IDLE_MINUTES |
0 (off) |
sign out sessions inactive this long |
WIKICRAFT_AUTH_PROVIDERS / _FILE |
— | sign-in providers (JSON list), see below |
WIKICRAFT_GOOGLE_CLIENT_ID / _SECRET / _ALLOWED_DOMAINS |
— | "Continue with Google" shortcut |
WIKICRAFT_MICROSOFT_CLIENT_ID / _SECRET / _TENANT / _ALLOWED_DOMAINS |
— | "Continue with Microsoft" (Entra ID) shortcut |
WIKICRAFT_OIDC_ISSUER / _CLIENT_ID / _CLIENT_SECRET / _NAME / _AUTO_CREATE |
— | single generic OIDC provider (redirect URI {BASE_URL}/api/auth/oidc/callback) |
WIKICRAFT_DISABLE_PASSWORD_LOGIN |
false |
turn off local passwords (SSO / LDAP only) |
WIKICRAFT_SMTP_URL |
— | outgoing mail: smtps://user:pass@host (TLS), smtp://user:pass@host:587?tls=required (STARTTLS) or smtp://host:25 (plain, local relays); also turns on Forgot password? |
WIKICRAFT_MAIL_FROM |
WikiCraft <wikicraft@localhost> |
sender address |
WIKICRAFT_WEBHOOK_ALLOW_PRIVATE |
false |
let webhooks call private / loopback addresses |
WIKICRAFT_UNFURL_ALLOW_PRIVATE |
false |
let link previews fetch private / loopback addresses (closed intranets only) |
WIKICRAFT_AI_API_KEY |
— | Anthropic API key: turns on Ask the wiki (off when unset; nothing leaves the server) |
WIKICRAFT_AI_MODEL / WIKICRAFT_AI_BASE_URL |
claude-opus-5-5 / https://api.anthropic.com |
model and API endpoint (e.g. a gateway) for Ask the wiki |
WIKICRAFT_EMBEDDINGS_URL / WIKICRAFT_EMBEDDINGS_MODEL |
— | OpenAI-compatible embeddings API and model: turns on semantic retrieval (e.g. https://api.openai.com/v1 + text-embedding-3-small, or Ollama's http://localhost:11434/v1 + nomic-embed-text); needs pgvector ≥ 0.7. Off when unset: keyword retrieval only, nothing leaves the server |
WIKICRAFT_EMBEDDINGS_API_KEY |
— | bearer token for the embeddings API |
WIKICRAFT_EMBEDDINGS_DIMENSIONS |
— | ask for shorter vectors (models that support it, like text-embedding-3-*); at most 4000 |
WIKICRAFT_EMBEDDINGS_QUERY_PREFIX / …_DOCUMENT_PREFIX |
— | text put before questions / passages for models trained with prefixes (nomic-embed-text: search_query: / search_document: ) |
Local accounts (e-mail + password) work out of the box. Any number of external providers can be
added; the login page shows a button per provider, and LDAP accounts use the normal password form
("E-mail or username"). WIKICRAFT_AUTH_PROVIDERS (or a mounted file via
WIKICRAFT_AUTH_PROVIDERS_FILE) takes a JSON list:
[
{"type": "oidc", "id": "microsoft", "preset": "microsoft", "tenant": "<tenant-id>",
"clientId": "…", "clientSecret": "…", "groupsClaim": "groups", "syncGroups": true},
{"type": "oidc", "id": "google", "preset": "google", "clientId": "…", "clientSecret": "…",
"allowedDomains": ["acme.com"]},
{"type": "oidc", "id": "keycloak", "name": "Acme SSO", "issuer": "https://sso.acme.com/realms/acme",
"clientId": "wikicraft", "clientSecret": "…"},
{"type": "github", "id": "github", "clientId": "…", "clientSecret": "…", "allowedOrgs": ["acme"]},
{"type": "ldap", "id": "ad", "name": "ACME directory", "url": "ldaps://dc1.acme.local",
"bindDn": "CN=wikicraft,OU=Service,DC=acme,DC=local", "bindPassword": "…",
"baseDn": "DC=acme,DC=local", "syncGroups": true},
{"type": "saml", "id": "okta", "name": "Okta", "idpMetadataUrl": "https://acme.okta.com/app/…/sso/saml/metadata"}
]| Type | Covers | Notes |
|---|---|---|
oidc |
Microsoft Entra ID (preset: microsoft + tenant), Google (preset: google), GitLab (preset: gitlab), Okta, Keycloak, Authentik, Auth0, Zitadel, … (issuer) |
authorization code + PKCE + nonce; redirect URI {BASE_URL}/api/auth/sso/{id}/callback |
github |
GitHub and GitHub Enterprise (apiUrl, webUrl) |
verified primary e-mail; allowedOrgs restricts sign-in to org members |
ldap |
OpenLDAP, Active Directory, FreeIPA, lldap, … | search with a service account (bindDn), then bind as the user; startTls; default filter matches uid, mail, sAMAccountName, userPrincipalName (userFilter to override, {username} is escaped) |
saml |
Okta, Entra ID, ADFS, Keycloak, Shibboleth, OneLogin, … | build feature saml (on in the Docker image); SP metadata at {BASE_URL}/api/auth/sso/{id}/metadata, ACS {BASE_URL}/api/auth/sso/{id}/acs; signed responses only (no SHA-1), SP-initiated only |
Common options: name (button label), allowedDomains (only these e-mail domains),
trustEmail (use e-mail addresses the provider doesn't mark as verified — default on for LDAP,
SAML and single-tenant Microsoft, off otherwise; true also allows linking existing admin
accounts),
autoCreate (create accounts on first login; default on for private providers and for public
ones restricted by allowedDomains / allowedOrgs, off for unrestricted Google/GitHub), defaultRole (member /
guest), syncGroups (mirror the provider's groups into WikiCraft groups; a name already used by a
local group gets a "(provider)" suffix instead of taking that group over).
Accounts are linked by the provider's stable subject id; on first login an existing account with the same verified e-mail (including authors imported from Confluence) is claimed. Deactivated users can't sign in through any provider. Workspaces can require two-factor authentication for admins or for everyone; it then applies after SSO sign-ins too.
web/ React 19 + Tiptap 3 (ProseMirror) + Yjs, TanStack Query; translations in src/locales
crates/server axum 0.8 HTTP API + WebSocket collaboration (yrs), sqlx/PostgreSQL,
tantivy search, object_store blobs, CQL, importers, Word and PDF (Typst)
exports, MCP server, CLI → single binary
crates/doc-model ProseMirror-compatible document model: Yjs ⇄ JSON (y-prosemirror format),
Markdown ⇄ JSON, safe HTML rendering, plain text
crates/confluence Confluence export readers (entities.xml streaming, Cloud CSV),
storage-format → ProseMirror converter (50+ macros), import planner
Documents are stored as Yjs state (for collaboration) plus derived ProseMirror JSON and plain text (for API, search, exports). Every block/inline type the importer produces exists in the editor schema, so imported content stays fully editable.
See PLAN.md for the feature roadmap and competitive analysis, and
research/confluence-import/ for the Confluence export analysis.
All endpoints live under /api/v1 (integrations should use this versioned path; /api is the
same API without the version, used by the web app). Authenticate with the session cookie (browser; mutating requests
must send x-wikicraft: 1) or with a personal token: Authorization: Bearer wct_…
(create one via POST /api/me/tokens). The full OpenAPI 3.1 description is served at
/api/openapi.yaml (generated by scripts/gen-openapi.py; a unit test fails when a route is
missing from it) — load it into Swagger UI, Redoc, Postman or a client generator. The main
endpoints:
| Area | Endpoints |
|---|---|
| Auth | GET/POST /setup, POST /auth/login, POST /auth/logout, GET /me, GET/POST /me/tokens |
| Spaces | GET/POST /spaces, GET/PATCH/DELETE /spaces/{key}, GET /spaces/{key}/tree, GET/PUT /spaces/{key}/permissions (roles and the permissions each grant leaves out), GET /spaces/{key}/trash |
| Pages | POST /pages, GET/PATCH/DELETE /pages/{id}, PUT /pages/{id}/content (ProseMirror JSON or Markdown), POST /pages/{id}/move, /copy, /restore, GET /pages/{id}/children, GET/POST /pages/{id}/versions, POST /pages/{id}/versions/{n}/restore, GET/PUT /pages/{id}/restrictions, GET/PUT /pages/{id}/labels, GET/PUT /pages/{id}/properties, GET /pages/{id}/export?format=md|html|docx|pdf|json (tree, toc, cover), GET /pages/{id}/collab (WebSocket) |
| Comments | GET/POST /pages/{id}/comments, PATCH/DELETE /comments/{id} |
| Attachments | GET/POST /pages/{id}/attachments, GET /attachments/{id}/content, PUT /attachments/{id}/labels, DELETE /attachments/{id} |
| Discovery | GET /search?q=&space=, GET /cql?cql=&space= (query language), GET/POST /retrieve (passages for RAG, below), GET /recent, GET /favorites, GET /labels/{name}, GET /labels/{name}/attachments, GET /resolve?space=&title= / ?source=confluence:<id> |
| Reports | GET /reports/pages?labels=&spaces=&cql=&sort=&max=, GET /reports/properties?labels=&where=, GET /reports/tasks?spaces=&status=, GET /reports/decisions, GET /tasks (mine) |
| Sharing | GET/POST /pages/{id}/share-links, DELETE /share-links/{id}, public: GET /public/{token}[/pages/{id}], GET /public/{token}/attachments/{id} |
| Import / export | POST /imports/confluence (multipart file, options), POST /imports/confluence/analyze, POST /imports/markdown, POST /imports/backup, POST /spaces/{key}/export {format: html|markdown|backup}, GET /jobs/{id}, GET /jobs/{id}/download |
| Account | POST /auth/forgot, POST /auth/reset, GET /auth/invite, POST /auth/accept-invite, GET/PUT /me/preferences, PUT /me/profile (incl. language), GET /auth/providers |
| Admin | GET/POST /users, POST /users/invite, PATCH /users/{id}, GET/POST /groups, group members, PUT /admin/workspace (name, look, date format, time zone, default language), GET /admin/audit, GET /admin/stats, POST /admin/reindex, GET/PUT /admin/settings, GET /admin/mail, POST /admin/mail/test, webhooks (below) |
Admins register endpoints under Admin → Webhooks (or POST /api/admin/webhooks {url, events, spaceKey}).
Events: page.created, page.updated (new version), page.moved, page.trashed, page.restored,
comment.created, attachment.created, space.created (plus ping from "Send test"). Each
delivery is a JSON POST:
{"event": "page.created", "timestamp": "…", "webhookId": "…", "deliveryId": "…",
"data": {"page": {"id": "…", "title": "…", "version": 1, "url": "https://wiki/pages/…"},
"space": {"key": "ENG", "name": "Engineering"}, "actor": {"name": "…", "email": "…"}}}Verify X-WikiCraft-Signature: sha256=<hex HMAC-SHA256(body, secret)>. Non-2xx answers are
retried with exponential backoff (8 attempts). Payloads carry metadata only, never page content.
Targets on private/loopback addresses are refused unless WIKICRAFT_WEBHOOK_ALLOW_PRIVATE=true.
Chat channels: pick Slack or Mattermost, Microsoft Teams (Workflows) or Google Chat as
the format ("format": "slack" | "teams" | "googlechat") and paste the channel's incoming-webhook
URL; events then arrive as readable messages ("Ann updated Q3 plan in Finance") with a link.
Other AI applications can use the wiki as their knowledge source: GET /api/retrieve?q=… (or
POST /api/retrieve {"query": …}) returns the passages that best answer a question, taken only
from pages the caller may open (API token or session; read-only tokens work for both).
curl -H "Authorization: Bearer wct_…" "https://your-wiki/api/retrieve?q=How+do+I+rotate+the+VPN+certificates&space=IT&limit=5"{"mode": "hybrid", "passages": [{
"pageId": "…", "title": "VPN guide", "spaceKey": "IT", "spaceName": "IT", "path": ["Infrastructure"],
"headings": ["Operations", "Certificates"], "blockId": "x1y2z3", "url": "https://your-wiki/pages/…#block-x1y2z3",
"text": "## Certificates\n\nCertificates expire after 90 days. Rotate them with …", "labels": ["vpn"],
"updatedAt": "…", "version": 12, "attachment": null, "score": 0.0325, "matchedBy": ["keyword", "semantic"]}]}- Passages follow the document's structure: a new one starts at every heading, long sections
are cut between blocks (~1200 characters), big tables and code blocks between lines (repeating
the table header / code fence). Each passage is Markdown, knows its heading path and links to
the block it starts at. The extracted text of attached files (PDF, Word, text, …) adds
passages of its own (
attachmentsays which file). Passages are cut again whenever a page, its labels or its attachments change. - Ranking: keyword retrieval (the full-text search picks candidate pages, BM25 picks their
passages) and, when configured, semantic retrieval (nearest passage vectors) are fused by
reciprocal rank fusion.
mode:auto(default: hybrid when semantic retrieval is available),hybrid,keywordorsemantic;noteexplains when semantic retrieval was asked for but couldn't run. - Parameters:
space(keys, comma-separated),label(pages with any of them),limit(default 8, at most 50),perPage(passages per page, default 3),maxChars(character budget for the passage texts). - Semantic retrieval needs
WIKICRAFT_EMBEDDINGS_URL+WIKICRAFT_EMBEDDINGS_MODEL(any OpenAI-compatible/embeddingsendpoint) and the pgvector extension (≥ 0.7; the Docker Compose file uses thepgvector/pgvectorimage; elsewhere runCREATE EXTENSION vector;once as a superuser). A background worker embeds every passage, then only passages whose text changed; vectors are stored ashalfvecwith an HNSW index, and changing the model embeds everything again. Page text goes to the embeddings API, so pick a local model (Ollama) if it must stay in-house. Admin → Overview shows progress and errors. - Keeping an external vector store in sync instead? Export spaces as Markdown
(
POST /spaces/{key}/export) and follow the webhooks. Content copied out of the wiki no longer follows its permissions, though; the retrieval API does.
Create a personal API token (profile menu → Profile, API tokens & AI) and point any MCP client
at https://your-wiki/api/mcp:
claude mcp add --transport http wikicraft https://your-wiki/api/mcp --header "Authorization: Bearer wct_…"Tools: retrieve (the passages that best answer a question, with URLs to cite), search, query_pages (CQL), get_page (Markdown), list_spaces, list_children,
recent_changes, create_page, update_page, append_to_page, add_comment. Every call runs
through the same authorization checks as the UI. Give an assistant that should only read a
read-only token ({"scope": "read"} when creating it): the writing tools then refuse.
With WIKICRAFT_AI_API_KEY set, the search page offers Ask the wiki: the question goes through
the retrieval above (hybrid when embeddings are configured), the best passages are sent to
Claude, and the answer comes back with numbered citations linking to the exact sections. Only
pages the asker can open are ever sent; page text is passed as data, not instructions. Questions
are rate limited per person (20 per 10 minutes).
Licensed under either of
- Apache License, Version 2.0 (LICENSE-APACHE)
- MIT license (LICENSE-MIT)
at your option. Third-party components and their licenses are listed in
THIRD-PARTY-LICENSES.md (scripts/third-party-licenses.py; the Docker
image carries them in /usr/share/doc/wikicraft).
Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions.
