MCP server for ALT Linux man pages (manpages.altlinux.team)
  • Go 95.5%
  • Dockerfile 4.5%
Find a file
Repository files (latest commit first)
Filename Latest commit message Latest commit date
Alexey Shabalin bf6b000226
All checks were successful
ci / test (push) Successful in 1m13s
release / binaries (push) Successful in 1m10s
release / image (push) Successful in 1m39s
Release 0.1.1
2026-08-06 16:01:00 +03:00
.forgejo/workflows ci: one shared build recipe for images and release artifacts 2026-08-05 19:14:33 +03:00
internal Release 0.1.1 2026-08-06 16:01:00 +03:00
.gitignore feat: alt-man-mcp — MCP server for man pages (manpages.altlinux.team layout) 2026-07-14 19:49:33 +03:00
alt-man-mcp.container feat: alt-man-mcp — MCP server for man pages (manpages.altlinux.team layout) 2026-07-14 19:49:33 +03:00
alt-man-mcp.network feat: alt-man-mcp — MCP server for man pages (manpages.altlinux.team layout) 2026-07-14 19:49:33 +03:00
Containerfile ci: one shared build recipe for images and release artifacts 2026-08-05 19:14:33 +03:00
go.mod chore: move to altlinux.space/ai-stack 2026-08-05 19:17:41 +03:00
go.sum feat: alt-man-mcp — MCP server for man pages (manpages.altlinux.team layout) 2026-07-14 19:49:33 +03:00
LICENSE chore: MIT license, copyright ALTLinux Team 2026-08-05 19:17:41 +03:00
main.go Release 0.1.1 2026-08-06 16:01:00 +03:00
README.md feat: alt-man-mcp — MCP server for man pages (manpages.altlinux.team layout) 2026-07-14 19:49:33 +03:00

alt-man-mcp

A read-only Go MCP server for live access to man pages served by any manpages site in the manpages.altlinux.team layout — a per-branch names.json index plus per-page HTML. It exposes three tools (get_manpage, search_manpages, list_sections) over stdio or streamable-http and never writes anything back.

The source site is configurable with --man-base-url (default https://manpages.altlinux.team): point it at any compatible mirror or site that serves the same names.json + HTML format.

This is not RAG: no embeddings are built and no vector index is used. The server resolves a page against a lightweight in-memory name index and does a targeted live-fetch of a single HTML page, extracting readable text from it.

Data source

1. Name index — one JSON object per branch:

https://manpages.altlinux.team/<branch>/names.json   (~6 MB)

Format: {"<name>": [{"s":"<section>","u":"<path>","d":"<short description>"}], ...}. The key is the lowercased page name; one name may yield several records (different packages/sections/languages). Example: "ls":[{"s":"1","u":"c/coreutils/ls.1.html","d":"list directory contents"}, ...].

From the path u are derived: the package (second-to-last segment, c/coreutils/ls.1.htmlcoreutils) and the language (a .ru.html suffix → Russian page, otherwise English; there is no separate names.ru.json).

The index is loaded once per branch and cached in memory (TTL 1 hour, concurrent loads deduplicated via singleflight). Search and name resolution filter this in-memory map, without large per-request downloads.

2. Page content — HTML by the path from the index:

https://manpages.altlinux.team/<branch>/<u>        (~16 KB)

The man-page text is extracted from the HTML (golang.org/x/net/html): the main container is div.manual-text (mandoc output); sections h1.Sh## …, subsections h2.Ss### …, paragraphs p.Pp, option lists dl.Bl-tag (dt — the option, dd — the indented explanation), literal pre blocks in triple backticks. The site header/nav/footer live outside div.manual-text and are discarded. Pages are cached by URL with a short TTL (5 min).

3. Section manifest (small) for list_sections:

https://manpages.altlinux.team/<branch>/exports/index.json

The heavy ndjson.zst exports (tens of MB per request) are no longer used — the only large download is reduced to a single names.json per branch.

Tools

Tool Purpose
get_manpage(name, section?, package?, branch?, lang?) Full readable text of a single man page by name (extracted from HTML). section and package disambiguate. If the exact name is not found — similar names are returned.
search_manpages(query, branch?, section?, limit?) Search pages by a name/description substring in the name index (in memory, fast). Ranking: exact name → prefix → name substring → description only.
list_sections(branch?) Which sections and languages are available for a branch and how many pages each holds (from index.json).

Defaults: branch=sisyphus, lang=en (for get_manpage — falls back to en if there is no ru translation), limit=20.

Build and run

go build ./...
go test ./... -count=1

# stdio (for a local agent / debugging)
./alt-man-mcp --transport stdio

# streamable-http (endpoint /mcp, health /healthz)
./alt-man-mcp --transport http --addr :8092

Flags:

  • --transport stdio|http (default stdio)
  • --addr (default :8092)
  • --man-base-url — base URL of the manpages site (default https://manpages.altlinux.team). Set it to any compatible mirror or site that serves the same names.json + per-page HTML format.

Usage with MCP clients

The server speaks MCP over stdio; the binary is alt-man-mcp. Point --man-base-url at the manpages site you want (omit it to use the default).

Claude.mcp.json:

{
  "mcpServers": {
    "man": {
      "command": "alt-man-mcp",
      "args": ["--transport=stdio", "--man-base-url=https://manpages.altlinux.team"]
    }
  }
}

Codex~/.codex/config.toml:

[mcp_servers.man]
command = "alt-man-mcp"
args = ["--transport=stdio"]

opencodeopencode.json:

{
  "mcp": {
    "man": {
      "type": "local",
      "command": ["alt-man-mcp", "--transport=stdio"],
      "enabled": true
    }
  }
}

Container / quadlet

podman build -t alt-man-mcp .

Quadlet units: alt-man-mcp.container + alt-man-mcp.network (Options=mtu=1500 is required for rootless podman). The default port is :8092 (rdb=8090, bugzilla=8091).