Troubleshooting
Common issues, error messages, and solutions for OpenZIM MCP.
Notation: examples on this page use Python pseudo-call syntax (
zim_health(),zim_search(query="...", mode="...")). The MCP wire format is JSON-RPC{"name": "...", "arguments": {...}}— your MCP client handles the framing. Tool names match the 8-tool advanced surface (zim_query,zim_search,zim_get,zim_get_section,zim_browse,zim_metadata,zim_links,zim_health).
Error response shape#
Before diving into specific failure modes — v2 changed how errors come back from tools. Every tool catches exceptions and returns a structured ToolErrorPayload instead of raising — except zim_query, whose guidance failures (no archive specified, an empty query/topic/search term, a meta-only or chained query) return markdown guidance and still carry isError: false:
{
"error": true,
"operation": "invalid_path_combination",
"message": "Provide one of `entry_path`, `entry_paths`, or `main_page=True`."
}
Check for it with isinstance(result, dict) and result.get("error") is True. Since v2.6 the server also sets the MCP isError flag on these — EnvelopeAwareMCPServer.call_tool recognises the envelope on the way out and returns a CallToolResult with isError=True — so a client that branches on isError works too. No tool advertises an outputSchema, so nothing arrives in structuredContent; the envelope is JSON in the response’s text content block. Common error operation values you’ll see in this guide:
invalid_path_combination—zim_getgot an impossible branch:entry_path+entry_paths,entry_paths+ a non-fullview,entry_paths+content_offset,binary+entry_paths/main_page/a non-fullview,binarywithoutentry_path,main_page+ a path or a non-fullview, or no selector at allinvalid_mode—zim_browsegot amodeoutside{page, walk}. This is the only enum rejection that reaches you under its own name, becausezim_browse.modeis deliberately typedstrwith the enum attached as schema metadata so the handler gets to answer.invalid_argument— every other out-of-range enum value.zim_get.view,zim_search.mode, andzim_links.direction/kindareLiteral-typed, so pydantic rejects the call before the tool body runs and you getoperation="invalid_argument"with aninvalid_argumentskey naming the field. The namesinvalid_view,invalid_directionandinvalid_kindexist in the source but are unreachable over MCP — do not branch on them.unknown_argument— an argument name the tool does not declare (a typo, or a parameter from a different tool). The envelope carriesunknown_arguments,accepted_arguments, andclosest_matcheswhen something is near enough to suggest, so it is usually self-correcting. Emitted for every tool since v3.1.0.invalid_section—zim_get_sectiongot an empty/missingsection_idinvalid_content_offset—zim_get/zim_querygot a negativecontent_offsetinvalid_limit—zim_search/zim_querygot alimitbelow 1, or above the cap (50 formode="title"/mode="suggest"and forcross_file=True; 100 forfulltextwithnamespace/content_typefilters; 1000 for plain single-archivefulltext)invalid_offset—zim_search/zim_querygot a negativeoffset(offset=0is the valid default)invalid_max_content_length—zim_get/zim_querygot amax_content_lengthbelow 1 (zim_searchhas no such parameter)invalid_combination—zim_searchgot mutually exclusive arguments (anycursor, a non-zerooffsetoutside single-archive fulltext,cross_file+zim_file_path,mode="suggest"+cross_file, ornamespace/content_typefilters +cross_file)missing_archive—zim_searchcould not resolve an archive formode="suggest",mode="title", or single-archive fulltextinvalid_query—zim_querygot a query over the 4096-character caprate_limited— the token bucket for the internal operation was exhaustedcursor_decode— thecursoris malformed, truncated, or carries an unsupported version; onzim_queryit is also the catch-all for every cursor fault, including one over the 2048-character cap. Drop it and restart the sequencecursor_mismatch— the cursor was issued by a different tool. Advanced surface only; cursors are not portable across operationscursor_context_mismatch— right tool, different target: another namespace, another entry, another linkkind, or a changedinclude_assets. A cursor cannot be retargeted — start over for the new targetcursor_unsupported— onlyzim_links(direction="related"), which returns one ranked set and does not paginate. Omitcursor- See Cursors for which tools issue them, and for the two places where the usual “drop the cursor and pass
offset” recovery does not work file_not_found/entry_not_found/section_not_found/invalid_max_chars— data-layer failures surfaced byzim_get_section.section_not_foundis the one envelope that carries extra keys:available_section_ids,available_section_ids_truncated,available_section_ids_total, andclosest_matchwhen a near match exists.inbound_sidecar_unavailable—zim_links(direction="inbound")against an archive with no link-graph sidecar- the tool’s own wire name —
zim_get,zim_search,zim_query,zim_browse,zim_links,zim_metadata,zim_health,zim_get_section— when an unexpected exception escapes the tool body (archive read error, smart-retrieval miss). There is noget_entryorsearchoperation value: those are internal rate-limiter bucket names, never envelope operations. get server health/get server configuration— the only twooperationvalues that are not snake_case, and the only two that never appear at the top level of a response. The no-argumentzim_health()builds.health,.configurationand.loaded_archivesindependently, and each of the first two catches its own exceptions — so a failure inside one replaces that block alone with an envelope while the rest of the response still comes back. The top-level dict stays an ordinary success payload, soisErrorisfalse: test.healthand.configurationfor anerrorkey rather than assuming the whole call succeeded. This partial case is the only unflagged one — a failure in theloaded_archivesscan, or anywhere in thezim_health(zim_file_path=...)validation branch, surfaces as a normal top-leveloperation="zim_health"envelope withisError: true.zim_query’ssynthesize=Truepath addssynthesize_pipeline_error,synthesize_not_applicable,no_archives_available,meta_only_guidance,chained_intent_rejected,topic_required,search_terms_required,multi_entity_chain_rejected, andinvalid_path. The default path also produceszim_path_not_found(azim_file_pathmatching no loaded archive) andzim_query(any other exception the catch-all absorbs), plus thecursor_decodeenvelope a malformed or mismatchedcursorproduces. Only the guidance conditions — no archive specified, empty query, meta-only query, chained query — come back as markdown withisError: false.
The envelope itself is defined in openzim_mcp/responses.py — the ToolErrorPayload TypedDict and the tool_error() builder every failure path must go through — and the isError mapping in openzim_mcp/mcp_envelope.py. The underlying exception hierarchy in openzim_mcp/exceptions.py is what the broad-except paths wrap; it shapes the message text but not the envelope’s keys — error_code is never emitted.
Quick diagnostics#
First diagnostic#
Which check you can run depends on the tool surface you are running, and the default install is the narrow one — simple mode registers zim_query and nothing else, so zim_health is not there to call.
Every install — confirm which binary is answering:
openzim-mcp --version
It prints openzim-mcp followed by the installed version. A version older than you expect is the single most common cause of “the docs describe a parameter my server rejects”. A command not found here, when your MCP client reports the server failing to start, means the client’s PATH does not include the install location.
Simple mode (the default) — ask for the file list:
"list available ZIM files"
This is the only intent that reports on the server rather than reading an archive, and it exercises directory resolution, permissions and archive readability in a single call. The response names each archive with its path, size, modification time and a readable flag; an archive missing from it is an allowed-directories or file-permission problem, not a server fault.
Do not ask “check the server health and configuration” on a default install. There is no health intent in simple mode, so the phrase is parsed as a topic and searched inside the archive — what comes back is No search results found for "check the server health and configuration", which reads like a broken server and is not one.
Advanced mode — call zim_health:
zim_health()
Available once the surface is advanced (--mode advanced, OPENZIM_MCP_TOOL_MODE=advanced, or the published Docker image, which bakes it in). Returns combined health, resolved configuration and loaded-archives data in one response, consolidating three separate v1 tools into a single call.
Over HTTP — probe the endpoints directly:
curl -fsS http://localhost:8000/healthz
# {"status":"ok"}
curl -fsS http://localhost:8000/readyz
# {"status":"ready"}
Both are auth-exempt, so they work with no token and regardless of tool mode.
Installation issues#
Python version problems#
Error: Python 3.12+ required
Symptoms:
- Server fails to start
- Import errors during installation
Solutions:
- Check Python version:
python --version - Install Python 3.12+: Visit python.org
- Use correct Python:
python3.12 -m openzim_mcp
Package installation failures#
Error: uv not found or pip install failed
Solutions:
- Install uv:
curl -LsSf https://astral.sh/uv/install.sh | sh - Use pip instead:
pip install -e . - Check permissions: Ensure write access to installation directory
Virtual environment issues#
Error: ModuleNotFoundError: No module named 'openzim_mcp'
Solutions:
-
Activate virtual environment:
# Windows venv\Scripts\activate # macOS/Linux source venv/bin/activate -
Reinstall in virtual environment:
pip install -e .
ZIM file issues#
No ZIM files found#
Error: No ZIM files found in allowed directories from the file listing, or the No ZIM files found in any directory warning inside zim_health
Symptoms:
- Empty
.loaded_archivesfromzim_health - Server starts but no content available
Solutions:
- Check directory path: Verify the path exists and is correct
- Check file extensions: Ensure files have
.zimextension - Check permissions: Ensure read access to directory and files
- Download ZIM files: Get files from Kiwix Library
Permission denied#
Error: Permission denied: '/path/to/zim/files'
Solutions:
- Fix directory permissions:
chmod 755 /path/to/zim/files - Fix file permissions:
chmod 644 /path/to/zim/files/*.zim - Run as correct user: Ensure user has read access
- Check SELinux/AppArmor: May block file access on some systems
Corrupted ZIM files#
Error: Failed to open ZIM archive: ...file.zim (a corrupt or truncated archive) or File is not a ZIM file: ...file.txt (wrong extension)
Symptoms:
- Search operations fail
- Content retrieval errors
- Metadata extraction fails
Solutions:
- Verify file integrity: Re-download the ZIM file
- Check file size: Compare with expected size from download source
- Test with different file: Try a known-good ZIM file
- Check disk space: Ensure sufficient space for file operations
MCP client configuration#
Server not responding#
Error: Connection refused or Server timeout
Symptoms:
- MCP client can’t connect
- Commands hang or timeout
- No response from server
Solutions:
- Check server process: Ensure server is running
- Verify configuration: Check MCP client config file
- Check paths: Ensure all paths in config are correct
- Restart client: Restart MCP client application
- Check logs: Look for error messages in server output
Configuration file issues#
Error: Invalid configuration or Command not found
Common issues:
// Wrong - missing directory parameter
{
"command": "python",
"args": ["-m", "openzim_mcp"]
}
// Correct - includes directory and ZIM path
{
"command": "uv",
"args": [
"--directory", "/path/to/openzim-mcp",
"run", "python", "-m", "openzim_mcp",
"/path/to/zim/files"
]
}
Solutions:
- Validate JSON: Use a JSON validator to check syntax
- Check paths: Ensure all paths exist and are accessible
- Use absolute paths: Avoid relative paths in configuration
- Test command manually: Run the command in terminal first
Search and content issues#
zim_search returns 0 results#
Error: none — a miss comes back as a success payload with "results": [] and "total": 0 (isError: false); simple-mode zim_query renders the same miss as No search results found for "<query>".
Possible causes:
- Typos in search terms: Check spelling
- Content not in ZIM file: Verify content exists
- Wrong namespace: Try different namespaces
- Search index issues: ZIM file may lack search index
Solutions:
- Try broader terms: Use more general search terms
- Browse namespaces: Use
zim_browseto explore content - Check ZIM metadata: Use
zim_metadatato understand content (returns metadata + namespace counts in one payload) - Try different ZIM files: Test with known-good content
Entry not found#
Error: Entry not found: 'A/Article_Name'
Smart retrieval: OpenZIM MCP automatically tries to find entries with different encodings, but sometimes manual intervention is needed.
Solutions:
- Use title-mode search first:
zim_search(query="Article Name", mode="title")is the cheapest title-to-path resolver - Check exact path: Use
zim_browse(namespace="C")to find correct path - Try different encodings:
A/Article_NamevsA/Article%20NameA/Article_NamevsA/Article-Name
- Check namespace: Ensure correct namespace (A, C, etc.)
Content truncation#
Issue: Content appears cut off
Cause: max_content_length parameter limiting content
Solutions:
- Increase limit: Use higher
max_content_lengthvalue - Get structure first: Use
zim_get(view="structure")orzim_get(view="toc")to understand content cheaply - Request specific sections: Use
zim_get_section(section_id=...)to target specific parts of long articles
Performance issues#
Slow response times#
Symptoms:
- Long delays for search results
- Timeouts on large operations
- High memory usage
Solutions:
- Check cache settings: Ensure caching is enabled
- Reduce result limits: Use smaller
limitvalues - Monitor server health: Check cache hit rates
- Optimize ZIM files: Use smaller or more focused ZIM files
- Increase system resources: More RAM helps with large ZIM files
Memory issues#
Error: Out of memory or system becomes unresponsive
Solutions:
- Reduce cache size: Lower
OPENZIM_MCP_CACHE__MAX_SIZE - Use smaller ZIM files: Start with smaller content sets
- Limit concurrent operations: Avoid multiple large operations
- Increase system RAM: 2GB+ recommended for large ZIM files
Cache problems#
Issue: Poor cache performance or cache misses
Diagnostics: cache stats live inside zim_health under .health.cache_performance, so this needs the advanced surface:
zim_health()
The cache_performance block always has 13 fields — enabled, size, max_size, ancillary_entries, total_entries, size_bytes, max_bytes, ttl_seconds, hits, misses, hit_rate, background_cleanup, persistence_enabled — plus persistence_path and persistence_file_exists when persistence is on.
Solutions:
- Increase cache size: Raise
OPENZIM_MCP_CACHE__MAX_SIZE. - Increase TTL: Raise
OPENZIM_MCP_CACHE__TTL_SECONDS. - Flush the cache: There are no
cache_clear/cache_stats/warm_cachetools — restart the server to flush. Ifpersistence_enabledis true that is not enough on its own: the shutdown writes every unexpired entry back out and the next start reloads them, so stop the server, delete the file named by.health.cache_performance.persistence_path, then start it again. (For pre-warming after restart, callzim_get(entry_path=...)on your high-value entries from your client.) - Aim for >70% hit rate for read-heavy workloads.
Security issues#
Path traversal warnings#
Error: Path contains suspicious pattern: ...file.zim or Access denied - Path is outside allowed directories: ...file.zim — both arrive as the **Technical Details** line of a **Security Validation Error** envelope.
Cause: Attempting to access files outside allowed directories
Solutions:
- Check file paths: Ensure paths are within allowed directories
- Use relative paths: Avoid
../in file paths - Verify configuration: Check allowed directories setting
Permission errors#
Error: Access denied or Permission denied
Solutions:
- Check file ownership: Ensure correct user owns files
- Fix permissions: Use
chmodto set appropriate permissions - Check parent directories: Ensure all parent directories are accessible
- Avoid running as root: Use appropriate user account
HTTP transport issues#
OpenZimMcpConfigurationError: HTTP transport bound to <host> requires authentication#
The safe-default startup check refuses to bind a non-loopback host without an auth token. Either:
export OPENZIM_MCP_AUTH_TOKEN="$(openssl rand -hex 32)"
# or bind loopback only:
openzim-mcp --transport http --host 127.0.0.1 ...
OpenZimMcpConfigurationError: SSE transport bound to <host> is not allowed#
The SSE transport has no auth middleware in this server, so it must bind 127.0.0.1. For exposed deployments use --transport http (streamable HTTP) with OPENZIM_MCP_AUTH_TOKEN set.
401 unauthorized on /mcp#
Bearer token missing or wrong. Note OPTIONS /mcp is not exempt from auth (closed preflight-bypass attack surface) — a bare OPTIONS /mcp probe without a token gets 401. Browser CORS preflights are unaffected: when OPENZIM_MCP_CORS_ORIGINS is configured, the CORS middleware sits outside auth and answers the preflight itself (browsers never attach Authorization to a preflight).
/healthz and /readyz are exempt from auth and useful for unauthenticated probing.
Browser CORS errors when resuming sessions#
Mcp-Session-Id is in the server’s allow_headers and expose_headers since v1.0, and stays there for clients on the initialize handshake. If you have a reverse proxy in front that strips this header, their sessions can’t resume. A 2026-07-28 client has no session to resume — it needs MCP-Protocol-Version, Mcp-Method, and (for tools/call) Mcp-Name to survive the proxy instead; all three are in allow_headers.
OPENZIM_MCP_CORS_ORIGINS rejected at startup#
Wildcard "*" is rejected (whitespace-padded " * " too). List origins explicitly:
export OPENZIM_MCP_CORS_ORIGINS='["https://app.example.com","https://other.example.com"]'
UserWarning: Host 'localhost' does not resolve to loopback#
Your /etc/hosts maps localhost away from 127.0.0.1. The server emits a warning and treats the host as public, which then triggers the safe-default refusal. Bind explicitly to 127.0.0.1 or fix /etc/hosts.
Health endpoint behavior#
/healthz(liveness) — returns 200 OK if the process is running and the event loop is responsive. No auth./readyz(readiness) — returns 200 OK if at least one allowed directory is readable, 503 with{"status":"not_ready", "reason":"no readable allowed directories"}otherwise. No auth.
Per-entry resource issues#
zim://wikipedia/entry/A/Climate_change not found#
Clients MUST URL-encode / as %2F in the path segment because the SDK’s URI template engine treats / as a segment separator. Correct form:
zim://wikipedia/entry/A%2FClimate_change
A literal slash will fail to route. Other RFC 3986 reserved characters in the path also need encoding.
Subscription issues#
Subscriptions never fire#
- Check
OPENZIM_MCP_SUBSCRIPTIONS_ENABLED— set tofalsedisables the watcher and withholds the capability:subscriptions/listenfails with method-not-found, so an acknowledged listen actually proves the subscription gate is open. - Notifications are published only under the HTTP transport (
--transport http). On stdio and SSE the capability is not advertised and a listen request fails with method-not-found. - Check what your client asked for. A
.zimappearing or disappearing publishesnotifications/resources/list_changed(opt in withresourcesListChanged: true); only an in-place replacement publishesnotifications/resources/updated, and only for that archive’s ownzim://{name}URI. Listingzim://filesunderresourceSubscriptionsnever fires. - Tune
OPENZIM_MCP_WATCH_INTERVAL_SECONDS(default 5, range 1-60). Slower poll = longer detection latency. - Detection compares mtime and size — same-size archive replacement is caught via the mtime change, same-mtime rewrites via the size change.
- A client that stops reading its stream overruns the SDK’s per-stream event backlog and has the stream ended; process notifications asynchronously and re-listen if the stream closes. There is no replay, so re-read the resource after re-listening.
resources/subscribewas removed by the 2026-07-28 revision and is not served on this build — it answers-32601 Method not found. A client that only speaks theinitializehandshake cannot receive change notifications at all.
Configuration errors#
Cleaner OpenZimMcpConfigurationError instead of pydantic dump#
The server catches pydantic.ValidationError from OpenZimMcpConfig construction and re-surfaces it as OpenZimMcpConfigurationError with a human-readable message naming the offending field. If you’re grepping logs for “validation error”, search for OpenZimMcpConfigurationError instead.
Path traversal warnings show ...filename.zim#
Path-traversal rejections redact absolute filesystem paths to ...filename.zim form so the canonical allowed-directory layout is not leaked. The truncated form is expected, not corrupted output.
Advanced debugging#
Enable debug logging#
export OPENZIM_MCP_LOGGING__LEVEL=DEBUG
uv run python -m openzim_mcp /path/to/zim/files
Check server logs#
Look for error patterns in server output:
ERROR: Critical errors requiring attentionWARNING: Potential issues to investigateINFO: Normal operation messages
Test with minimal setup#
- Use small ZIM file: Test with
<100MBfile - Single operation: Test one tool at a time
- Fresh environment: Clean virtual environment
- Default configuration: No custom environment variables
Getting help#
Before asking for help#
- Check this guide: Review relevant sections above
- Run diagnostics: Use
zim_health— with no argument it consolidates server health, configuration, and loaded-archives into a single response (three v1 tools in one call); pass azim_file_pathto validate one archive (Archive.check()+ checksum + index/identity) - Check logs: Look for error messages — error text is safe to copy verbatim (paths and PIDs are redacted)
- Test minimal case: Reproduce with simple setup
Where to get help#
- GitHub Issues: Report bugs or ask questions
- Documentation: Check other docs pages — FAQ, Architecture overview
Information to include#
When reporting issues, include:
- Operating system and version
- Python version:
python --version - OpenZIM MCP version:
python -c "import openzim_mcp; print(openzim_mcp.__version__)"or checkzim_health→.configuration.configuration.config_hash(the outer key is the block, the inner one the report) and the MCPserverInfo.version(reads fromimportlib.metadata; reports openzim-mcp’s actual version, not the MCP SDK default) - ZIM file details: Size, source, name
- Error messages: Full error text
- Configuration: MCP client config (remove sensitive paths)
- Steps to reproduce: Exact commands used
Still having issues? Open an issue with detailed information about your problem.
The v1.x maintenance window closed when v2.5.0 shipped (2026-06-18); only the current major line is supported — see SECURITY.md for the policy. The CHANGELOG carries the v1 → v2 migration table and the v3.0.0 breaking-changes entry.