discourse-mcp
# discourse-mcp
A small **MCP stdio server** that gives an AI agent tools to read and act on a
**single Discourse forum** — a focused, dependency-free Python fork of the
official [`@discourse/mcp`](https://github.com/discourse/discourse-mcp).
- **Python stdlib only.** No Node, no npm, no third-party packages. Talks to
Discourse over `urllib`, speaks MCP (JSON-RPC 2.0, newline-delimited) over
stdio.
- **Single-site, one identity.** Credentials come from the environment; there
are no profiles, no multi-site `auth_pairs`, and no `discourse_select_site`
bootstrap. A bot serves one forum as one user.
- **The forum tools a participant needs — plus the ones `@discourse/mcp`
lacks:** reading, posting, and private messages, *and* liking, custom-emoji
reactions, bookmarking, watch/track/mute, and reading the bot's own
notifications.
Built as the agent's "hands" for [discourse-acp](../discourse-acp) (an ACP
harness), but it is a plain MCP server — any MCP client can spawn it.
## Install
```sh
pip install . # or: pipx install .
```
Installs a `discourse-mcp` console script.
## Configure (environment)
| Env var | Required | Meaning |
|---|---|---|
| `DISCOURSE_URL` | yes | forum base URL, e.g. `https://forum.example.com` |
| `DISCOURSE_API_KEY` | yes | API key for the bot account |
| `DISCOURSE_API_USERNAME` | yes | the bot's username (`Api-Username`) |
| `DISCOURSE_MCP_ALLOW_WRITES` | no (default `0`) | `1` to enable write tools (post/like/bookmark/…) |
| `DISCOURSE_MCP_MAX_READ_LENGTH` | no (default `50000`) | truncate raw post bodies to this many chars |
| `DISCOURSE_MCP_DEFAULT_SEARCH` | no | prefix prepended to every search query |
A global admin key acts as `api_username`; a per-user key acts as that user.
Either way the bot posts as itself — there is no acting-user impersonation.
## Run
```sh
discourse-mcp # run the MCP server on stdio (what an MCP client spawns)
discourse-mcp selftest # drive initialize + tools/list + tools/call, no network
discourse-mcp tools # print the full tool catalog as JSON
```
An MCP client (e.g. Claude Code, or `claude-agent-acp`) spawns it as a stdio
server. Example client config:
```json
{ "command": "discourse-mcp",
"env": { "DISCOURSE_URL": "https://forum.example.com",
"DISCOURSE_API_KEY": "…", "DISCOURSE_API_USERNAME": "mybot",
"DISCOURSE_MCP_ALLOW_WRITES": "1" } }
```
## Tools
All names are prefixed `discourse_`. Read tools are always available; **write
tools appear only when `DISCOURSE_MCP_ALLOW_WRITES=1`**. Read tools return a
compact projection (not raw Discourse JSON) and truncate bodies to
`DISCOURSE_MCP_MAX_READ_LENGTH`.
**Reads:** `search`, `read_topic`, `read_post`, `get_user`, `list_categories`,
`list_notifications` (the bot's own feed — a side-effect-free peek),
`read_private_message`, `list_private_messages`.
**Writes:** `create_post`, `create_topic`, `update_post`,
`create_private_message`, `reply_private_message`, `like_post`, `unlike_post`,
`react_post` (custom emoji; needs the discourse-reactions plugin), `bookmark`,
`delete_bookmark`, `watch_topic` (muted / regular / tracking / watching).
## Relation to `@discourse/mcp`
This is intentionally **not** a 1:1 port. The official server exposes ~100 tools
across many admin/opt-in toolsets (workflows, themes, webhooks, data-explorer,
AI, …). This fork keeps the surface a **conversational forum participant**
actually uses and adds the day-to-day **engagement** actions the official server
omits (likes/reactions/bookmarks/watch, own-notifications). Tool names and the
projected read shapes mirror `@discourse/mcp` where they overlap.
## MCP compliance
Protocol version `2025-06-18` (falls back gracefully to the client's requested
version). Implements `initialize`, `notifications/initialized`, `tools/list`,
`tools/call`, and `ping`. NDJSON stdio framing; stdout carries only protocol
messages, all logging goes to stderr.
TDQS
Scored across 8 tools
Each tool maps to a distinct resource/action: search, topic, post, user, categories, notifications, PM list, and PM content. The two PM tools are clearly list-vs-read, and read_private_message's error behavior clarifies its boundary.
Tool names consistently use snake_case and the discourse_ prefix, but the verb choice is slightly mixed across search, read, get, and list. This is mostly predictable, with minor deviations like discourse_get_user standing apart from the discourse_read_* content tools.
Eight tools is a well-scoped size for a read-only Discourse client. Each tool earns its place, covering public reading, search, user info, categories, notifications, and private messages without redundancy.
The read-only surface covers the main resources, but there is no direct way to list or browse topics, such as latest topics or topics within a category, so discovery depends heavily on search. Write/reply operations are also absent, which is consistent with a read-only intent but limits full forum workflows.