MCP server for MediaWiki wikis (any instance; base URL via --mw-base-url)
  • Go 98.4%
  • Dockerfile 1.6%
Find a file
Repository files (latest commit first)
Filename Latest commit message Latest commit date
Alexey Shabalin 4bb1fe5f29
All checks were successful
release / binaries (push) Successful in 1m9s
ci / test (push) Successful in 1m19s
release / image (push) Successful in 1m39s
Release 0.2.1
2026-08-06 16:00:57 +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.2.1 2026-08-06 16:00:57 +03:00
.gitignore chore: rename to mediawiki-mcp 2026-07-30 16:06:39 +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-mediawiki-mcp — MCP server for MediaWiki wikis 2026-07-14 19:49:38 +03:00
LICENSE chore: MIT license, copyright ALTLinux Team 2026-08-05 19:17:41 +03:00
main.go Release 0.2.1 2026-08-06 16:00:57 +03:00
main_test.go chore: move to altlinux.space/ai-stack 2026-08-05 19:17:41 +03:00
mediawiki-mcp.container chore: rename to mediawiki-mcp 2026-07-30 16:06:39 +03:00
mediawiki-mcp.network chore: rename to mediawiki-mcp 2026-07-30 16:06:39 +03:00
README.md Release 0.2.1 2026-08-06 16:00:57 +03:00

mediawiki-mcp

A Go MCP server for accessing any MediaWiki wiki through the MediaWiki Action API. The target wiki is chosen at runtime with --mw-base-url, so the same binary works against any MediaWiki instance, not a specific site.

The server runs in one of two modes, chosen by whether --mcp-auth-header is set:

Mode When Who the wiki sees Tools
Anonymous (default) --mcp-auth-header unset nobody for reads; the static MW_USERNAME bot for edits, if configured 6 read tools, +2 write tools with a static bot and --read-only off
Personal credentials --mcp-auth-header=<Header>, http transport the account whose login:password arrived in that header, per session per session: none without a usable credential, otherwise 6 read tools, +2 write tools with --read-only off

On a public wiki reading is anonymous and always available, and the server is read-only by default. Edits (edit/create) are enabled only when credentials are set (MW_USERNAME + MW_PASSWORD, a Special:BotPasswords bot password) and --read-only is not set. The write tools edit the REAL configured wiki.

A wiki closed to anonymous readers — the usual internal-wiki setup, where even action=query&meta=siteinfo answers readapidenied — can only be served in the second mode: the server has no account of its own and works under each client's own credential (see Personal credentials).

Switching modes changes nothing about the first one: without --mcp-auth-header the server behaves exactly as it did before personal credentials existed — anonymous reads, static bot for writes, client headers ignored.

Configuration

The wiki is not hard-coded. Everything is driven by flags / environment variables:

  • --mw-base-url (env MW_BASE_URL) — base URL of the target MediaWiki wiki. From it <base>/api.php is built. The built-in default (https://www.altlinux.org) is only an example; point it at your own wiki, e.g. --mw-base-url=https://wiki.example.org.
  • --wiki-label (env MW_LABEL) — one line naming this wiki, e.g. --wiki-label='внутренняя вики компании — wiki.example.org'. It is stamped at the front of every tool description. The tool names are the same on every instance of this server, so when an assistant has two of them connected — a public wiki and an internal one — the description is the only thing that distinguishes search_wiki from search_wiki. Without the flag the host of --mw-base-url is used, which is worse but still unambiguous.
  • --mw-username (env MW_USERNAME) — bot name (Special:BotPasswords), required for write tools.
  • --mw-password (env MW_PASSWORD) — bot password (Special:BotPasswords), required for write tools.
  • --mcp-auth-header (env MCP_AUTH_HEADER, http only) — name of the inbound header in which a client sends its own wiki credential, e.g. Authorization. Unset → inbound header auth is disabled and client headers are ignored.
  • --session-ttl (default 30m, http only) — how long a logged-in session of one credential is cached before logging in again.
  • --read-only (env MW_READ_ONLY, default false) — disable the write tools even when credentials are set.

Reads of a public wiki never need credentials. Writes require both a credential and the --read-only gate to be off: writeEnabled = (credential present) && !--read-only.

Personal credentials (http)

--mcp-auth-header=Authorization turns the server into a multi-user one: it has no account of its own, and each session works under the account whose credential arrived in that header. This is the only way to serve a wiki that refuses anonymous reads, and it keeps every user's permissions their own instead of levelling everybody to one service account.

The value is a pair login:password, split on the first colon. On MediaWiki the login usually comes from Special:BotPasswords and looks like Ivanov@mcp — the @ belongs to the login and is preserved:

Ivanov@mcp:kq8f3l2n5x7v9b1m4c6z8t0y2w4r6e8u
Basic <base64(login:password)>          # also accepted

What one session gets depends on its credential:

Header Tools
absent / empty / unparseable none — on such a wiki they could not work anyway, and an empty tool list is what tells the client to fill the credential in
a well-formed pair the 6 read tools, plus the 2 write tools when --read-only is off

Sessions

Login is action=query&meta=tokens&type=loginaction=login, after which the session cookie authenticates every request. Sessions are cached per credential (--session-ttl, default 30 minutes), so a conversation does not log in again on every tool call; different credentials never share a session, the cache is bounded and thread-safe, and the password is neither logged nor included in any error message. An expired session is renewed once, transparently. A credential the wiki refuses produces a readable explanation with the way out (Special:BotPasswords), not a protocol error.

Without --mcp-auth-header nothing of this applies: the server reads the wiki anonymously, exactly as before.

Bot password rights

A bot password carries only the rights its owner ticked when creating it on Special:BotPasswords, so the boxes matter. The labels below are quoted verbatim from the MediaWiki interface (English, and Russian as grant-* messages render them):

Checkbox Needed for
Basic rights — «Основные права» everything, including reading: without it even get_site_info fails
Edit existing pages — «Редактирование существующих страниц» edit_page
Create, edit, and move pages — «Создание, редактирование и переименование страниц» create_page
Edit protected pages — «Редактирование защищённых страниц» editing a protected page (an admin right on top of that)

A refusal from the API (permissiondenied, writeapidenied, cantcreate, protectedpage, blocked, readonly, ratelimited, …) is answered with the name of the missing checkbox and where to tick it, rather than with the raw code. The wiki shows a new bot password once; the set of rights of an existing one can be changed on the same page.

Edits go live immediately

There is no draft, no preview and no confirmation step: a successful edit_page / create_page publishes the change at once, under the account whose credential is in the header, and it shows up in the page history and in Recent Changes as an edit by that person. Edits made with a personal credential are deliberately not flagged bot=1 — they are that person's edits, not a robot's, and must be visible as such. Undoing one is a manual step through the page history. Before a mode=replace edit, read the page with get_page first: replace overwrites the whole article, including anyone else's edits made in the meantime.

Open WebUI (per-user secrets)

The header is what Open WebUI's per-user secrets fill in: the connection declares one user_config slot, and each user pastes their own bot password into it.

"headers": { "Authorization": "{{USER_SECRET:wiki_credentials}}" },
"config": { "user_config": {
  "type": "object",
  "properties": {
    "wiki_credentials": {
      "type": "string",
      "title": "Логин и пароль бота вики",
      "description": "Одной строкой `Имя@ИмяБота:пароль` — заводится на Special:BotPasswords",
      "input": { "type": "password" }
    }
  },
  "required": ["wiki_credentials"]
}}

A user who has not filled the slot in simply sees no tools, which is the signal to fill it in.

Data source

The MediaWiki Action API <base>/api.php, format=json. Read requests are made without authentication. The base URL is configured with --mw-base-url (env MW_BASE_URL); from it <base>/api.php is built. User-Agent is mediawiki-mcp/0.2.1, timeout 30 s.

How article text is extracted

The TextExtracts extension (prop=extracts&explaintext=1, plain text) may not be installed on a given wiki — the API then replies Unrecognized value for parameter "prop": extracts. The readable text is therefore taken via action=parse&prop=text (the engine renders the article to HTML, container div.mw-parser-output) and flattened into a markdown-like form using golang.org/x/net/html: headings → ## …, paragraphs, lists (- …), code blocks (```). Service markup (edit [edit] links, table of contents, footnotes <sup>, navboxes, catlinks) is dropped. Search snippets and section headings are stripped of tags by a separate simplified parser.

Tools (read-only)

Tool API Purpose
search_wiki(query, limit?) list=search Full-text search of articles by keywords; titles + snippets.
get_page(title) parse&prop=text|sections Article content as plain text (extracted from HTML). Follows redirects. Missing article → friendly text with search hints.
get_page_sections(title) parse&prop=sections Table of contents (list of sections) of an article.
get_site_info() meta=siteinfo Engine version, language, statistics (articles/pages/edits/files/users), namespaces.
list_category_members(category, limit?) list=categorymembers Articles and subcategories of a category. The Category:/Категория: prefix is optional.
page_url(title) Canonical article URL (spaces → underscores, percent-encoding). Does not fetch the page.

Defaults: search_wiki limit=10, list_category_members limit=20 (maximum 50 for both).

Tool descriptions are bilingual (English + Russian keywords) so they match both English and Russian queries.

Tools (write) — only with credentials

Registered only when writeEnabled = (credential present) && !--read-only. Otherwise they are absent from the tool list (read-only — 6 tools, read-write — 8).

Tool API Purpose
edit_page(title, text, summary?, mode?) action=edit Edit an article. mode: replace (default, overwrite all), append (to the end), prepend (to the beginning).
create_page(title, text, summary?) action=edit + createonly=1 Create a new page; error if the article already exists.

Both tools publish immediately, with no confirmation, as the account whose credential is in play — the static bot in anonymous mode, the session's own user in personal-credential mode — and the change is visible in the page history right away. mode=replace overwrites the entire article, so read it with get_page first.

Authentication is done as a bot (Special:BotPasswords): login is lazy (on the first write call), with a cookie session (net/http/cookiejar) and a csrf token (cached; on badtoken — one automatic re-login). The password and tokens are not written to logs and do not leak into response text. Wiki refusals (no permission / page protection / spam filter / CAPTCHA) are returned as friendly text naming the missing right and suggesting a manual edit.

Build and run

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

# stdio (for a local agent / debugging)
./mediawiki-mcp --transport stdio --mw-base-url https://wiki.example.org

# streamable-http (endpoint /mcp, health /healthz)
./mediawiki-mcp --transport http --addr :8094 --mw-base-url https://wiki.example.org

Flags:

  • --transport stdio|http (default stdio)
  • --addr (default :8094)
  • --mw-base-url (env MW_BASE_URL, default https://www.altlinux.org — an example)
  • --wiki-label (env MW_LABEL) — one-line identity of this wiki inside every tool description; default: the host of the base URL
  • --mw-username (env MW_USERNAME) — bot name (Special:BotPasswords)
  • --mw-password (env MW_PASSWORD) — bot password
  • --mcp-auth-header (env MCP_AUTH_HEADER, http only) — inbound header with the client's own login:password
  • --session-ttl (default 30m, http only) — lifetime of a cached logged-in session
  • --read-only (env MW_READ_ONLY, default false) — disable write even when credentials are set
# read-write: edits performed as the bot
MW_USERNAME='YourBot@mcp' MW_PASSWORD='<botpassword>' \
  ./mediawiki-mcp --transport http --addr :8094 --mw-base-url https://wiki.example.org

# closed wiki: no account of its own, every client sends its own credential
./mediawiki-mcp --transport http --addr :8094 \
  --mw-base-url https://intranet.example.org --mcp-auth-header Authorization --read-only

On startup the server logs its mode: personal credentials via header X, read-write mode (user X), read-only mode (no MW_USERNAME/MW_PASSWORD) or read-only mode (--read-only). Passwords are not logged — neither the static one nor the ones arriving in headers.

Usage with MCP clients

The server speaks MCP over stdio; the binary is mediawiki-mcp. Point it at your wiki with --mw-base-url. Set MW_USERNAME/MW_PASSWORD only if you want the write tools (they stay hidden otherwise).

Claude (.mcp.json)

{
  "mcpServers": {
    "mediawiki": {
      "command": "mediawiki-mcp",
      "args": ["--transport=stdio", "--mw-base-url=https://wiki.example.org"],
      "env": {
        "MW_USERNAME": "<bot>",
        "MW_PASSWORD": "<bot password, optional for write>"
      }
    }
  }
}

Codex (~/.codex/config.toml)

[mcp_servers.mediawiki]
command = "mediawiki-mcp"
args = ["--transport=stdio", "--mw-base-url=https://wiki.example.org"]
env = { MW_USERNAME = "...", MW_PASSWORD = "..." }

opencode (opencode.json)

{
  "mcp": {
    "mediawiki": {
      "type": "local",
      "command": ["mediawiki-mcp", "--transport=stdio", "--mw-base-url=https://wiki.example.org"],
      "enabled": true
    }
  }
}

Container / quadlet

podman build -t mediawiki-mcp .

The final image is FROM scratch, CGO_ENABLED=0, USER 65534, with a CA bundle for TLS. Quadlet units: mediawiki-mcp.container + mediawiki-mcp.network (Options=mtu=1500 is required for rootless podman). The default port is :8094.