Skip to main content
Glama

nodebb-plugin-mcp

MCP for a NodeBB forum, served by the forum itself. Each person connects their AI client (Claude Code, an IDE) with their own forum account and can then search the forum, pull answers out of existing threads, investigate problem reports, and work support tickets on a Shotef triage board — always with exactly their own forum permissions.

Works on a plain NodeBB forum; the search, Q&A and Shotef plugins each unlock more (see Plugins).

Install

Like any NodeBB plugin:

cd /path/to/nodebb
npm install github:heknon/nodebb-mcp     # or a local checkout's path; it builds itself on install
./nodebb activate nodebb-plugin-mcp
./nodebb build && ./nodebb restart

(It is not on the npm registry yet. Installing from GitHub needs read access to the repository.)

The forum now serves MCP at https://forum.example.com/api/mcp, inside NodeBB's own API. ACP → Plugins → MCP shows that address, the settings, and every connected app.

Related MCP server: Discourse MCP

Connect

claude mcp add --transport http forum https://forum.example.com/api/mcp

The first time the client connects, the browser opens the forum: the person signs in the way they always do (password, SSO, two-factor — whatever the forum uses) and sees "Claude Code wants to use the forum as you". Allow sends them back to the client, which is now connected as them. Nothing else to configure: no tokens to copy, no passwords through the MCP client.

Each person's own page, /mcp/connections, lists their connected apps with a Disconnect button, and shows the command above.

Tools

What a tool may change decides when it is available:

Changes

Available

Who may use it

nothing

always

anyone the forum lets read the content

ticket — Shotef team state

always, when Shotef is installed

members of the team handling the ticket, and administrators (Shotef checks)

public — what forum readers see

only with Public writes on (ACP → Plugins → MCP; NODEBB_ENABLE_WRITES=1 locally)

the connected person, within their NodeBB privileges

Tool

Does

Changes

Needs

forum_capabilities

Who you are on the forum, which plugins are live, what is missing

nothing

—

find_answers

Topics that already answer a question, with the answer from each (labelled accepted or merely upvoted) and its ticket stage

nothing

—

investigate_issue

Existing answers, still-open reports, triage state, and next steps for a problem report

nothing

—

search_forum

Full-text search with snippets, flagging hits that have a ticket

nothing

a search plugin and a signed-in user (otherwise a recent-titles scan)

get_topic

A thread in full, accepted answer and triage stage marked

nothing

—

get_topic_answer

The answer to one topic

nothing

Q&A for an accepted answer (otherwise the top reply, labelled)

get_post

One post with its topic

nothing

—

list_categories

Category tree with ids

nothing

—

list_recent_topics

Recent, popular, top or unread topics, forum-wide or for one category, with ticket stages

nothing

a signed-in user for unread

list_unanswered_questions

Questions nobody has answered

nothing

Q&A (otherwise topics with no replies)

get_triage_status

A topic's stage: received, in progress, answered, closed

nothing

Shotef

get_triage_board

A team's board by column, plus the status keys set_ticket_status takes

nothing

Shotef, team member

search_tickets

Find tickets on every board you can see: by words, lane (active, backlog, intake, closed), status, owner, priority, help wanted, Q&A state, tag, age, reporter silence

nothing

Shotef, team member

get_ticket

A ticket with its history and internal notes

nothing

Shotef, team member

add_ticket_note

Leave an internal note only the team sees

ticket

Shotef, team member

set_ticket_status

Move a ticket, or close it as resolved or no-response

ticket

Shotef, team member

assign_ticket

Claim, release, or assign a ticket. Claiming a ticket in the intake lane (a category two teams share, taken by neither) takes it for your team

ticket

Shotef, team member (manager to assign someone else)

set_ticket_priority

Set priority and the help-wanted flag

ticket

Shotef, team member

pass_ticket

Hand a ticket to another team

ticket

Shotef, team member

create_topic

Start a topic, optionally as a Q&A question

public

Public writes

reply_to_topic

Post a reply

public

Public writes

edit_post

Replace a post's text

public

Public writes

vote_post

Upvote, downvote, or withdraw a vote

public

Public writes

mark_solved

Accept a reply as a question's answer

public

Public writes, Q&A, Shotef, team member

Ticket changes are not entirely invisible: the reporter sees the coarse stage ("in progress", "closed") and Shotef notifies them when it changes. Notes and the ticket history stay with the team. To keep a server off the board entirely, give it a token for a user who is on no triage team and is not an administrator (Shotef treats administrators as members of every team).

Names. Teams, categories and assignees can be given by name instead of id, and spelling need not be exact. "suport", "the billing team", "S&O" or "generel discusion" all find what was meant. A lookup takes the closest unique match and its output says what it matched. A change (pass_ticket, assign_ticket, create_topic) needs the exact name, ignoring case and punctuation. A near miss there is answered with "did you mean …?" and nothing changes, because a wrong guess would move someone's ticket. A name that fits several ("general", with both General Discussion and E2E General Discussion) is never guessed: the options are listed.

Prompts: answer_forum_question and investigate_report chain the read tools; work_ticket takes a ticket from report to closed (with public writes off, it drafts the reply into a note for a person to post).

How access works

  • Each connection is the person. Allowing an app creates a NodeBB API token for that person (listed in ACP → Settings → API Access as "MCP: Claude Code (alice)"). Every tool call goes through the forum's own API with that token, so NodeBB checks it like any request of theirs: category privileges, bans, Shotef's team rules.

  • Ending access. The person disconnects the app on /mcp/connections; an administrator revokes it in ACP → Plugins → MCP, or deletes its API token; or the client revokes its own token. Any of these cuts it off on the next request.

  • Standard OAuth. The forum is the authorization server: discovery (/.well-known/oauth-authorization-server/api/mcp), dynamic client registration, PKCE, one-time codes, hourly access tokens and rotating refresh tokens, all stored hashed in the forum's database. Nothing outside the forum is involved, so it works air-gapped.

  • Any number of forum processes. The MCP endpoint keeps no session between requests, and all state is in the database, so a multi-process forum works as-is.

Settings (ACP → Plugins → MCP):

Setting

Default

Effect

Public writes

off

Lets connected apps post topics and replies, edit posts, vote and accept answers, as the person and within their privileges. Shotef ticket actions do not need it. Takes effect on the next request, in every forum process.

Before going live:

  • HTTPS. OAuth requires the forum to be served over https:// (plain http is accepted only on localhost).

  • Forums under a subpath (https://example.com/forum): OAuth discovery lives at the origin root, so the reverse proxy must also pass /.well-known/oauth-authorization-server/forum/api/mcp and /.well-known/oauth-protected-resource/forum/api/mcp to NodeBB, as they are (the plugin answers them at the root; no rewriting).

  • Rate limits. Client registration, authorization and token requests are limited per IP (the MCP SDK's defaults: 20 registrations an hour). Behind a proxy, set NodeBB's trust_proxy so that is per person, not per proxy.

  • Content-Security-Policy. If you turned on NodeBB's CSP, its form-action must allow the clients' callback addresses (e.g. http://localhost:* for Claude Code), or the browser blocks the way back after Allow.

Running it locally instead (stdio)

Without installing anything on the forum, one person can run the same tools on their own machine, acting as one forum account (npm install && npm run build in a checkout first):

{
  "mcpServers": {
    "forum": {
      "command": "node",
      "args": ["/path/to/nodebb-mcp/dist/index.js"],
      "env": {
        "NODEBB_URL": "https://forum.example.com",
        "NODEBB_USERNAME": "support-bot",
        "NODEBB_PASSWORD": "…"
      }
    }
  }
}

Variable

Default

Purpose

NODEBB_URL

required

Forum root, e.g. https://forum.example.com (a subpath is fine)

NODEBB_USERNAME, NODEBB_PASSWORD

—

The account to sign in as. Without credentials the server is a guest: public content only, and no search

NODEBB_API_TOKEN

—

Alternative to a password (ACP → Settings → API Access). Wins if both are set

NODEBB_UID

—

User to act as. Required with a master token; ignored otherwise

NODEBB_ENABLE_WRITES

off

1 / true enables the public tools

NODEBB_TIMEOUT_MS

15000

Per-request timeout

NODEBB_MAX_POSTS_PER_TOPIC

50

Posts read per topic

NODEBB_CAPABILITY_TTL_MS

300000

How long plugin detection is cached

NODEBB_SHOTEF_NAMESPACE

shotef

Shotef's route prefix under /api/v3/plugins/

NODEBB_SESSION_FILE

per-user cache file

Where the signed-in session is kept (~/.cache/nodebb-mcp/, owner-only); off keeps it in memory

NODEBB_SESSION_CHECK_MS

30000

How often an idle session is re-checked before a Shotef or other Write API read

Signing in with a password. The server keeps the session cookie, sends the CSRF token on writes, and signs in again by itself when the session expires or is revoked. NodeBB limits sign-ins (by default, 5 failures an hour lock the account; 20 sign-ins an hour from one IP lock the IP), so new processes reuse the cached session, a wrong password is tried once and remembered for an hour, and the password itself is never written anywhere. The session shows on the account's sessions page as nodebb-mcp 0.2.0 on Linux. Two-factor accounts need an API token instead.

Check who you are. NodeBB does not reject an invalid or revoked token; it quietly treats the caller as a guest. forum_capabilities shows the user the forum actually resolved, and flags credentials it refused or ignored.

Plugins

None are required. When one is missing, the tools still answer and say in their output what they could not see.

Plugin

With it

Without it

Search: nodebb-plugin-dbsearch (on by default in NodeBB v4)

Ranked search over titles and post bodies, for a token's user (guests lack the search privilege by default)

A scan of recent topic titles, flagged as such

nodebb-plugin-question-and-answer

Accepted answers, the unsolved queue, mark_solved

The most-upvoted reply, labelled not marked accepted; the queue becomes topics without replies

Shotef

Triage stage, board, tickets, and the ticket tools

Triage is reported as unknown

The ticket tools need Shotef's HTTP API (/api/v3/plugins/shotef/tickets/…, see its README). On a build without it, forum_capabilities reports triage actions as absent and says why, and the ticket tools refuse before sending anything. Such builds also ignore API tokens: the board then reads as empty for a token, but works when the server signs in with NODEBB_USERNAME / NODEBB_PASSWORD.

An unconfirmed reply is never presented as an accepted answer, and every refusal from the forum comes back with its reason and what to do about it.

Development

npm run check      # build + unit tests (fake forum, no NodeBB needed)
npm run e2e        # build, seed and test against a real NodeBB; provisions one if missing
  • Unit tests (tests/*.test.js) run against tests/fake-forum.js, an HTTP model of NodeBB 4.16 with the three plugins.

  • End-to-end tests (tests/e2e/) run against a real forum seeded by e2e/seed.mjs, which runs this checkout as nodebb-plugin-mcp (symlinked, like Shotef). The plugin suites connect through the forum's real OAuth, login and Connect page — one of them in headless Chromium, ACP included; the others drive the stdio server once per forum identity. They run once with all plugins on and once with them off. e2e/forum.sh starts, stops, rebuilds and inspects that forum.

Layout: src/plugin/ is the NodeBB side (routes, OAuth provider, database store), library.cjs + plugin.json + templates/ + public/ are what NodeBB loads, and everything else in src/ is the MCP server both modes share.

When the two disagree, the real forum wins and the fake is fixed. CLAUDE.md covers this container; .claude/skills/nodebb-mcp-dev/ covers NodeBB's API quirks and how to add a tool.

Plugin-authoring docs

The server also serves a guide to writing NodeBB plugins (v3/v4) as MCP resources, distilled from the Shotef plugin:

URI

Covers

nodebb://plugin-dev/overview

Mental model, plugin anatomy, the five rules

nodebb://plugin-dev/architecture

The nbb loader, layout, lifecycle, clustering, notifications

nodebb://plugin-dev/hooks

static: / filter: / action: hooks and how they fire

nodebb://plugin-dev/endpoints

Page routes, /api/v3/plugins routes, socket.io

nodebb://plugin-dev/data-layer

The db abstraction, key design, caching

nodebb://plugin-dev/client-acp-templates

Client scripts, ACP modules, templates, languages, CSS

nodebb://plugin-dev/gotchas

17 traps as symptom → cause → fix

nodebb://plugin-dev/testing

node:test units, seeding, HTTP and browser checks

nodebb://plugin-dev/recipes

Scaffolding, dev environment, snippets

nodebb://plugin-dev/worked-example

An annotated tour of a real plugin

They live in resources/ as plain markdown. To add one, write the file and add an entry to resources/index.json.

License

MIT

Available Tools

12 tools
find_answersFind existing answersA
Read-only

Find topics that already answer a question, and extract the answer from each. Prefers author-accepted answers when the Q&A plugin is installed, and falls back to the strongest reply otherwise — always labelling which it used, so an unconfirmed reply is never presented as a confirmed answer. This is the first tool to reach for when someone asks a question the forum may have seen before.

Inputs: question (required), tags, depth, categories, +1 more.

forum_capabilitiesForum capabilitiesA
Read-only

Report which forum features this server can actually use: reachability, authentication, full-text search, the Q&A plugin (accepted answers), and the Shotef triage plugin. Call this first when a result looks thin or a tool reports a feature as unavailable — it explains what is missing and what would enable it.

Inputs: refresh.

get_postRead a single postA
Read-only

Read one post by its id, with the topic it belongs to. Use when a search result or a link points at a specific reply and you only need that reply.

Inputs: pid (required).

get_topicRead a topicA
Read-only

Read a topic in full: the original post, replies in order, and — when the relevant plugins are installed — which reply is the accepted answer and where the topic sits on the triage board. This is the tool for understanding a reported issue in the reporter's own words.

Inputs: tid (required), page, max_posts.

get_topic_answerGet a topic's answerA
Read-only

Get the answer to one specific topic. With the Q&A plugin this is the accepted answer, and an open question is reported as genuinely unanswered rather than guessed at. Without it, returns the most-upvoted reply, labelled as unconfirmed.

Inputs: tid (required).

get_triage_boardGet the triage boardA
Read-only

The team's triage board: every open ticket by workflow column, with priority, owner and age. Use it to see the current support workload, or to find which issues are stalled. Requires the Shotef plugin and a token belonging to a team member — a non-member gets an explanation rather than an error.

Inputs: team.

get_triage_statusGet triage statusA
Read-only

Where a topic stands in the support workflow: received, in progress, awaiting the reporter, or closed — plus which team handles it and whether it is parked. Answers "is anyone working on this, and was it ever resolved?". Requires the Shotef triage plugin; without it, reports that cleanly and suggests reading the topic instead.

Inputs: tid (required).

investigate_issueInvestigate an issueA
Read-only

The full sweep for a reported problem: find every related topic, extract the answers that already exist, separate them from the reports still open, fold in triage status, and end with concrete next steps. Use this when someone reports a problem and you need to know whether the forum has seen it before, whether it was solved, and whether anyone is already on it. Slower than find_answers because it opens several topics — prefer find_answers for a plain question.

Inputs: issue (required), tags, depth, categories, +2 more.

list_categoriesList categoriesA
Read-only

List the forum's category tree with ids, descriptions and topic counts. Use it to find the category id to pass to search_forum, list_recent_topics, or investigate_issue.

Inputs: no named parameters.

list_recent_topicsList recent topicsA
Read-only

Browse topic listings — recent, popular, top, or unread — optionally inside one category. Useful for getting a feel for what is being reported lately, and as the way to explore a forum that has no search plugin.

Inputs: cid, page, limit, source.

list_unanswered_questionsList unanswered questionsA
Read-only

List questions nobody has answered yet — the queue for someone who wants to help. With the Q&A plugin this is the forum's real unsolved list; without it, recent topics that have no replies, which approximates it but cannot tell a question from a discussion. Shows triage status per item when the Shotef plugin is installed.

Inputs: page, limit.

search_forumSearch the forumA
Read-only

Full-text search across topic titles and post bodies. Returns ranked matches with a snippet showing why each matched. Requires a search plugin on the forum (NodeBB core has none); when one is absent this falls back to scanning recent topics by title and says so. For answering a question, prefer find_answers, which also extracts the answers.

Inputs: query (required), in, page, tags, +5 more.

Tool Schema Changelog

Recent tool additions, removals, and schema changes observed during successful MCP inspections.

Loading tool schema changelog…

    TDQS

    A4.1/5.0

    Scored across 12 tools

    Disambiguation4/5

    There is meaningful overlap in the search/answer cluster (search_forum, find_answers, investigate_issue, get_topic_answer), but the descriptions explicitly cross-reference and route the agent (e.g. 'prefer find_answers', 'prefer find_answers for a plain question'). get_topic vs get_post and triage status vs board are cleanly separated, so an agent can reliably pick.

    Naming Consistency4/5

    Almost everything follows a verb_noun pattern: get_post, list_categories, search_forum, get_topic, find_answers, investigate_issue, get_triage_status, get_triage_board. The lone deviation is forum_capabilities (noun-only, no verb), but it stays snake_case and readable.

    Tool Count5/5

    12 tools is well within the ideal band and each earns its place by covering a distinct read/triage action. No redundant or filler tools are apparent.

    Completeness4/5

    The set covers reading posts, topics, categories, listings, search, answer extraction, unanswered queues, and triage status/board — a coherent read-and-triage surface. It is read-only, so any posting/replying/updating workflow the domain might need is absent, but that appears intentional and is not a dead end for the stated purpose.

    Related MCP Connectors

    Related MCP Servers