DiscourseBLOCK
MCP client for Discourse sites
Overview
From the repository's own README, as read at the audited commit. Badges and raw HTML are left out.
Discourse MCP
A Model Context Protocol (MCP) stdio server that exposes Discourse forum capabilities as tools and resources for AI agents.
- Entry point:
src/index.ts→ compiled todist/index.js(binary name:discourse-mcp) - SDK:
@modelcontextprotocol/sdk - Node: >= 24
- Version: 0.3.1 (simplifies write opt-in so
--allow_writesis sufficient and deprecatesread_only=false; 0.3.0 added operator-selectable toolsets, structured directory output, and expanded opt-in administration capabilities; 0.2.x introduced breaking changes from 0.1.x, including JSON-only tool output; category/group resources remain deprecated compatibility surfaces alongside canonical list tools)
Quick start (release)
- Run (read‐only, recommended to start)
npx -y @discourse/mcp@latest
Then, in your MCP client, either:
- Call the
discourse_select_sitetool with{ "site": "https://try.discourse.org" }to choose a site, or - Start the server tethered to a site using
--site https://try.discourse.org(in which casediscourse_select_siteis hidden).
- Enable writes (opt‐in, safe‐guarded)
npx -y @discourse/mcp@latest --allow_writes --auth_pairs '[{"site":"https://try.discourse.org","api_key":"'$DISCOURSE_API_KEY'","api_username":"system"}]'- Run with only Data Explorer built-in tools
npx -y @discourse/mcp@latest --toolsets data_explorer --tools_mode discourse_api_only
This exposes discourse_select_site plus the read-only Data Explorer tools. Add --site, authentication, and the write flags as needed; see Built-in toolsets.
- Use in an MCP client (example: Claude Desktop) — via npx
{
"mcpServers": {
"discourse": {
"command": "npx",
"args": ["-y", "@discourse/mcp@latest"],
"env": {}
}
}
}Alternative: if you prefer a global binary after install, the package exposes discourse-mcp. ```json { "mcpServers": { "disco49c8468d24c1OBSERVED · 2026-10-07Connect
Built from this server's own package name, version and transport as found in its source — not copied from anyone's documentation, so it cannot drift against a page we do not control.
claude mcp add mcp -- npx -y @discourse/[email protected]
Exposed tools (142)
83 read · 47 write · 12 destructive. Blast radius: 12 tools can delete or overwrite — an agent that can be talked into calling a tool can be talked into calling this one.
| Tool | Risk | Description |
|---|---|---|
Agent | read | Description |
Title | read | Forum title |
alice | read | Most Recent Poster |
always_first | read | Always available. |
discourse_ai_create_agent | write | Create a typed Discourse AI agent, including an optional subagent delegation allowlist of up to 20 existing agent IDs. Requires admin credentials and write mode. Tool entries may be names or [name, options, force] tuples. |
discourse_ai_create_agent_user | write | Explicitly create the bot user used by an AI agent for mention/chat entry points. Requires admin credentials and write mode. |
discourse_ai_create_custom_tool | write | Create a database-backed synchronous MiniRacer scripted tool. Call discourse_ai_get_custom_tool_guide before authoring. Never provide raw secret values. Requires admin credentials and write mode. |
discourse_ai_delete_agent | destructive | Delete a non-system AI agent. Requires admin credentials and write mode. |
discourse_ai_delete_custom_tool | destructive | Delete a scripted tool. Agents referencing it may stop functioning as configured. Requires admin credentials and write mode. |
discourse_ai_export_agent | read | Export a portable AI agent bundle. It may include referenced custom-tool definitions but excludes secret bindings. Treat prompts and scripts as sensitive. |
discourse_ai_export_custom_tool | read | Export portable custom-tool JSON. Discourse excludes secret bindings; MCP also strips them defensively. Treat script source as sensitive. Requires admin credentials. |
discourse_ai_get_agent | read | Get the full editable configuration of one Discourse AI agent. Custom tools use custom-<id>; native tools use native-<id> in tool tuples. |
discourse_ai_get_custom_tool | read | Get one database-backed scripted AI tool, including source and configured secret bindings. Treat the response as sensitive. Requires admin credentials. |
discourse_ai_get_custom_tool_guide | read | Retrieve one focused MiniRacer authoring topic. Usually pass only topic. preset_id is optional and used only with topic=presets to load one live example; extra preset_id values on other topics are ignored. Requires admin credentials. |
discourse_ai_get_feature_config | read | Resolve a visible feature module ID and return its computed state plus editable settings from its exact ai-features/<module> area. Credential settings are omitted. Requires admin credentials. |
discourse_ai_get_topic_summary | read | Retrieve an existing cached upstream Discourse AI topic summary, including algorithm and staleness evidence. Requires Discourse AI; this tool does not generate or claim complete MCP-authored output. |
discourse_ai_import_agent | destructive | Import a portable AI agent bundle. The bundle may create or overwrite embedded custom tools. force requires confirm_force=true. Requires admin credentials and write mode. |
discourse_ai_import_custom_tool | destructive | Import portable custom-tool JSON. force overwrites a matching tool_name and requires confirm_force=true. Secret IDs may be bound, but raw secret values are never accepted. Requires admin credentials and write mode. |
discourse_ai_list_agents | read | List concise Discourse AI agent summaries and slim tool/model metadata. Defaults to slim; use discourse_ai_get_agent to load one full configuration, or view=full only when the complete index is explicitly needed. Requires admin credentials. |
discourse_ai_list_custom_tools | read | List concise database-backed custom-tool summaries and compact live preset metadata. Scripts, bindings, verbose parameter descriptions, and full records are omitted; use discourse_ai_get_custom_tool or the guide |
discourse_ai_list_features | read | List visible computed Discourse AI modules, feature enabled state, assigned agents, and effective LLMs. Requires admin credentials. |
discourse_ai_list_sentiment_posts | read | List staff-visible posts classified positive, negative, or neutral by Discourse AI for an exact category/tag and date window. Classification is model output, not proof of tone, satisfaction, risk, or an argument. |
discourse_ai_semantic_search | write | Run Guardian-filtered Discourse AI semantic search and return bounded grouped post/topic evidence. Requires Discourse AI embeddings. Results are bounded and expose no fabricated page cursor. |
discourse_ai_test_custom_tool | write | Actually execute a persisted or unsaved custom-tool script. It can make external requests and cause side effects; this is not dry validation. Requires admin credentials and write mode. |
discourse_ai_update_agent | write | Partially update one AI agent, including enabled state, permissions, model, prompt, tools, subagent delegation, and RAG configuration. Only supplied fields are sent. Requires admin credentials and write mode. |
discourse_ai_update_feature_config | write | Atomically bulk-update only non-secret settings freshly exposed by one exact AI feature area, with optional compare-before-write original_values. Refreshes effective state afterward. Production behavior changes immediately. Requires admin credentials and write mode. |
discourse_create_category | write | Create a new category. Returns JSON with id, slug, and name. |
discourse_create_group | write | Create a custom group with complete core settings, initial members and owners, custom fields, associations, and optional plugin fields. Requires staff group-creation permission. |
discourse_create_post | write | Create a post in a topic. author_username requires a global API key. Returns the actual upstream author so callers can verify attribution. |
discourse_create_private_message | write | Create a private message for typed user, group, or email recipients. Unknown emails may create staged users. author_username requires a global API key; the response reports actual attribution. |
discourse_create_query | write | Create a new saved Data Explorer query. Requires admin API key and write access. |
discourse_create_tag_group | write | Create a tag group with an explicit complete permission list such as permissions: [{group_id: 0, access: \ |
discourse_create_theme | write | Create a local Discourse theme or component. For textual HTML/SCSS/JS fields provide value and omit upload_id; for assets provide numeric upload_id with type=upload and omit value. Supplied code can execute or deploy for visitors; explicit confirmations and admin write access are required. |
discourse_create_topic | write | Create a new topic. author_username requires a global API key. Returns the actual upstream author so callers can verify attribution. |
discourse_create_user | write | Create a user through Discourse |
discourse_create_webhook | write | Create a guarded outbound webhook using the live event and content-type catalog. External delivery and risky options require explicit confirmation. |
discourse_create_workflow | write | Create a draft from a complete small graph or template. Prefer a complete graph over blank creation. ${authoringPlaybook} |
discourse_delete_draft | destructive | Delete a draft by key. Requires current sequence number to prevent conflicts. |
discourse_delete_group | destructive | Permanently delete a custom group. Automatic groups cannot be deleted. Requires staff access. |
discourse_delete_query | destructive | Soft-delete a Data Explorer query. The query can be restored by an admin. Requires admin API key and write access. |
discourse_delete_theme | destructive | Permanently delete one non-system, non-default theme/component after a fresh exact-name check. Dependencies are not detached automatically and bulk deletion is not exposed. |
discourse_delete_webhook | destructive | Delete one webhook after a fresh exact destination-fingerprint precondition. |
discourse_delete_workflow | destructive | Permanently delete a workflow. Fails when another workflow calls it. |
discourse_discard_workflow_draft | read | Replace the draft with its published snapshot. Requires an existing published version. |
discourse_evaluate_workflow_expression | read | Evaluate a workflow expression without mutating the forum. Interpolating expressions start with =. |
discourse_get_chat_messages | read | Get messages from a chat channel. Returns JSON object with channel_id, messages array (id, username, created_at, message, edited, thread_id, in_reply_to_id), and meta. |
discourse_get_draft | read | Retrieve a specific draft by key. Returns JSON with draft_key, sequence, and parsed data (title, reply, categoryId, tags, action). |
discourse_get_group | read | Get complete visible group details by numeric ID or name, including caller capabilities and group settings. |
discourse_get_post_replies | write | Read reply relationships for a post as recursive descendant IDs, bounded rich direct replies, or the upstream-bounded ancestor history. Preserves upstream ordering and references. |
discourse_get_post_revision | write | Get a staff/Guardian-authorized post revision with bounded diffs, edit reason, actor, version, and hidden-state evidence. |
discourse_get_query | read | Get full details of a Data Explorer query including SQL and parameters. Requires admin API key. |
discourse_get_report | write | Execute one discovered staff-visible Discourse report with dates, facets, typed filters, limit, and upstream caching. This exposes existing reports, not arbitrary SQL. |
discourse_get_review_queue_count | read | Get the authoritative count of pending reviewable records visible to the authenticated user. This counts queue items, not individual flag/score records; upstream Guardian permissions remain authoritative. |
discourse_get_reviewable | read | Refresh one shortlisted reviewable |
discourse_get_support_dashboard | read | Get the staff Solved support dashboard. Requires Discourse Solved. |
discourse_get_tag_group | read | Get authoritative tag-group state by ID. Requires a locally configured API credential and upstream staff authority. The deterministic state_hash is an MCP optimistic precondition, not an upstream atomic lock; a race remains possible after preflight. |
discourse_get_theme | read | Inspect one local, ZIP-imported, or Git-backed Discourse theme/component as an administrator. Source values are bounded, opt-in, and may be unavailable for remote records; private key material is never returned. |
discourse_get_topic_view_stats | read | Get up to 300 ascending daily topic view counts. Views combine anonymous and logged-in views; they are not unique viewers or an engagement judgment. |
discourse_get_user | read | Get user info. Returns JSON with id, username, name, trust_level, created_at, bio, admin, and moderator. |
discourse_get_user_moderation_summary | read | Get staff-authorized behavioral moderation counters for a user. Omits unrelated administrative capability flags; Discourse permissions remain authoritative. |
discourse_get_user_summary | read | Get profile-visible aggregate user activity, top topics/replies/links, interaction partners, categories, badges, likes, visits, reading, and post/topic counts. Missing metrics remain null when hidden or unavailable. |
discourse_get_webhook | read | Get one fresh safe webhook configuration for inspection and mutation preconditions. |
discourse_get_workflow | read | Get the complete round-trippable workflow graph. GET immediately before updating it. |
discourse_get_workflow_execution | write | Poll an execution for status, errors, timing, trigger data, and steps. |
discourse_handle_group_membership_request | read | Approve or deny one pending group membership request. Approval adds the requester to the group; denial only removes the request. |
discourse_invite_group_members_by_email | write | Add existing accounts immediately when an address matches, and invite unknown addresses to the forum with this group preassigned. Use add_group_members_by_email when every address is known to belong to an account. |
discourse_invite_to_private_message | write | Add a user or group to a private message, or submit an opaque email invitation. Email success does not confirm delivery or participant access. author_username requires a global API key. |
discourse_join_group | read | Join a visible group that allows public admission as the authenticated user. |
discourse_leave_group | read | Leave a group that allows public exit as the authenticated user. |
discourse_list_categories | read | Exhaustively list categories visible to the selected identity through bounded pagination. Returns stable IDs, slugs, hierarchy (parent_category_id; pid is a legacy alias), access flags, counts, structured completion metadata, and JSON text fallback. Opt in with the administration toolset. |
discourse_list_directory_items | read | List user-directory/cohort metrics with authoritative totals and continuation. Supports Discourse periods, ordering, visible groups, page 0-10, and the upstream maximum of 50 rows. |
discourse_list_group_members | read | List a group |
discourse_list_group_membership_requests | read | List pending membership requests and reasons for a group the caller can manage, with filtering and pagination. |
discourse_list_group_posts | read | List visible posts authored by a group using Discourse |
discourse_list_groups | read | List visible groups. Empty input exhaustively traverses the bounded directory; any explicit page or filter field preserves the existing single-page query behavior. Both modes return one structured envelope with truthful completion metadata and JSON text fallback. Opt in with the groups toolset. |
discourse_list_latest_posts | read | List the site |
discourse_list_private_messages | read | List authenticated personal or group private-message mailboxes. Returns normalized JSON messages and pagination metadata. |
discourse_list_reports | read | Discover staff-visible Discourse report identifiers and metadata. The catalog is authoritative because core and plugins can add or hide reports. |
discourse_list_reviewable_topics | read | Non-exhaustive topic-level signal only: list pending topics at or above Discourse |
discourse_list_site_settings | read | List admin-visible Discourse site settings with bounded metadata, optionally returning only settings whose current value differs from the default. Secret and credential-like values are always masked. |
discourse_list_themes | read | List admin-visible Discourse themes and components. This opt-in admin read returns bounded summaries and local pagination; it does not expose theme source code. |
discourse_list_user_actions | read | List a user |
discourse_list_user_posts | read | Get paginated list of user posts/replies. Returns JSON object with posts array (id, topic_id, post_number, slug, title, created_at, excerpt, category_id) and meta (page, limit, has_more). |
discourse_list_users | read | List users via admin API. Requires admin API key. Returns ~100 users per page (Discourse |
discourse_list_webhook_events | read | List bounded webhook delivery diagnostics. Payload and response previews are omitted unless explicitly confirmed. |
discourse_list_webhooks | read | List safe webhook summaries and the live authoring catalog without exposing webhook secrets or destination credentials. |
discourse_list_workflow_credentials | read | List workflow credentials with secrets redacted. Credential creation remains in the admin UI. |
discourse_list_workflow_executions | read | List global or per-workflow execution history. |
discourse_list_workflow_node_types | read | List a slim catalog, or pass identifier for schemas, ports, and output contracts. ${authoringPlaybook} |
discourse_list_workflow_templates | read | List templates or retrieve a complete template graph by id. ${authoringPlaybook} |
discourse_list_workflow_versions | read | List workflow version history and UUIDs available for restore. |
discourse_list_workflows | read | List compact Discourse Workflow summaries. Requires an admin API key. |
discourse_manage_user_activation | read | Activate, approve, activate-and-approve, or deactivate one user through Discourse |
discourse_perform_reviewable_action | read | Perform one exact dynamic action after a fresh serialized preflight. Choose by the action |
discourse_ping_webhook | read | Enqueue one explicitly confirmed external webhook ping after a fresh destination precondition. |
discourse_read_post | write | Read a specific post. Returns JSON with id, topic_id, post_number, username, created_at, and raw content. |
discourse_read_private_message | read | Read an authenticated private message, its posts, and direct allowed-user and allowed-group records. Rejects public topics. |
discourse_read_topic | write | Read topic metadata and posts. Large post limits can require multiple upstream requests. For moderation queues, prefer reviewable list/detail evidence instead of fanning this tool out across flagged topics. |
discourse_read_topic_posts | write | Read exact, earliest, latest, around-post, or username-filtered topic evidence. Selection is bounded to 50 posts and reports the visible stream size without claiming the entire topic was loaded. |
discourse_redeliver_webhook_event | read | Redeliver exactly one old webhook event after destination, ownership, and status preconditions. Bulk redelivery is not exposed. |
discourse_reply_private_message | read | Safely reply to an existing private message after verifying its archetype. author_username requires a global API key; the response reports actual attribution. |
discourse_request_group_membership | read | Request membership in a visible group that allows requests, sending the reason to its owners. |
discourse_resolve_workflow_entity | read | Resolve category, tag, group, user, badge, chat channel, or data-table ids for workflow parameters. |
discourse_restore_workflow_version | read | Restore a historical workflow version by UUID into the draft. |
discourse_run_query | write | Execute a Data Explorer query with parameters. Returns columns, rows, result_count, duration_ms. Queries run in read-only transactions with 10-second timeout. Requires admin API key. |
discourse_run_workflow | write | Run the current unpublished draft asynchronously. This can create posts, send chat, or call external HTTP; poll the returned execution id. |
discourse_run_workflow_step | write | Run one non-trigger, non-waiting draft node asynchronously. Requires upstream pin-data or a prior successful run. |
discourse_save_draft | write | Create or update a draft. Returns JSON with draft_key and new sequence number. |
discourse_search | read | Search site content. Returns JSON object with results array of matching topics (id, slug, title) and meta (total, has_more). |
discourse_search_posts | write | Search post-level evidence with Discourse query syntax. Unlike discourse_search, this preserves matched posts, highlighted blurbs, authors, topics, categories, and truthful bounded continuation. This is keyword search, not Discourse AI semantic search. |
discourse_select_site | read | Validate and select a Discourse site. Returns JSON with site URL and title. |
discourse_sync_remote_theme | write | Check or install updates for an existing Git-backed theme. Both actions clone external code and persist state; update deploys code and runs settings migrations, requiring explicit confirmations. |
discourse_update_group | write | Update all core group profile, visibility, admission, notification-default, association, email, SMTP, custom, and plugin settings supported by the caller and group type. |
discourse_update_post | write | Update the content of an existing post. Returns JSON with updated post details. |
discourse_update_query | write | Update an existing Data Explorer query. Only provided fields are updated. Requires admin API key and write access. |
discourse_update_site_setting | destructive | Set or reset one ordinary admin-visible Discourse site setting after a fresh optimistic-concurrency check. Secret and structured settings are excluded. |
discourse_update_theme | write | Update bounded metadata, default status, or complete theme-component relationships. Relationship arrays replace the entire graph side; source, settings, translations, and remote refresh are intentionally excluded. |
discourse_update_theme_fields | destructive | Replace or explicitly delete selected fields on a local or ZIP-imported theme. Text replacements require value and prohibit upload_id; asset replacements require numeric upload_id with type=upload and prohibit value. This can deploy executable code; Git-backed and system themes are rejected. |
discourse_update_theme_setting | write | Set or explicitly revert one declared setting owned by a theme/component. This does not mutate themeable global site settings; object values are validated upstream against the declared schema. |
discourse_update_theme_translations | write | Replace values for explicit translation keys declared by a theme, after a fresh locale read. Unknown keys are rejected; deletion/revert is not supported in this release. |
discourse_update_topic | write | Update an existing topic (title, category, tags, featured_link). Returns JSON with updated topic details. |
discourse_update_user | write | Update user profile fields. If upload_id is provided, also sets the user |
discourse_update_webhook | write | Partially update a webhook after fresh destination and active-state preconditions. Relationship arrays are complete replacements. |
discourse_update_workflow | write | GET first. A replace PUT replaces the whole draft graph; omitted nodes are deleted. Use complete nodes+connections or operations[], never both. ${authoringPlaybook} |
discourse_update_workflow_pin_data | write | Pin test items to a node name, or omit items to unpin it. |
discourse_upload_file | write | Upload an image or file to Discourse. Provide either: image_data (base64 with filename), a remote HTTP(S) URL, or an absolute local file path. user_id is required for avatar/background uploads. Returns upload_id for use in avatar/profile updates. Use short_url to embed images in posts. |
discourse_upload_theme_asset | write | Upload a bounded local/base64 asset for later attachment to a theme field. No remote URL fetch is supported; upload alone does not attach or deploy the asset. |
fixture.remote | read | Remote fixture |
fixture_empty_toolsets | read | Proves every definition belongs to at least one toolset. |
fixture_inferred | read | Exercises inferred handler input. |
fixture_invalid_structured | read | Proves incorrect structured output fails project typecheck. |
fixture_invalid_toolset | read | Proves toolset names are checked while authoring. |
fixture_other | read | Has a heterogeneous schema. |
fixture_structured | read | Checks schema-linked structured output typing. |
forwarding_fixture | read | Forwards registration and invocation values unchanged. |
plain_registration_fixture | read | Does not advertise an output schema. |
selection_third | read | Site-selection gated. |
sensitive | read | D |
structured_registration_fixture | read | Advertises an output schema. |
t | read | D |
tool | read | Description |
write_second | write | Write gated. |
Trust audit
BLOCKgrade D · trust 69/100 Do not install this without reading the findings. The audit found something that could harm you or your machine.
| Layer | What it checks | Result |
|---|---|---|
| L0 | Provenance & inventory | PASS |
| L1 | Static analysis of the code | WARN |
| L2 | Instruction surface (what it tells the agent) | FAIL |
| L3 | Class-specific surface | WARN |
| L4 | Behavioural (sandbox) | SKIPPED |
What the source does
- Filesystem
- declared (1 observation(s))
- Network
- declared (4 observation(s))
- Shell
- none-observed
- Dependencies
- not all pinned
- Secrets in source
- none-found
Findings (13)
- Toolset selection does not bypass write safety. A selected write tool remains absent unless writes are enabled.
http: section("Synchronous backend-proxied HTTP requests", ["http.get(url, options?)", "http.post(url, options?)", "http.put(url, options?)", "http.patch(url, options?)", "http.delete(url, options?)",return [`http://localhost:${port}`, `http://127.0.0.1:${port}`, `http://[::1]:${port}`];discourse_ai_delete_agent, discourse_ai_delete_custom_tool, discourse_ai_import_agent, discourse_ai_import_custom_tool, discourse_delete_draft, discourse_delete_group, discourse_delete_query, discours
const indexPath = path.resolve(__dirname, "../../dist/index.js");
const packagePath = path.resolve(__dirname, "../../package.json");
const p = path.resolve(__dirname, '../../fixtures/try', name);
const indexPath = path.resolve(__dirname, '../../dist/index.js');
const indexPath = path.resolve(__dirname, '../../dist/index.js');
const closed = await fetch(`http://127.0.0.1:${port}/mcp`, {const health = await fetch(`http://127.0.0.1:${port}/health`);const health = await fetch(`http://127.0.0.1:${port}/health`);zod, @eslint/js, @types/node, eslint, typescript, typescript-eslint
Gates applied: instruction_override, no_behavioural_pass.
49c8468d24c1full audit observations/trust-audit/mcp-server/discourse__discourse-2.json · Report an issue / request a re-scanAudit history
Every audit this server has had. A grade with a past is a grade somebody is still checking.
| Date | Source | Verdict | Grade | Score | Change |
|---|---|---|---|---|---|
| 2026-10-07 | 49c8468d24c1 | BLOCK | D | 69 | first audit |
Questions
What is the Discourse MCP server?
MCP client for Discourse sites
What tools does Discourse expose?
142 in total: 83 read-only, 47 that write, and 12 that can delete or overwrite (discourse_ai_delete_agent, discourse_ai_delete_custom_tool, discourse_ai_import_agent, discourse_ai_import_custom_tool, discourse_delete_draft). Every one is listed on this page with its risk.
Is Discourse safe to connect to an agent?
No — not without reading the findings first. The audit graded it D (69/100) and found 1 critical or high issue in the source. Each one is listed on this page with the file and line it is on. Separately from the audit: 12 of its tools can destroy data, so scope the token you give it to what you actually need.
What credentials does Discourse need?
No credential environment variables were found in its source, so it appears to need none.
How does Discourse run?
It speaks stdio and streamable-http, so it runs as a local process your client starts. It is published on npm as @discourse/mcp at 0.3.1.
How current is this page?
The grade is for one exact copy of the source (49c8468d24c1), read on 2026-10-07. The repository is watched and re-audited when it changes.