An offline, SEO-and-LLM-friendly ZIM archive server. Pure Go implementation designed for serving compressed offline content with real article search and a JSON API for LLM tool use.
ZimContext is a Go server that reads ZIM archives and serves their content over HTTP with server-side rendering. It gives search engines, LLMs, and humans direct access to the articles inside ZIM files without any client-side JavaScript framework.
Natively compiled Go with efficient memory management and concurrent request handling.
Server-side rendering delivers complete HTML to search engines and bots.
JSON search API lets LLMs search and retrieve articles from offline documentation.
No internet required at runtime. All content comes from local ZIM archives.
ZIM (Zeno IMproved) is an open file format from OpenZIM designed for storing web content offline. A single ZIM file can hold an entire website, including HTML, images, CSS, and JavaScript, compressed into a portable archive.
Common ZIM archives include Wikipedia, Stack Overflow, Project Gutenberg, DevDocs (React, Node.js, Python, etc.), and many other knowledge bases. You can download ZIM files from the Kiwix Library.
ZimContext turns your ZIM archives into a search tool that any LLM can use. Point the LLM at the /zimcontext/api/search endpoint and it can:
This is especially useful for giving LLMs access to up-to-date documentation that may be beyond their training cutoff, or for grounding responses in authoritative offline sources.
GET /zimcontext/api/archivesList all loaded ZIM archives with metadata. Useful for discovering archive keys to pass to /zimcontext/api/search?archive=.
| Parameter | Type | Default | Description |
|---|---|---|---|
page | int | 1 | Page number (1-indexed) |
limit | int | 20 | Results per page |
Example request:
GET /zimcontext/api/archives?page=1&limit=5
Example response:
{
"page": 1,
"limit": 5,
"total": 13,
"archives": [
{
"key": "devdocs_en_react_2025-07",
"title": "React",
"description": "React documentation",
"language": "en",
"article_count": 136,
"size": 37748736,
"date": "2025-07"
}
]
}
GET /zimcontext/api/searchSearch articles across all loaded ZIM archives. Returns JSON.
| Parameter | Type | Default | Description |
|---|---|---|---|
q | string | (required) | Search query |
archive | string | (all) | Filter by archive key (filename stem) |
limit | int | 20 | Maximum number of results |
offset | int | 0 | Skip first N results (for pagination) |
Example request:
GET /zimcontext/api/search?q=useEffect&archive=devdocs_en_react_2025-07&limit=5&offset=0
Example response:
{
"query": "useEffect",
"archive": "devdocs_en_react_2025-07",
"total": 12,
"offset": 0,
"results": [
{
"title": "useEffect",
"archive": "devdocs_en_react_2025-07",
"path": "reference/react/useEffect.html",
"url": "/zimcontext/archive/devdocs_en_react_2025-07/reference/react/useEffect.html",
"snippet": "...useEffect is a React Hook that lets you synchronize a component with an external system..."
}
]
}
Copy-paste this tool definition into your LLM configuration to enable ZimContext search:
{
"type": "function",
"function": {
"name": "search_zimcontext",
"description": "Search articles in offline ZIM archives (documentation, Wikipedia, etc.) hosted by ZimContext. Returns titles, snippets, and URLs.",
"parameters": {
"type": "object",
"properties": {
"query": {
"type": "string",
"description": "Search query to find articles by title"
},
"archive": {
"type": "string",
"description": "Optional: filter by archive key (e.g. 'devdocs_en_react_2025-07'). Omit to search all archives."
},
"limit": {
"type": "integer",
"description": "Maximum results to return (default 20)"
}
},
"required": ["query"]
}
}
}
List archives tool definition:
{
"type": "function",
"function": {
"name": "list_zimcontext_archives",
"description": "List all ZIM archives loaded in ZimContext. Returns archive keys, titles, and metadata. Use this to discover available archives before searching.",
"parameters": {
"type": "object",
"properties": {
"page": {
"type": "integer",
"description": "Page number, 1-indexed (default 1)"
},
"limit": {
"type": "integer",
"description": "Results per page (default 20)"
}
},
"required": []
}
}
}
ZimContext exposes an MCP (Model Context Protocol) endpoint at /zimcontext/mcp. This lets LLM tools like Cursor, Claude Code, and other MCP clients discover and call ZimContext tools natively — no manual tool definitions needed.
Add a remote MCP server in Cursor Settings > MCP Servers. Paste:
{
"mcpServers": {
"zimcontext": {
"url": "http://demo.carefuldream.com/zimcontext/mcp"
}
}
}
Run this command to register ZimContext as an MCP server:
claude mcp add zimcontext --transport http http://demo.carefuldream.com/zimcontext/mcp
Point any MCP Streamable HTTP client at:
POST http://demo.carefuldream.com/zimcontext/mcp
Content-Type: application/json
The server exposes three tools: list_archives, search_docs, and get_doc. Clients discover them automatically via the tools/list method.
ZimContext can be used as a search engine in SearXNG, a self-hosted metasearch engine. This lets you search your ZIM archives alongside public search engines, with your local documents prioritized in the results.
SearXNG's built-in json_engine can query ZimContext's search API directly. No custom Python code is needed — just add a configuration block to your SearXNG settings.yml. Set the weight higher than other engines so your local documents rank first.
Add this to the engines: section of your SearXNG settings.yml. Replace the search_url with the address where SearXNG can reach ZimContext.
If SearXNG reaches ZimContext through its public URL:
engines:
- name: ZimContext
engine: json_engine
shortcut: zim
search_url: http://demo.carefuldream.com/zimcontext/api/search?q={query}&limit=10&offset={pageno}
results_query: results
url_query: url
title_query: title
content_query: snippet
categories: [general]
paging: true
page_size: 10
first_page_num: 0
disabled: false
weight: 3
timeout: 5.0
If SearXNG and ZimContext run on the same Docker network, use the container name directly. Add enable_http: true since the connection is plain HTTP:
engines:
- name: ZimContext
engine: json_engine
shortcut: zim
search_url: http://zimcontext:8080/api/search?q={query}&limit=10&offset={pageno}
results_query: results
url_query: url
title_query: title
content_query: snippet
categories: [general]
paging: true
page_size: 10
first_page_num: 0
disabled: false
weight: 3
timeout: 5.0
enable_http: true
| Setting | Value | Purpose |
|---|---|---|
engine | json_engine | SearXNG's built-in generic JSON engine |
search_url | ZimContext API URL | Use your public HTTPS URL or Docker internal address |
results_query | results | JSON path to the results array |
url_query | url | JSON field containing the article URL |
title_query | title | JSON field containing the article title |
content_query | snippet | JSON field containing the text snippet |
weight | 3 | Multiplier for result ranking. Higher values = results rank above other engines. Default for most engines is 1. |
page_size | 10 | Controls how {pageno} is calculated. Must match the limit value in the URL. |
first_page_num | 0 | Offset starts at 0 (not 1) |
timeout | 5.0 | Seconds to wait for ZimContext response |
enable_http | true | Required when search_url uses plain HTTP. Omit for HTTPS. |
You can also add separate SearXNG engines for specific archives. This lets users filter by source in the SearXNG interface:
- name: Wikipedia (ZimContext)
engine: json_engine
shortcut: zimwiki
search_url: http://demo.carefuldream.com/zimcontext/api/search?q={query}&archive=wikipedia_en_all&limit=10&offset={pageno}
results_query: results
url_query: url
title_query: title
content_query: snippet
categories: [general]
paging: true
page_size: 10
first_page_num: 0
disabled: false
weight: 3
timeout: 5.0
Use the archive keys from the archives API as the archive parameter value.
Run SearXNG and ZimContext together on the same Docker network:
services:
searxng:
image: searxng/searxng:latest
ports:
- "8888:8080"
volumes:
- ./searxng/settings.yml:/etc/searxng/settings.yml
depends_on:
- zimcontext
zimcontext:
image: zimcontext:latest
volumes:
- ./library:/app/library
- ./data:/app/data
environment:
- BASE_PATH=
- PORT=8080
In this setup, use http://zimcontext:8080/api/search?q={query}&limit=10&offset={pageno} as the search_url with enable_http: true, since both containers communicate over the internal Docker network.
See the Archives page for the list of currently loaded ZIM files.
Added new ZIM files? Scan the directory to pick them up without restarting the server.