zig-docs-mcp
# zig-docs-mcp
**Local, open-source MCP server + agent skills** that serve **always-fresh
official Zig documentation for the latest release**, a curated
**high-performance lightweight-software guidance corpus**, and a safe,
**dry-run-first auto-update** for an out-of-date local Zig toolchain.
```
zig-docs-mcp
├── zigdocs MCP server (stdio, local, no accounts)
├── guidance/ curated performance guidance (12 topics)
├── skills/zig-docs agent skill: operating rules for Zig work
└── skills/zig-docs-mcp agent skill: Python integration (`zdoc` singleton)
```
Zig changes fast, and answers from training memory go stale between minor
releases — 0.16 replaced the entire I/O layer and moved `Dir` from `std.fs` to
`std.Io`. This server fetches official docs and released std sources per call
(with short-TTL revalidation), so every answer cites the version it came from.
When your local compiler falls behind the docs, the server says so and offers
a gated upgrade. It never mutates your system without explicit confirmation.
---
## Table of contents
1. [Why](#why)
2. [How it stays fresh](#how-it-stays-fresh)
3. [Requirements](#requirements)
4. [Install the server](#install-the-server)
5. [Connect an MCP client](#connect-an-mcp-client)
6. [Prime Agent integration](#prime-agent-integration)
7. [MCP tool reference](#mcp-tool-reference)
8. [Toolchain auto-update](#toolchain-auto-update)
9. [Guidance corpus](#guidance-corpus)
10. [Configuration](#configuration)
11. [Development](#development)
12. [Troubleshooting](#troubleshooting)
13. [License](#license)
---
## Why
- **Docs rot fast.** Zig's std library layout moves between minor releases.
Serving the *latest release's* real sources is the only honest source of
API truth. `zig_std` resolves symbols by walking actual re-exports in the
released tree — not a scraped snapshot.
- **Performance advice should be mechanical.** The bundled corpus explains
allocation strategy, data layout, comptime, binary size, startup latency,
SIMD, concurrency, and benchmarking — grounded in how hardware and the
runtime actually behave (cache lines, syscalls, page faults), not vibes.
- **A behind compiler quietly invalidates everything.** `zig_version_status`
compares your toolchain against the upstream index on every check, and
`zig_update` offers a concrete, reviewable upgrade plan.
- **Everything is local-first.** The server runs on your machine over stdio.
No accounts, no tokens, no telemetry. Network goes only to ziglang.org for
docs, release notes, and source tarballs.
## How it stays fresh
- Cached responses **revalidate against ziglang.org** when older than 6 hours
(`force=true` revalidates immediately). Revalidation uses conditional GETs
(`ETag` / `Last-Modified`), so it is cheap.
- **Offline-safe**: if the network is down, cached content is served with a
`stale` flag instead of failing. (The very first run needs network once.)
- Std sources come from the **official per-release `src` tarball** — the
canonical content even when GitHub release tags lag (0.16.0 was not tagged
on GitHub when this was built). The tarball is downloaded once per version
and only `lib/std/**` is extracted.
- The `channel` parameter selects `stable` (latest release, the default) or
`master` (nightly), so you can preview next-release changes.
Cache layout (`~/.cache/zig-docs-mcp/`, override with `ZIG_DOCS_MCP_CACHE`):
```
~/.cache/zig-docs-mcp/
├── http/ upstream bodies + ETag/Last-Modified metadata
├── langref-0.16.0.json parsed reference sections (per version)
├── notes-0.16.0.json release-notes digest
├── zig-0.16.0-src.tar.xz source tarball cache
└── src/0.16.0/lib/std/ extracted std sources (550 files)
```
## Requirements
- Python ≥ 3.10 and [uv](https://docs.astral.sh/uv/)
- macOS or Linux (auto-update supports Homebrew and standalone installs;
Windows gets a working plan printout but no tarball strategy yet)
- Network access to ziglang.org for first fetches and revalidation
## Install the server
```bash
git clone https://github.com/gbrlpzz/zig-docs-mcp
cd zig-docs-mcp
uv tool install . # installs the `zigdocs` command on your PATH
zigdocs --help # verify
```
Prefer not to install? Run it straight from the clone:
```bash
uv run --project ~/zig-docs-mcp zigdocs
```
## Connect an MCP client
Any MCP client that speaks stdio. Point it at the `zigdocs` command:
```json
{
"mcpServers": {
"zig-docs": {
"command": "zigdocs"
}
}
}
```
Without a global install, use the clone directly:
```json
{
"mcpServers": {
"zig-docs": {
"command": "uv",
"args": ["run", "--project", "/path/to/zig-docs-mcp", "zigdocs"]
}
}
}
```
## Prime Agent integration
Two skills ship in this repo. Symlink them and restart the session (or run
`/reload`):
```bash
ln -sfn ~/zig-docs-mcp/skills/zig-docs ~/.agents/skills/zig-docs
ln -sfn ~/zig-docs-mcp/skills/zig-docs-mcp ~/.agents/skills/zig-docs-mcp
```
Then, from the agent kernel:
```python
from zig_docs_mcp import zdoc
await zdoc.zig_version_status() # local vs latest upstream
await zdoc.zig_update() # dry-run upgrade plan
await zdoc.zig_update(dry_run=False, confirm=True) # apply after user agrees
await zdoc.zig_langref(section="Errors") # fresh language reference
await zdoc.zig_std(symbol="std.heap.ArenaAllocator") # std docs from released source
await zdoc.zig_changelog() # what changed in the release
await zdoc.perf_guidance(topic="allocation-strategy") # curated guidance
await zdoc.zig_search(query="vectorization") # search everything at once
```
Calls return their result as a JSON string (full-topic guidance reads return
raw markdown); parse with `json.loads(...)` when you need fields like
`version` or `docs`. Arguments are keyword-only. The server command is
resolved in order: `ZIG_DOCS_MCP_CMD`, a `zigdocs` on PATH, then
`uv run --project` against `ZIG_DOCS_MCP_REPO` (default `~/zig-docs-mcp`).
`skills/zig-docs/SKILL.md` contains the operating rules the agent follows:
version-gate first, docs before code, cite the doc version, and never apply
an update without the user's explicit go-ahead.
## MCP tool reference
### `zig_version_status`
Compares the local `zig version` with the latest upstream release.
```json
{
"local_version": "0.16.0",
"local_path": "/opt/homebrew/bin/zig",
"latest_stable": "0.16.0",
"master": "0.17.0-dev.1818+7051f8e73",
"up_to_date": true
}
```
When the local toolchain is older, the response adds `behind` and a
`suggestion` pointing at `zig_update` (illustrative example):
```json
{
"local_version": "0.15.2",
"latest_stable": "0.16.0",
"up_to_date": false,
"behind": "local 0.15.2 < latest 0.16.0",
"suggestion": "Call the zig_update tool (dry-run first) to upgrade the local toolchain to the latest stable release."
}
```
### `zig_update`
Upgrades the local toolchain. **Dry-run is the default** — it prints the exact
plan and changes nothing. Applying requires `dry_run=false, confirm=true`.
See [Toolchain auto-update](#toolchain-auto-update).
### `zig_langref`
Official Language Reference, fetched fresh for the channel version.
- `section="Errors"` → full section text (code blocks preserved):
```
### Error Set Type
An error set is like an enum. However, each error name across the entire
compilation gets assigned an unsigned integer greater than 0. ...
```
- `query="vector"` → ranked section hits:
```json
[{"section_id": "Vectors", "title": "Vectors§"},
{"section_id": "Builtin-Functions", "title": "Builtin Functions§"}]
```
- no arguments → the list of all section ids.
### `zig_std`
Standard-library docs from the **exact released source**. Symbol resolution
walks real re-exports (`std.zig` → `heap.zig` → `heap/ArenaAllocator.zig`),
follows `@import` aliases, and returns the `///` docs plus the declaration
text from that release:
```json
{
"symbol": "std.ArrayList",
"version_source": "0.16.0",
"file": "lib/std/std.zig",
"line": 49,
"declaration": "pub fn ArrayList(comptime T: type) type {\n return array_list.Aligned(T, null);\n}",
"docs": "A contiguous, growable list of items in memory. This is a wrapper around a\nslice of `T` values. ..."
}
```
If a name is not a plain top-level declaration in the walked namespace
(layouts move between releases), the tool falls back to a corpus-wide search
of top-level declarations, best match first — e.g. `std.fs.Dir` on 0.16
correctly surfaces `lib/std/Io/Dir.zig`. `query="arena"` searches std doc
comments directly.
### `zig_changelog`
Release-notes digest for the current channel version: section titles plus a
short summary each. Useful right after a release lands
(`zig_changelog(force=true)`).
### `perf_guidance`
Curated guidance for high-performance lightweight software. No arguments
lists topics; `topic="allocation-strategy"` returns the full guide (raw
markdown with Principle / Mechanics / Zig idiom / Anti-patterns / Rules of
thumb); `query=...` searches across all guides.
### `zig_search`
Unified search across langref, std doc comments, and guidance:
```json
{"query": "vectorization", "langref": [...], "guidance": [...], "std": [...], "std_version": "0.16.0"}
```
`scope` narrows it: `all` (default) | `langref` | `std` | `guidance`.
## Toolchain auto-update
`zig_update` picks a strategy automatically:
1. **Homebrew-managed zig** (binary resolves inside the brew prefix) →
`brew upgrade zig`:
```json
{
"mode": "dry-run (nothing changed). Re-run with confirm=true to apply.",
"target_version": "0.16.0",
"current": "0.16.0",
"strategy": "homebrew",
"command": ["brew", "upgrade", "zig"],
"note": "Homebrew formula may lag the newest release slightly."
}
```
2. **Standalone install** (official tarball, any other location) → downloads
the platform tarball from the upstream index, extracts to
`~/.local/opt/zig-<version>`, and shims `~/.local/bin/zig`:
```json
{
"strategy": "standalone-tarball",
"download": "https://ziglang.org/download/0.16.0/zig-aarch64-macos-0.16.0.tar.xz",
"install_dir": "~/.local/opt/zig-0.16.0",
"steps": ["download ...", "extract ...", "symlink ~/.local/bin/zig -> .../zig/zig"],
"activation": "~/.local/bin is first on PATH; new zig takes effect immediately"
}
```
If `~/.local/bin` is **not** first on PATH, the plan says so explicitly — the
old compiler would still win, and the tool tells you how to fix the order.
Safety rules:
- Default is a dry-run. Nothing is downloaded, moved, or linked.
- Applying requires `dry_run=false, confirm=true` together.
- Agents using this server are instructed to show the plan and get the
user's explicit go-ahead before confirming.
## Guidance corpus
Twelve topics in `guidance/`, shipped inside the wheel and served by
`perf_guidance`. Principles are universal; snippets are Zig 0.16-era; exact
API truth always comes from `zig_std`, never from the corpus.
| Topic | One-line summary |
| --- | --- |
| `allocation-strategy` | Match allocator to lifetime; arena bump-pointer cost vs general allocator bookkeeping; hidden allocations. |
| `data-oriented-design` | SoA vs AoS byte math on 64-byte cache lines; hot/cold splitting; `MultiArrayList`. |
| `comptime-over-runtime` | Comptime results become rodata/immediates; runtime tables cost dirty pages. |
| `zero-copy-parsing` | Slices are 16 bytes; allocate-per-token costs an allocation, a memcpy, and cache lines per token. |
| `binary-size` | Size = reachability; strip, panic modes, dep hygiene; smaller text = fewer startup page faults. |
| `startup-latency` | No init_array, lazy text page faults, lazy init, no work before argv. |
| `memory-layout` | Padding math, field ordering, packed structs, `@sizeOf` comptime asserts. |
| `simd-and-vectorization` | Auto-vectorization blockers, lane-wise accumulate + single reduce, `@select` vs branches. |
| `concurrency-and-io` | MESI cost of shared writes, futex parking, syscall batching, false-sharing padding. |
| `error-handling-cost` | Errors are u16 values; `try` is a predicted branch; no unwinding. |
| `benchmarking-methodology` | Release builds, warmup, min/median over mean, sink to defeat DCE, counters. |
| `dependency-lightweightness` | Std-first; deps add linked code and build fragility; vendor tiny utilities. |
## Configuration
| Variable | Meaning | Default |
| --- | --- | --- |
| `ZIG_DOCS_MCP_CACHE` | cache directory | `~/.cache/zig-docs-mcp` |
| `ZIG_DOCS_MCP_CMD` | full server command line (skill override) | — |
| `ZIG_DOCS_MCP_REPO` | repo dir for the `uv run` fallback | `~/zig-docs-mcp` |
## Development
```bash
make sync # deps
make test # unit tests (offline; std-source tests skip without warm cache)
make e2e # spawns the real server over stdio, calls every tool
make fmt # ruff format + check
```
The e2e suite needs network on first run (it warms the cache). Unit tests
that exercise symbol resolution run against the warm std-source cache and are
skipped cleanly when it is absent.
## Troubleshooting
- **`zigdocs server not found`** (Prime Agent skill): install with
`uv tool install .` from the clone, or set `ZIG_DOCS_MCP_REPO` to the clone
path, or set `ZIG_DOCS_MCP_CMD` to a full command line.
- **First run fails while offline**: the cache starts empty; fetch once while
online. After that, stale-cache fallback keeps every tool working.
- **Results look stale after a new release**: pass `force=true` (the 6-hour
TTL otherwise applies).
- **`zig version` still old after an update**: a new shell is needed, and
`~/.local/bin` must precede the previous install directory on PATH. The
dry-run plan states the exact situation for your machine.
- **Homebrew zig lags the newest release**: brew formulas trail releases; use
the standalone strategy (remove the brew formula, install standalone) if
you need day-one versions.
## License
MIT — see [LICENSE](LICENSE).
TDQS
Scored across 7 tools
Each tool targets a distinct purpose: version checking, updating, language reference, standard library docs, changelog, performance guidance, and a unified search that complements the others. No two tools appear to do the same thing, and descriptions clarify boundaries.
Most tools follow a 'zig_' prefix, but the second element varies in style (noun, verb_noun, abbreviation). 'perf_guidance' breaks the prefix pattern. Still, names are short, descriptive, and predictable enough for an agent to infer purpose.
Seven tools is well-scoped for a documentation and toolchain server. Each tool earns its place, covering version checks, updates, references, and search, without bloat or missing essentials.
The surface covers the core documentation lifecycle: version status, update, language reference, std docs, changelog, performance guidance, and a cross-cutting search. There are no obvious gaps for the stated purpose of providing Zig documentation and version management.