Discourse MCP
OfficialThe Discourse MCP server enables AI agents to interact with Discourse forums through a standardized Model Context Protocol interface.
Core Capabilities:
Site Management: Select and validate any Discourse forum by URL (validates via
/about.json). Supports single-site tethering or dynamic site selection.Content Discovery:
Full-text search with configurable result limits (1-50 results) and private content support
Advanced topic filtering with a query language supporting categories (OR/AND logic, subcategories, exclusions), tags (OR/AND operations, tag groups), status filters, personal filters (bookmarked/watching/tracking), date filters (created/activity with absolute or relative dates), numeric filters (likes/posts/views with thresholds), and flexible sorting
List categories, tags, and get user profiles
Permission-aware results respecting user authentication
Content Reading:
Read topics with metadata and configurable post limits (up to 100, with optional start position)
Read individual posts by ID
Retrieve chat messages with flexible pagination
List public and user-specific chat channels
Draft Management: List, retrieve, save, and delete drafts
Write Operations (opt-in, requires authentication and
--allow_writesflag):Create posts, topics, categories, and users
Authentication & Configuration:
Supports Admin API Keys (full control) and User API Keys (personal operations)
Profile-based configuration for secure credential management
Multiple transport options (stdio or HTTP with configurable port)
Configurable logging levels, timeouts, concurrency, and content length limits
Safety & Resilience:
Read-only by default
Rate limiting (~1 req/sec for writes)
Built-in retry logic with backoff
In-memory GET cache
Privacy-focused logging with secret redaction
Optional remote tool discovery (can be disabled)
Deployment: Run via npx for quick testing or global installation with flexible command-line configuration.
Provides comprehensive tools for interacting with Discourse forum platforms, including searching posts, reading topics and posts, managing categories and tags, retrieving user information, filtering topics with advanced query syntax, and creating posts/categories when write permissions are enabled.
Click on "Install Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@Discourse MCPsearch for recent posts about AI integration"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
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/sdkNode: >= 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@latestThen, in your MCP client, either:
Call the
discourse_select_sitetool with{ "site": "https://try.discourse.org" }to choose a site, orStart 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_onlyThis 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.{ "mcpServers": { "discourse": { "command": "discourse-mcp", "args": [] } } }
Related MCP server: USCardForum MCP Server
Configuration
The server registers tools under the MCP server name @discourse/mcp. Choose a target Discourse site either by:
Using the
discourse_select_sitetool at runtime (validates via/about.json), orSupplying
--site <url>to tether the server to a single site at startup (validates via/about.jsonand hidesdiscourse_select_site).Auth
None by default.
Admin API Keys (require admin permissions):
--auth_pairs '[{"site":"https://example.com","api_key":"...","api_username":"system"}]'User API Keys (any user can generate):
--auth_pairs '[{"site":"https://example.com","user_api_key":"...","user_api_client_id":"..."}]'HTTP Basic Auth (for sites behind a reverse proxy): Add
http_basic_userandhttp_basic_passto anyauth_pairsentry. This is useful for Discourse sites protected by HTTP Basic Authentication at the reverse proxy level.You can include multiple entries in
auth_pairs; the matching entry is used for the selected site. If bothuser_api_keyandapi_keyare provided for the same site,user_api_keytakes precedence.
Write safety
Writes are disabled by default.
Built-in write tools are only registered when
--allow_writesis enabled. This includes post, topic, private-message, category, user, upload, draft, and saved Data Explorer query mutations.Private-message listing and reading also require a matching authenticated site because PM data is never public.
Toolset selection does not bypass write safety. A selected write tool remains absent unless writes are enabled.
Write tools require a matching
auth_pairsentry for the selected site; otherwise they return an error.A ~1 req/sec rate limit is enforced for write actions.
Flags & defaults
--help,-h, or positionalhelp: print current CLI help and exit successfully before loading profiles or starting a transport.--version,-v, or positionalversion: print one package-version line and exit successfully.-vmeans version; logging verbosity uses--log_level.--allow_writes(default: false): enable mutation tools. This single explicit opt-in is sufficient.--read_only <boolean>: deprecated compatibility setting.trueis an explicit read-only override and conflicts with--allow_writes;falsehas no effect and should be removed from commands and profiles.--timeout_ms <number>(default: 15000)--concurrency <number>(default: 4)--log_level <silent|error|info|debug>(default: info)debug: Shows HTTP request URLs, statuses, and detailed network/retry information (response bodies are never logged because admin APIs may echo sensitive content)info: Shows retry attempts and general operational messageserror: Shows only errorssilent: No logging output
--show_emails(default: false). includes emails in user tools. Requires admin access--tools_mode <auto|discourse_api_only|tool_exec_api>(default: auto)--toolsets <name[,name...]>: Expose selected built-in domains. Omit for the compact default catalog (all non-opt-in domains); use--toolsets allto include opt-in category/group/tag-group, moderation, workflow, and AI administration domains. See Built-in toolsets.--site <url>: Tether MCP to a single site and hidediscourse_select_site.--default-search <prefix>: Unconditionally prefix every search query (e.g.,tag:ai order:latest).--max-read-length <number>: Maximum characters returned for post content (default 50000). Applies todiscourse_read_postand per-post content indiscourse_read_topicanddiscourse_read_private_message. The tools preferrawcontent by requestinginclude_raw=true.--allowed_upload_paths <paths>: Comma-separated list or JSON array of directories allowed for local file uploads. Required to enable local file uploads indiscourse_upload_file. Example:--allowed_upload_paths "/home/user/images,/tmp/uploads"or--allowed_upload_paths '["/home/user/images"]'. These security-sensitive paths do not receive~expansion.--transport <stdio|http>(default: stdio): Use standard input/output by default, or loopback-only Streamable HTTP with JSON responses. HTTP explicitly supports one stateful MCP client/session per process. Every post-initialize request must carry the returnedMcp-Session-Id; a second initialize is rejected. After session DELETE/close, restart the process before connecting another client./healthreturns503 restart_requiredin that closed state. Request bodies are bounded to 4 MiB.--port <number>(default: 3000): Port to listen on when using HTTP transport.--cache_dir <path>(reserved)--profile <path.json>(see below)
Profile file (keep secrets off the command line)
{
"auth_pairs": [
{
"site": "https://try.discourse.org",
"api_key": "<redacted>",
"api_username": "system"
},
{
"site": "https://example.com",
"user_api_key": "<user_api_key>",
"user_api_client_id": "<client_id>"
},
{
"site": "https://protected.example.com",
"api_key": "<redacted>",
"api_username": "system",
"http_basic_user": "username",
"http_basic_pass": "password"
}
],
"allow_writes": true,
"show_emails": true,
"log_level": "info",
"tools_mode": "auto",
"site": "https://try.discourse.org",
"default_search": "tag:ai order:latest",
"max_read_length": 50000,
"transport": "stdio",
"port": 3000,
"allowed_upload_paths": ["/home/user/images", "/tmp/uploads"]
}Run with:
node dist/index.js --profile /absolute/path/to/profile.json
# Current-user home expansion is also supported:
node dist/index.js --profile ~/discourse-mcp-profile.jsonFlags still override values from the profile. A leading current-user ~, ~/, or ~\ in the **profile path** expands to the current home directory; ~otheruser, shell-style expansion elsewhere, and upload-allowlist expansion are intentionally unsupported.
Built-in toolsets
Toolsets let an operator expose only the built-in domains needed by an MCP client. They are optional: when --toolsets and the profile field are both omitted, the server registers the default catalog (including search, discourse_search, and discourse_filter_topics). Administrative and specialized domains marked (opt-in) below—including themes—must be selected explicitly. Use --toolsets all only when every built-in domain is deliberately required.
Pass one name or a comma-separated union:
# Data Explorer reads, plus the site-selection bootstrap tool
npx -y @discourse/mcp@latest \
--toolsets data_explorer \
--tools_mode discourse_api_only
# Search and topic tools, retaining canonical registration order
npx -y @discourse/mcp@latest \
--toolsets search,topics \
--tools_mode discourse_api_only
# Every built-in domain, including opt-in workflows
npx -y @discourse/mcp@latest \
--toolsets all \
--tools_mode discourse_api_only
# Author, test, and run workflows (admin key required)
npx -y @discourse/mcp@latest \
--toolsets workflows \
--site https://forum.example.com \
--auth_pairs '[{"site":"https://forum.example.com","api_key":"...","api_username":"system"}]' \
--allow_writes \
--tools_mode discourse_api_onlyProfiles use an array (a comma-separated string is also accepted):
{
"toolsets": ["users", "uploads"]
}Available toolsets are:
Toolset | Built-in tools |
|
|
| Topic-level search/filtering plus post-level keyword evidence |
| Core topic/post reads, exact stream selection, post search, user-post activity, and mutations |
| User lookup/listing, user-post activity, and user mutations |
| Chat message retrieval |
| Draft retrieval, save, and deletion |
| File upload |
| Query retrieval, execution, creation, update, and deletion |
| Authenticated personal/group PM listing and reading, plus write-gated creation, replies, and participant invitations |
| Reply relationships, site-wide post activity, topic view history, user activity summaries and timelines, and directory/cohort metrics |
| Category discovery, admin-visible site settings, and explicitly confirmed user activation/approval state changes |
| Masked site-setting inspection plus write-gated, preflighted updates of ordinary non-secret settings |
| Safe webhook and delivery-history inspection plus write-gated lifecycle, ping, and single-event redelivery operations |
| Theme/component inspection plus write-gated local creation, editing, installation, remote synchronization, asset upload, and guarded deletion |
| Exhaustive empty-input group directory listing, explicit page/filter compatibility mode, complete group CRUD and membership operations, and fixed-page group-authored post evidence |
| Public visibility-filtered search plus staff inventory/detail and write-gated, preflighted create/update/delete lifecycle |
| Authenticated review queue triage, user behavioral counters, bounded post revisions, and one freshly preflighted reviewable action |
| Admin-only workflow discovery, graph authoring, expression evaluation, pin-data, draft runs, step runs, executions, and version management |
| Admin-only AI agent discovery, typed lifecycle, bot-user creation, and portable import/export |
| Admin-only database-backed scripted custom-tool guide, lifecycle, actual execution testing, and import/export |
| Admin-only AI feature discovery and exact-area, non-secret feature-setting updates; also includes agent discovery |
| Staff-visible Discourse report discovery/execution and the Discourse Solved support dashboard |
| Read-oriented Discourse AI cached summaries, semantic search, and staff sentiment classifications |
| Expands to every built-in toolset, including opt-in domains; absorbs other selections |
Toolset membership is intentionally separate from safety and authorization:
Selected toolsets form a union. A tool in multiple selected sets is registered once, in the canonical built-in order.
allexpands to every real domain and is never tool metadata.Omitted selection excludes opt-in-only tools. Tool definitions may not mix opt-in and default memberships, which prevents accidental default exposure.
discourse_select_siteis automatically retained as a bootstrap capability for every untethered subset. With--site, it remains hidden as usual.Read-only mode still removes tools that require write enablement. For example,
--toolsets data_explorerexposes query retrieval and execution by default; add--allow_writesto expose saved-query mutations.Existing call-time authentication and admin checks are unchanged. Data Explorer tools still require admin access when called; group operations retain Discourse's own staff, owner, visibility, and self-service authorization rules.
Toolsets filter built-in tools only; they are not an authorization or complete capability boundary. MCP resources and prompts remain available, and all existing call-time access checks remain authoritative. Remote Tool Execution API discovery is controlled independently by
--tools_mode; use--tools_mode discourse_api_onlywhen the MCP tool list must contain only the selected built-in domains. The server logs an informational notice when selected toolsets are combined with remote discovery.A selected domain can contribute no tools under the current safety configuration—for example,
uploadsin read-only mode. The server logs an informational notice when this occurs.Unknown or empty toolset selections are configuration errors. Values are de-duplicated after trimming whitespace.
Category, group, and tag-group directories
Directory capabilities are deliberately opt-in, so omitting --toolsets adds zero category/group/tag-group tools:
Select
administrationfordiscourse_list_categories. Empty input performs bounded exhaustive traversal through the 1-based category-search endpoint when the deployment permits it. If anonymous POST is rejected, bounded paginated nested category-index GETs (and only on their rejection, legacy/site.json) are returned with explicitanonymous_fallback/legacy_site_jsonincomplete metadata—never as exhaustive. Optionalterm,max_pages,max_requests,max_results, anddeadline_msbound focused discovery; fallback term matching is applied locally because category index does not implement search terms. Category records retain URL/hierarchy fields;parent_category_idis canonical and nullable, whilepidis a legacy alias retained for compatibility. The existing rich no-input projection is intentional: the reproducible 300-record fixture insrc/test/directory_tools.test.tsmeasures about 45 KB, so this release preserves its useful counts/access fields rather than adding a secondfieldscontract.Select
groupsfordiscourse_list_groups.{}is the canonical exhaustive, deduplicating operation. Supplying any explicit existing key—including{ "page": 0 }or{ "asc": false }—preserves the historical one-page/filter query behavior. Both modes return{ groups, meta, extras?, total_rows_groups?, load_more_groups? }; filtered mode is intentionallycomplete: false.Directory and tag-group successes advertise MCP
outputSchemaand returnstructuredContent. The JSON text content is the same normalized value for clients that do not consume structured output. Malformed upstream records return ordinaryisError: truetool results rather than protocol output-validation failures.
Select the dedicated tag_groups domain for six tools:
discourse_search_tag_groupsis public, Guardian-filtered discovery. It always sends an explicit limit and reports possible truncation. Search omits tag-group IDs, parents, and permissions, so it is not authoritative inventory; case-insensitive exact group names are the correlation key.qandnamescombine with AND semantics, and upstream treats%/_as SQL LIKE wildcards.discourse_list_tag_groupsanddiscourse_get_tag_grouprequire configured API credential shape plus upstream staff authority. The local helper cannot prove a staff role; Discourse is authoritative and privacy-preservingly returns 404 to non-staff. Reads can work when tagging is disabled.discourse_create_tag_group,discourse_update_tag_group, anddiscourse_delete_tag_groupadditionally require effective write mode and upstreamtagging_enabled. MCP inputs and normalized outputs represent permissions as explicit entries, for example[{"group_id":0,"access":"full"},{"group_id":9,"access":"readonly"}]; group ID0is Discourse's built-in everyone group. The server converts these entries to Discourse's numeric permission map (1= full,3= readonly) only at the HTTP boundary.parent_tagis an optional{id}or{name}selector: omit it or usenullwhen creating without a parent; blank client placeholders are treated as omitted. New selector names requireallow_tag_creation=truebecause persistent tags are created and normal indexing/plugin hooks run.Updates require a fresh
expected_state_hash, merge omitted fields locally, and send complete tags/parent/one-per-topic/permissions because partial upstream bodies clear state. Tag/parent removals, permission replacement, and possible materialization of serializer-synthesized everyone/full legacy permissions require explicit confirmations (includingacknowledge_possible_synthetic_permission_materialization). The hash is an MCP optimistic precondition, not an upstream atomic lock; races can still occur after preflight.Deletes require exact ID/name/hash plus explicit cascade and unresolved-plugin acknowledgements. Deletion cascades memberships, permissions, and category allowed/required relationships, but does not delete tags or topic-tag rows. Plugin dependency discovery is not exhaustive. Discourse's scoped API-key action map may not authorize delete. A 200 acknowledgement is not success until a post-delete GET proves absence; uncertain post-dispatch outcomes are non-retryable
outcome_unknownerrors.
These toolsets control discovery only. Staff role, Guardian visibility, scoped-key authority, write mode, and site settings remain call-time/upstream decisions.
Webhooks and site settings
The webhooks and site_settings toolsets are opt-in and admin-sensitive. Selecting either toolset controls discovery only—it is not authorization. Every call requires matching selected-site admin-style authentication, Discourse remains the final authorization and validation authority, and mutations additionally require --allow_writes. The existing site-setting read remains available through administration, but site-setting mutation is discoverable only through an explicit site_settings selection.
# Inspect safe webhook summaries and bounded delivery diagnostics
npx -y @discourse/mcp@latest --site https://forum.example.com \
--toolsets webhooks --tools_mode discourse_api_only \
--auth_pairs '[{"site":"https://forum.example.com","api_key":"...","api_username":"system"}]'
# Deliberately enable guarded site-wide setting changes
npx -y @discourse/mcp@latest --site https://forum.example.com \
--toolsets site_settings --tools_mode discourse_api_only \
--auth_pairs '[{"site":"https://forum.example.com","api_key":"...","api_username":"system"}]' \
--allow_writesWebhook delivery, ping, and redelivery make requests to external systems; enqueue or HTTP success does not prove that the destination processed an event correctly. Webhook secrets are never returned, URL userinfo is removed, query values are masked, and raw event headers are never passed through. Event payload/body previews require explicit sensitive-content confirmation and are bounded and credential-redacted. Bulk redelivery is intentionally unsupported.
Site settings affect the entire forum. Reads mask both upstream-secret and credential-like names; pass overridden_only: true to list only settings whose current value differs from the default. Updates support only one freshly visible ordinary setting at a time, require an expected current value and confirmation, and verify the result with an exact re-read. Secret/credential, upload, uploaded-image-list, and structured object settings, bulk updates, and existing-user backfills are intentionally unsupported.
Theme administration
The opt-in themes toolset is admin-sensitive and is never included in the default catalog. Read-only selection registers only discourse_list_themes and discourse_get_theme; every mutation additionally requires --allow_writes. Toolset selection does not grant admin access: configure matching site authentication and Discourse remains authoritative for admin, repository-allowlist, dependency, compiler, import, and migration checks.
Theme HTML, JavaScript, SCSS, settings migrations, assets, and third-party repositories can execute or deploy code for every visitor. The tools require operation-specific confirmations, but they do not sandbox, validate, or declare third-party code safe. Local archives and assets are accepted only from bounded base64 input or regular files beneath symlink-resolved --allowed_upload_paths roots.
Use deliberate operator configuration rather than enabling every toolset:
discourse-mcp \
--toolsets themes \
--site https://forum.example.com \
--auth_pairs '[{"site":"https://forum.example.com","api_key":"...","api_username":"system"}]' \
--allow_writes \
--allowed_upload_paths /srv/discourse-theme-inputsLocal themes and ZIP-imported themes can be edited directly (although ZIP source values are omitted by Discourse's detail serializer); Git-backed themes must be changed in their repository and synchronized. Components cannot be default/user-selectable or own color schemes. Text fields and upload fields are separate schema variants—never send placeholder upload IDs with SCSS/HTML/JavaScript:
{
"fields": [{
"name": "scss",
"target": "common",
"operation": "replace",
"type": "scss",
"value": "body { background: #241914; }"
}]
}Installation likewise uses one nested source variant. A repository install needs no archive placeholders:
{
"source": {
"kind": "repository",
"remote_url": "https://github.com/example/discourse-theme.git",
"branch": "main"
},
"confirm_external_code": true
}This release intentionally excludes private-repository key management, repository repointing, export, bulk deletion, arbitrary themeable site-setting mutation, and generic controller parameter pass-through.
Group management
The opt-in groups toolset covers the complete custom-group lifecycle: directory listing and full detail reads; create, update, and permanent delete; paginated member and owner reads; explicit selector-specific tools for adding/removing members and promoting/demoting owners by username, numeric user ID, or existing-account email; pending-request listing and approve/deny decisions; and authenticated request, public-join, and public-leave flows. A separate invitation tool handles addresses that may not have accounts yet, avoiding confusion between account lookup and forum invitations.
All mutations require --allow_writes. Creation and deletion additionally require staff/admin-style API credentials at the MCP access gate. Discourse remains authoritative for Guardian checks, group visibility, staff versus owner capabilities, automatic-group restrictions, membership settings, invitation limits, and the fields a caller may update. Core automatic groups cannot be created, deleted, or have membership/ownership changed; their permitted presentation and interaction settings can still be updated by authorized staff. Selecting the toolset does not grant any of these permissions.
Moderation queue
The opt-in moderation toolset exposes discourse_get_review_queue_count, discourse_list_reviewables, discourse_list_reviewable_topics, and discourse_get_reviewable in read-only mode. These tools require configured authentication, but intentionally do not impose an MCP admin-only gate: Discourse Guardian remains authoritative for staff and category-moderator visibility. Selecting the toolset grants no moderation permission.
For queue totals, use discourse_get_review_queue_count; its count is the number of pending reviewable records visible to the caller, not the number of individual flags. Use discourse_list_reviewables with only status: "pending" and offset: 0 for ordinary triage—do not invent topic, category, type, or user filters—and follow next_offset until has_more is false. Numeric topic/category placeholders of 0 and optional text placeholders of blank/all/any are treated as omitted, so strict-schema clients cannot accidentally filter to ID 1 or send invalid universal sentinels. List results already contain bounded evidence and dynamic actions; avoid fanning out discourse_read_topic or detail calls across the queue. discourse_list_reviewable_topics is only a convenience aggregation: upstream includes pending topics at or above its minimum review-priority threshold, omits queue items without topics, and reports score_count as the number of review score/flag records—not reviewable items. It must not be used to infer the complete queue size.
When --allow_writes is set, discourse_perform_reviewable_action is also registered. Call list/detail first and submit one exact available_actions[].id with confirm: true; choose from the full action description, not a repeated label such as “Delete post.” Discourse UI action IDs can be prefixed (post-… or user-…), while the route requires the associated server_action; the MCP validates and maps this automatically. Moderation mutations are serialized and paced across the complete fresh-GET/PUT operation, so a concurrent model batch cannot bypass the write throttle. The tool checks an optional expected version, rejects unadvertised fields, and returns normalized success/count fields. A failure after the PUT is marked as an unknown outcome with identifiers and must be verified rather than blindly retried. Discourse still enforces claims, optimistic conflicts, action validity, and Guardian permissions. The tools expose evidence and explicit operations; they do not recommend moderation decisions.
Private messages
The default private_messages toolset provides a PM-aware interface rather than reusing generic public-topic mutations. Listing and reading require configured authentication. Creation, replies, and invitations additionally require --allow_writes. Discourse remains authoritative for mailbox visibility, PM membership, recipient limits, group messageability, and all Guardian/API-key checks.
Personal mailboxes support inbox, sent, archive, unread, and new. Group mailboxes support all except sent; a personal inbox does not include every group inbox. If discourse_list_private_messages omits username, it resolves the authenticated user through /session/current.json. A supplied username selects a mailbox path—it does not impersonate that user. Discourse permits another user's inbox/sent/archive only where its authorization rules allow it, while unread and new remain owner-only even for admins.
PM recipients are typed as usernames, group names, or email addresses. During creation, a messageable group wins over a same-named user in Discourse's upstream classification. A nonexistent or non-messageable group_names value can therefore surface as a user-not-found-style upstream error. Unknown email recipients may immediately create staged users when site settings and sender permissions allow it; use a canonical username when staged-user creation is not intended.
Email invitations are intentionally opaque. A successful response does not confirm delivery or immediate participant access: an address belonging to an existing account may produce a successful no-op, while a new address receives access only after invitation redemption. Use username to add a known account immediately. Group invitation lookup is exact-case, so use the canonical group name. Optional author_username sends Api-Username; switching identities is supported only by an appropriate global API key, while User API Keys remain bound to their owner.
Workflow authoring
The workflows toolset targets the experimental discourse-workflows plugin (enable_discourse_workflows) and requires an admin API key. A typical loop is:
List node types, then request a specific
identifierfor its parameter schema, output ports, and$jsoncontracts.Resolve category, tag, group, user, badge, chat channel, or data-table ids.
Create from a template or submit a complete small graph.
GET the workflow immediately before editing. Replace with complete
nodesandconnections, or use MCP-sideoperations[]for mechanical edits. Omitting a node from a whole-graph update deletes it.Add pin-data and step-run, or manually run the current draft; poll
discourse_get_workflow_execution.Set
published: trueafter testing.
Flat connections such as [{"from":"Start","to":"Check","type":"main"}] are accepted and converted to Discourse's nested wire format. Use the source node's catalog output key: condition/filter ports are true and false, not always main. MCP rejects one-sided graph updates before HTTP. Runs are not dry-runs and can create posts, send chat messages, or call external HTTP.
Discourse AI administration
The three AI administration domains require a Discourse admin API key (or an admin user API key accepted by the selected endpoint). They are independently opt-in and default-off. Mutations—and custom-tool test execution—also require --allow_writes.
# Configure agents without exposing scripted source management
npx -y @discourse/mcp@latest --site https://forum.example.com \
--toolsets ai_agents --tools_mode discourse_api_only \
--auth_pairs '[{"site":"https://forum.example.com","api_key":"...","api_username":"system"}]' \
--allow_writes
# Assign agents and update safe feature settings, but do not expose custom-tool code editing
npx -y @discourse/mcp@latest --site https://forum.example.com \
--toolsets ai_agents,ai_features --tools_mode discourse_api_only \
--auth_pairs '[{"site":"https://forum.example.com","api_key":"...","api_username":"system"}]' \
--allow_writesThe agent index is intentionally concise by default: discourse_ai_list_agents omits system prompts and per-agent configuration, returning summary counts—including subagent_count—plus slim tool/model catalogs. Use discourse_ai_get_agent with an ID to inspect one full configuration. view: "full" is available only for clients that explicitly need the complete upstream index.
Agent create/update schemas accept subagent_ids, an ordered allowlist of up to 20 unique existing agent IDs that the parent may delegate to. Negative IDs are valid for system agents. Discourse validates that every ID exists, rejects self-delegation, and prevents a configured tool from colliding with the generated spawn_agent tool; use the full agent list or detail response to resolve IDs before writing.
discourse_ai_list_custom_tools follows the same pattern: it returns compact records and preset signatures without scripts, bindings, or verbose parameter documentation. Use discourse_ai_get_custom_tool for one stored tool, or call the guide with topic: "presets" and a preset_id for one complete preset example.
ai_custom_tools manages Discourse's database-backed AiTool records. It is separate from remote tools dynamically discovered at /ai/tools, which remain controlled by --tools_mode. Script authoring is synchronous MiniRacer JavaScript: define invoke(parameters); do not use async, browser APIs, or Node modules. Call discourse_ai_get_custom_tool_guide with only the focused topic you need. preset_id is optional and meaningful only for topic: "presets"; it is ignored for other topics. Use topic: "preamble" for the exact selected-server contract before creating or substantially changing a script. The same exact live preamble and minimal template is exposed as the conditional discourse://ai/custom-tools/authoring-guide resource when this toolset is selected. Resources are application-driven; the guide tool is model-controlled, so autonomous clients should use the tool rather than assume a host attached the resource. A future optional authoring prompt would be user-controlled and would guide an explicitly initiated workflow rather than replace model-callable discovery.
Safety: discourse_ai_test_custom_tool actually executes code and can issue external requests or cause site side effects. Feature updates alter production behavior immediately and are limited to non-secret settings returned from one exact ai-features/<module> area. Custom-tool source, prompts, bindings, exports, and test parameters should be treated as sensitive. Use the narrowest toolset combination and test on a non-production site first.
Remote Tool Execution API (optional)
With
tools_mode=auto(default) ortool_exec_api, the server discovers remote tools via GET/ai/toolsafter you select a site (or immediately at startup if--siteis provided) and registers them dynamically. Set--tools_mode=discourse_api_onlyto disable remote tool discovery.
Networking & resilience
Retries on 429/5xx with backoff (3 attempts).
Lightweight in‑memory GET cache for selected endpoints.
Privacy
Secrets are redacted in logs. Errors are returned as human‑readable messages to MCP clients.
MCP Resources
Resources provide application-addressable static/semi-static read-only data. Category and group resources are retained as deprecated compatibility surfaces; their canonical model-callable replacements are the opt-in directory tools above. Other resources remain appropriate when an MCP host attaches them.
discourse://site/categories (deprecated; use
discourse_list_categorieswith--toolsets administration)Uses the same complete bounded/cached category fetcher, then enriches permissions in bounded ID chunks.
Output:
{ categories: [{id, name, slug, parent_category_id, pid, read_restricted, topic_count, post_count, perms?}], meta: {total, reported_total, pages_fetched, complete, has_more, truncated_reason?} }parent_category_idis canonical;pidis a legacy compatibility alias.permsis an array of{gid, perm}where perm: 1=full, 2=create_post, 3=readonly.permsis populated only when the selected identity can retrieve permission enrichment; otherwise it is omitted rather than fabricated.
discourse://site/tags
List all tags with usage counts
Output:
{ tags: [{id, name, count}], meta: {total} }
discourse://site/groups (deprecated; use
discourse_list_groupswith--toolsets groups)Uses the same complete bounded/cached exhaustive group fetcher and reports upstream failures explicitly rather than as a truthful empty site.
Output:
{ groups: [{id, name, automatic, user_count, vis, members_vis, mention, msg, public_admission, public_exit, allow_membership_requests}], meta: {total, reported_total, pages_fetched, complete, has_more, truncated_reason?} }Levels (0-4): 0=public, 1=logged_on_users, 2=members, 3=staff, 4=owners
Use case: Resolve
gidvalues from category permissions to group names, replicate group settings during migrations
discourse://chat/channels
List all public chat channels
Output:
{ channels: [{id, title, slug, status, members_count, description}], meta: {total} }
discourse://user/chat-channels
List user's chat channels (public + DMs) with unread/mention counts
Output:
{ public_channels: [...], dm_channels: [...], meta: {total} }Requires authentication
discourse://user/drafts
List user's drafts
Output:
{ drafts: [{draft_key, sequence, title, category_id, created_at, reply_preview}], meta: {total} }Requires authentication
discourse://ai/custom-tools/authoring-guide (conditional)
Registered only when
ai_custom_toolsis selected (including throughall)Returns the exact selected-site
empty_toolJavaScript preset: Discourse's current preamble plus minimalinvoke/detailstemplateMIME type:
text/javascript; requires selected-site admin credentialsApplications may attach this resource; models can retrieve the same content with
discourse_ai_get_custom_tool_guideandtopic: "preamble"
Evidence and analytics capabilities
The expanded read catalog exposes upstream evidence rather than MCP-authored judgments:
discourse_searchremains topic-focused. Usediscourse_search_postswhen matched posts are required: it preserves bounded post IDs with highlighted blurbs, authors, topics/categories, and truthful continuation. This supersedes the older proposal to add an unbounded list of bare post IDs to topic-search results.discourse_ai_semantic_searchis a separate opt-in Discourse AI embedding search. The opt-inactivitytooldiscourse_list_latest_postsis a fixed 50-row chronological feed with a post-ID cursor, not search.The default
discourse_read_topic_postsselects exact IDs, earliest/latest posts, an around-post window, or username-filtered posts. Latest/earliest selections use at most two upstream requests and cap the selected set at 50. The opt-inactivitytooldiscourse_get_post_repliesdistinguishes recursive descendant IDs, 20-row direct replies, and the site-bounded ancestor history.Topic and post reads preserve Discourse Solved fields when the plugin supplies them. An accepted answer is a resolution proxy, not proof that the original poster is satisfied.
The opt-in
activitydomain containsdiscourse_get_user_summaryfor profile-visible aggregates,discourse_list_user_actionsfor a paginated named event timeline, anddiscourse_list_directory_itemsfor visible directory/cohort metrics. The existing defaultdiscourse_list_user_postsremains the compatible post/reply view.The opt-in
administrationdomain makes categories and admin-visible site settings model-callable and provides confirmed activation/approval state changes. User creation requires a global admin API key; responses without an upstreamuser_idremain explicitly unconfirmed.Topic/post
author_usernamerequires a global API key, and creation responses report both requested and actual attribution so ignored impersonation cannot be mistaken for success.The opt-in
analyticsdomain discovers the staff-visible report catalog before executing a report and exposes the Solved support dashboard. Dashboard “unanswered” means unsolved with no qualifying regular reply—not no response from a designated team.The opt-in
ai_insightsdomain requires Discourse AI. Cached summaries report staleness, semantic search remains Guardian-filtered, and sentiment values are upstream model classifications rather than objective argument, satisfaction, or risk labels.
Plugin-specific 404 responses are intentionally reported as capability_or_resource_unavailable: the same upstream response can mean a disabled plugin/setting, a hidden resource, or a nonexistent resource. Toolset selection does not grant visibility or staff access.
Example compositions:
Catch up on a thread with
discourse_read_topic_postsinlatest/replies_onlymode, then let the calling model summarize the ordered evidence.Assess answer state by combining Solved topic filters, accepted-answer fields, and the support dashboard while disclosing that “solved” is only a proxy.
Check for group participation with
discourse_list_group_postsand topic IDs; group authorship is evidence, not proof of organizational responsibility.Review possible conflict by retrieving exact posts/reply chains and, optionally, upstream sentiment classifications; the calling model makes and explains any semantic judgment.
Tools
Built‑in tools (always present unless noted). All tools return strict JSON (no Markdown).
discourse_searchInput:
{ query: string; max_results?: number (1–50, default 10) }Output:
{ results: [{id, slug, title}], meta: {total, has_more} }
discourse_read_topicInput:
{ topic_id: number; post_limit?: number (1–50, default 5); start_post_number?: number }Output:
{ id, title, slug, category_id, tags, posts_count, posts: [{id, post_number, username, created_at, raw}], meta }
discourse_read_postInput:
{ post_id: number }Output:
{ id, topic_id, topic_slug, post_number, username, created_at, raw, truncated }
discourse_get_userInput:
{ username: string }Output:
{ id, username, name, trust_level, created_at, bio, admin, moderator }
discourse_list_user_postsInput:
{ username: string; page?: number (0-based); limit?: number (1–50, default 30) }Output:
{ posts: [{id, topic_id, post_number, slug, title, created_at, excerpt, category_id}], meta: {page, limit, has_more} }
discourse_filter_topicsInput:
{ filter?: string; view?: "filtered"|"top"|"hot" (default "filtered"); top_period?: "daily"|"weekly"|"monthly"|"quarterly"|"yearly"|"all"; page?: number (0-based); per_page?: number (1–50) }Filtered requires a nonblank
filterand uses/filter.json. Top rejectsfilter, uses/top.json, and defaults to weekly. Hot rejectsfilter/top_periodand is defined exactly as Discourse's daily top score—not semantic controversy, toxicity, or real-time velocity.Output:
{ results: [{id, slug, title, category_id, tags, created_at, last_posted_at, bumped_at, posts_count, reply_count, views, like_count, posters_count, closed, archived, pinned, visible, last_poster_username, posters}], meta: {view, top_period, page, per_page, returned, has_more, total?} }. Missing optional values remainnull;totaland continuation are never fabricated.Filter query language (succinct): key:value tokens separated by spaces; category/categories (comma = OR,
=category= without subcats,-prefix = exclude); tag/tags (comma = OR,+= AND) and tag_group; status:(open|closed|archived|listed|unlisted|public); personalin:(bookmarked|watching|tracking|muted|pinned); dates: created/activity/latest-post-(before|after) withYYYY-MM-DDor relative daysN; numeric: likes[-op]-(min|max), posts-(min|max), posters-(min|max), views-(min|max); order: activity|created|latest-post|likes|likes-op|posters|title|views|category with optional-asc; free text terms are matched.
Moderation tools (only with
--toolsets moderation; all require authentication)discourse_get_review_queue_count:{}→{ count, unit: "pending_reviewable_queue_items", status: "pending", scope }, wherecountis the authoritative number of pending reviewable records visible to the caller, not individual flags.discourse_list_reviewables: stable review filters and offset pagination → normalized reviewables with named status plus numericstatus_id, current versions, bounded evidence, scores, targets, and dynamic actions. For normal triage send onlystatusandoffset; usemeta.totaland follownext_offset. Upstream page size is fixed at 10.discourse_list_reviewable_topics:{}→ a non-exhaustive aggregation of pending topics at or above Discourse's minimum review priority.score_countcounts review score/flag records, not reviewable queue items; queue items without topics are absent.discourse_get_reviewable:{ reviewable_id; include_explanation? }→ refreshed bounded context, side-loaded references, editable fields, score evidence, and exact available actions; no recommendation is generated. Avoid bulk fan-out because list results already contain triage evidence.discourse_perform_reviewable_action(only when writes enabled):{ reviewable_id; action_id; expected_version?; additional_fields?; confirm: true }→ serialized, freshly preflighted action with normalized success and remaining-count fields. Submit the displayed dynamic action ID; MCP maps itsserver_actionto the Discourse route.
discourse_get_chat_messagesInput:
{ channel_id: number; page_size?: number (1–50, default 50); target_message_id?: number; direction?: "past" | "future"; target_date?: string (ISO 8601) }Output:
{ channel_id, messages: [{id, username, created_at, message, edited, thread_id, in_reply_to_id}], meta }
discourse_get_draftInput:
{ draft_key: string; sequence?: number }Output:
{ draft_key, sequence, found, data: {title, reply, category_id, tags, action} }
discourse_save_draft(only when writes enabled; see Write safety)Input:
{ draft_key: string; reply: string; title?: string; category_id?: number; tags?: string[]; sequence?: number (default 0); action?: "createTopic" | "reply" | "edit" | "privateMessage" }Output:
{ draft_key, sequence, saved }
discourse_delete_draft(only when writes enabled; see Write safety)Input:
{ draft_key: string; sequence: number }Output:
{ draft_key, deleted }
discourse_list_private_messages(requires authentication)Input:
{ username?: string; mailbox?: "inbox"|"sent"|"archive"|"unread"|"new"; group_name?: string; page?: number (0-based, default 0); per_page?: number (1–100, default 30) }Output:
{ mailbox, username, group_name, messages: [{topic_id, slug, title, posts_count, reply_count, created_at, last_posted_at, bumped_at, last_read_post_number, unread_posts, unseen, topic_archived, message_archived, notification_level, recent_participants}], meta: {page, per_page, has_more} }Omitting
usernameuses the authenticated user. Group mailboxes do not supportsent;unreadandnewcannot target another user.
discourse_read_private_message(requires authentication)Input:
{ topic_id: number; post_limit?: number (1–50, default 5); start_post_number?: number }Output:
{ topic_id, slug, title, archetype, subtype, posts_count, last_read_post_number, topic_archived, message_archived, allowed_users, allowed_groups, posts, meta }allowed_usersandallowed_groupsare direct records, not an expanded ACL. Public topics are rejected.
discourse_create_private_message(only when writes enabled; see Write safety)Input:
{ title: string; raw: string (<= 30k chars); usernames?: string[]; group_names?: string[]; email_addresses?: string[]; author_username?: string }(at least one recipient required)Output:
{ id, topic_id, post_number, slug, title }Unknown emails may create staged users. Messageable group names take precedence over same-named usernames.
discourse_reply_private_message(only when writes enabled; see Write safety)Input:
{ topic_id: number; raw: string (<= 30k chars); reply_to_post_number?: number; author_username?: string }Output:
{ id, topic_id, post_number, reply_to_post_number, slug }Performs an uncached PM-archetype safety check before posting, without advancing read state.
discourse_invite_to_private_message(only when writes enabled; see Write safety)Input:
{ topic_id: number; username?: string; group_name?: string; email_address?: string; notify_group_members?: boolean; custom_message?: string (<= 3000 chars); author_username?: string }(exactly one recipient required)Output: immediate normalized user/group addition, or
{ topic_id, recipient_type: "email", status: "submitted", participant_added: false, outcome_confirmed: false }Email submission never claims delivery or access.
custom_messageis email-only, group notifications default to enabled, and group names are exact-case.
discourse_create_post(only when writes enabled; see Write safety)Input:
{ topic_id: number; raw: string (<= 30k chars); author_username?: string }Output:
{ id, topic_id, post_number }
discourse_create_topic(only when writes enabled; see Write safety)Input:
{ title: string; raw: string (<= 30k chars); category_id?: number; tags?: string[]; author_username?: string }Output:
{ id, topic_id, slug, title }
discourse_update_topic(only when writes enabled; see Write safety)Input:
{ topic_id: number; title?: string; category_id?: number; tags?: string[]; featured_link?: string; original_title?: string; original_tags?: string[] }Output:
{ success, topic_id, updated_fields, topic: {id, title, slug, category_id, tags, featured_link} }
discourse_list_users(requires admin API key)Input:
{ query?: "active"|"new"|"staff"|"suspended"|"silenced"|"pending"|"staged"; filter?: string; order?: "created"|"last_emailed"|"seen"|"username"|"trust_level"|"days_visited"|"posts"; asc?: boolean; page?: number }Output:
{ users: [{id, username, name, email, avatar_template, trust_level, created_at, last_seen_at, admin, moderator, suspended, silenced}], meta: {page, has_more} }Note: Returns ~100 users per page (Discourse's fixed page size).
avatar_templatecontains{size}placeholder - replace with pixel size (e.g., 120) to get avatar URL
discourse_create_user(only when writes enabled; see Write safety)Input:
{ username: string (1-20 chars); email: string; name: string; password: string; active?: boolean; approved?: boolean; upload_id?: number }Output:
{ success, username, name, email, active, avatar_updated, message, avatar_error? }Note: If
upload_idis provided but avatar update fails,avatar_errorcontains the error message
discourse_update_user(only when writes enabled; see Write safety)Input:
{ username: string; name?: string; bio_raw?: string; location?: string; website?: string; title?: string; date_of_birth?: string; locale?: string; profile_background_upload_url?: string; card_background_upload_url?: string; upload_id?: number }Output:
{ success, username, updated_fields, avatar_updated, user: {...}, avatar_error? }Note: If
upload_idis provided but avatar update fails,avatar_errorcontains the error message
discourse_upload_file(only when writes enabled; see Write safety)Input:
{ upload_type: "avatar"|"profile_background"|"card_background"|"composer"; image_data?: string (base64); url?: string; filename?: string; user_id?: number }Output:
{ id, url, short_url, short_path, original_filename, extension, width, height, filesize, human_filesize }Constraints:
Provide exactly one of:
image_data(requiresfilename), remote HTTP(S) URL, or absolute local file pathuser_idis required for avatar/profile_background/card_background uploadsLocal file uploads require
--allowed_upload_pathsconfiguration (security: prevents arbitrary file reads)
Note: Use
short_url(e.g.,upload://abc123.png) to embed images in posts.
discourse_create_category(only when writes enabled; see Write safety)Input:
{ name: string; color?: hex; text_color?: hex; emoji?: string; icon?: string; parent_category_id?: number; description?: string }Output:
{ id, slug, name }
discourse_select_site(hidden when--siteis provided)Input:
{ site: string }Output:
{ site, title }
Development
Requirements: Node >= 24,
pnpm.Install / Build / Typecheck / Test
pnpm install --frozen-lockfile
pnpm typecheck
pnpm build
pnpm test
pnpm lintRun locally (with source maps)
pnpm build && pnpm devProject layout
Server & CLI:
src/index.tsHTTP client:
src/http/client.tsTool registry:
src/tools/registry.tsResource registry:
src/resources/registry.tsBuilt‑in tools:
src/tools/builtin/*Remote tools:
src/tools/remote/tool_exec_api.tsJSON helpers:
src/util/json_response.tsLogging/redaction:
src/util/logger.ts,src/util/redact.ts
Dependency and lockfile policy
pnpm (
packageManager: pnpm@10.14.0) is the authoritative development workflow, but bothpnpm-lock.yamlandpackage-lock.jsonare tracked for downstream/npm compatibility. Regenerate and commit both whenever dependencies change; CI runs frozen pnpm and cleannpm cibuilds so neither can silently drift.@modelcontextprotocol/sdkis intentionally pinned exactly to the reviewed1.30.0release. SDK updates are deliberate security/compatibility changes and must pass typecheck, real transport/output-schema tests, production high-severity audits for both lockfiles, and packaging smoke tests. Dev-only audit exceptions require a dated owner and remediation plan.
Testing notes
Tests run with Node’s test runner against compiled artifacts (
dist/test/**/*.js). Ensurepnpm buildbeforepnpm testif invoking scripts individually.
Publishing (optional)
The package is published as
@discourse/mcpand exposes abinnameddiscourse-mcp. Prefernpx @discourse/mcp@latestfor frictionless usage.
Conventions
All outputs are JSON-only for reliable programmatic parsing by agents.
Be careful with write operations; keep them opt‑in and rate‑limited.
See AGENTS.md for additional guidance on using this server from agent frameworks.
Examples
Quick Start with User API Key (No Admin Required)
# Step 1: Generate a User API Key
npx @discourse/mcp@latest generate-user-api-key \
--site https://discourse.example.com \
--save-to profile.json
# Step 2: Visit the authorization URL shown, approve the request, and paste the payload
# Step 3: Run the MCP server with your new key
npx @discourse/mcp@latest --profile profile.json --allow_writesOther Examples
Read‑only session against
try.discourse.org:
npx -y @discourse/mcp@latest --log_level debug
# In client: call discourse_select_site with {"site":"https://try.discourse.org"}Tether to a single site:
npx -y @discourse/mcp@latest --site https://try.discourse.orgCreate a post with Admin API Key (writes enabled):
npx -y @discourse/mcp@latest --allow_writes --auth_pairs '[{"site":"https://try.discourse.org","api_key":"'$DISCOURSE_API_KEY'","api_username":"system"}]'Create a post with User API Key (writes enabled, no admin required):
npx -y @discourse/mcp@latest --allow_writes --auth_pairs '[{"site":"https://try.discourse.org","user_api_key":"'$DISCOURSE_USER_API_KEY'"}]'Create a category (writes enabled):
npx -y @discourse/mcp@latest --allow_writes --auth_pairs '[{"site":"https://try.discourse.org","api_key":"'$DISCOURSE_API_KEY'","api_username":"system"}]'
# In your MCP client, call discourse_create_category with for example:
# { "name": "AI Research", "color": "0088CC", "text_color": "FFFFFF", "description": "Discussions about AI research" }Create a topic (writes enabled):
npx -y @discourse/mcp@latest --allow_writes --auth_pairs '[{"site":"https://try.discourse.org","api_key":"'$DISCOURSE_API_KEY'","api_username":"system"}]'
# In your MCP client, call discourse_create_topic, for example:
# { "title": "Agentic workflows", "raw": "Let's discuss agent workflows.", "category_id": 1, "tags": ["ai","agents"] }Private-message workflow with authenticated list/read and write-gated create/reply/invite:
npx -y @discourse/mcp@latest \
--site https://try.discourse.org \
--toolsets private_messages \
--tools_mode discourse_api_only \
--auth_pairs '[{"site":"https://try.discourse.org","user_api_key":"'$DISCOURSE_USER_API_KEY'"}]' \
--allow_writes
# In your MCP client:
# discourse_list_private_messages: { "mailbox": "inbox", "page": 0 }
# discourse_read_private_message: { "topic_id": 123, "post_limit": 10 }
# discourse_reply_private_message: { "topic_id": 123, "raw": "Here is the result.", "reply_to_post_number": 3 }
# discourse_create_private_message: { "title": "Claim review", "raw": "Please review.", "usernames": ["alice"], "group_names": ["reviewers"], "email_addresses": ["external@example.com"] }
# discourse_invite_to_private_message: { "topic_id": 123, "group_name": "reviewers", "notify_group_members": false }Run with HTTP transport (on port 3000):
npx -y @discourse/mcp@latest --transport http --port 3000 --site https://try.discourse.org
# Server will start on http://localhost:3000
# Health check: http://localhost:3000/health
# MCP endpoint: http://localhost:3000/mcpConnect to a site behind HTTP Basic Auth:
npx -y @discourse/mcp@latest --auth_pairs '[{"site":"https://protected.example.com","api_key":"'$DISCOURSE_API_KEY'","api_username":"system","http_basic_user":"username","http_basic_pass":"password"}]' --site https://protected.example.comAuthentication
Admin API Keys vs User API Keys
This MCP server supports two types of Discourse API authentication:
Admin API Keys (
api_key+api_username)Require admin/moderator permissions to generate
Created via Admin Panel → API → New API Key
Can perform all operations including user/category creation
Use headers:
Api-KeyandApi-Username
User API Keys (
user_api_key+ optionaluser_api_client_id)Can be generated by any user (no admin required)
User-specific permissions and rate limits
Ideal for personal use and non-admin operations
Use headers:
User-Api-KeyandUser-Api-Client-IdAuto-expire after 180 days of inactivity (configurable per site)
Learn more: https://meta.discourse.org/t/user-api-keys-specification/48536
Obtaining a User API Key
Easy Method: Built-in Generator (Recommended)
This package includes a convenient command to generate User API Keys:
# Interactive mode - follow the prompts
npx @discourse/mcp@latest generate-user-api-key --site https://discourse.example.com
# Save directly to a profile file
npx @discourse/mcp@latest generate-user-api-key --site https://discourse.example.com --save-to profile.json
# Specify custom scopes
npx @discourse/mcp@latest generate-user-api-key --site https://discourse.example.com --scopes "read,write,notifications"
# Get help
npx @discourse/mcp@latest generate-user-api-key --helpThe command uses Discourse's device authorization flow on supported sites (Discourse 2026.6.0 and newer):
It generates an RSA key pair and requests a short-lived authorization.
It displays an activation URL and a short code such as
ABCD-2345.You open the URL, enter the code, review the scopes, and authorize the request.
The command polls Discourse and retrieves the encrypted User API Key automatically.
It validates and decrypts the response, then prints the configuration or saves it to a profile.
No encrypted payload needs to be copied back into the terminal. For older Discourse sites, the command automatically falls back to the legacy authorization URL and payload prompt.
Manual Method
User API Keys require an OAuth-like flow documented at https://meta.discourse.org/t/user-api-keys-specification/48536. Key steps:
Generate a public/private key pair
Request authorization via
/user-api-key/newwith your public key, application name, client ID, and requested scopesUser approves the request (after login if needed)
Discourse returns an encrypted payload with the User API Key
Decrypt using your private key and use the key in your configuration
You can also manually create User API Keys via the Discourse UI (if enabled by the site):
Visit your user preferences → Security → API
Or use third-party tools that implement the User API Key flow
FAQ
Why is
create_postmissing? You're in read‑only mode. Enable writes as described above.Can I disable remote tool discovery? Yes, run with
--tools_mode=discourse_api_only.Can I avoid exposing
discourse_select_site? Yes, start with--site <url>to tether to a single site.Time outs or rate limits? Increase
--timeout_ms, and note built‑in retry/backoff on 429/5xx.Should I use Admin API Keys or User API Keys? Use User API Keys for personal use (no admin required). Use Admin API Keys only when you need admin-level operations or are setting up a system-wide integration.
Getting "fetch failed" errors? Run with
--log_level debugto see detailed error information including:The exact URL being requested
HTTP status codes (response bodies are deliberately not logged because they may contain sensitive content)
Network-level errors (DNS, SSL/TLS, connectivity issues)
Retry attempts and timing
Timeout diagnostics
Available Tools
14 toolsdiscourse_filter_topicsFilter TopicsA
Filter topics with a concise query language: use key:value tokens separated by spaces; category/categories for categories (comma = OR, '=category' = without subcats, '-' prefix = exclude), tag/tags (comma = OR, '+' = AND) and tag_group; status:(open|closed|archived|listed|unlisted|public) and personal in:(bookmarked|watching|tracking|muted|pinned); dates: created/activity/latest-post-(before|after) with YYYY-MM-DD or N (days); numeric: likes[-op]-(min|max), posts-(min|max), posters-(min|max), views-(min|max); order: activity|created|latest-post|likes|likes-op|posters|title|views|category with optional -asc; free text terms are matched full-text. Results are permission-aware.
| Name | Required | Description | Default |
|---|---|---|---|
| filter | Yes | Filter query, e.g. 'category:support status:open created-after:30 order:activity' | |
| page | No | Page number (0-based, default: 0) | |
| per_page | No | Items per page (max 50) |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
With no annotations provided, the description carries full burden for behavioral disclosure. It effectively describes key behavioral traits: the query language syntax, permission-aware results (important for access control), and pagination behavior (implied through page/per_page parameters). It doesn't mention rate limits, error conditions, or authentication requirements, but provides substantial operational context.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is densely packed with query syntax details but remains a single paragraph. While information-dense, it could benefit from better structure (e.g., bullet points for different filter types). Every sentence earns its place by explaining the query language, but the presentation could be more scannable for an AI agent.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a filtering tool with 3 parameters (one complex), no annotations, and no output schema, the description provides substantial context about the query language and permission-aware results. It adequately covers the tool's purpose and usage, though it could benefit from explicit examples of complete queries and more detail about result format since there's no output schema.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 100%, providing good documentation for all three parameters. The description adds value by explaining the complex 'filter' parameter syntax in detail (key:value tokens, operators, date formats, etc.), but doesn't add meaningful context for 'page' and 'per_page' beyond what the schema already states. Baseline 3 is appropriate given the high schema coverage.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description explicitly states 'Filter topics with a concise query language', providing a specific verb ('filter') and resource ('topics'). It clearly distinguishes this tool from siblings like 'discourse_search' by focusing on structured filtering rather than general search, and from 'discourse_list_categories/tags' by targeting topics specifically.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The description implies usage context through the detailed query language explanation, suggesting this tool is for structured filtering of topics. However, it doesn't explicitly state when to use this versus alternatives like 'discourse_search' or 'discourse_list_categories', nor does it provide exclusion criteria or prerequisites for use.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
discourse_get_chat_messagesGet Chat MessagesA
Get messages from a chat channel with flexible pagination and date-based filtering. Supports: (1) paginating with direction='past'/'future' from a target_message_id, (2) querying messages around a specific target_date, (3) getting messages around a target_message_id, or (4) fetching from last read position.
| Name | Required | Description | Default |
|---|---|---|---|
| channel_id | Yes | The chat channel ID | |
| page_size | No | Number of messages to return (default: 50, max: 500) | |
| target_message_id | No | Message ID to query around or paginate from | |
| direction | No | Pagination direction: 'past' for older messages (DESC), 'future' for newer messages (ASC) | |
| target_date | No | ISO 8601 date string (e.g., '2024-01-15' or '2024-01-15T10:30:00Z') to query messages around that date | |
| fetch_from_last_read | No | If true, start from the user's last read message | |
| include_target_message_id | No | Whether to include the target message in results (default: true) |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
With no annotations provided, the description carries the full burden. It mentions 'flexible pagination' and the four query modes, which gives some behavioral context. However, it lacks details on permissions, rate limits, error conditions, or response format (especially important since there's no output schema), leaving gaps for a mutation-free read operation.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is front-loaded with the core purpose, followed by a bullet-like list of four specific capabilities. Every sentence directly contributes to understanding the tool's functionality, with zero redundant or vague language.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a read operation with 7 parameters and no annotations or output schema, the description is adequate but incomplete. It covers the query modes well but omits details on permissions, rate limits, error handling, and return format. Given the complexity and lack of structured data, it should provide more behavioral context to be fully complete.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 100%, so the schema fully documents all 7 parameters. The description adds value by summarizing the four query modes, which helps contextualize parameters like target_message_id, direction, and fetch_from_last_read. However, it doesn't provide additional syntax or format details beyond the schema, meeting the baseline for high coverage.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description clearly states the verb ('Get') and resource ('messages from a chat channel'), and specifies the key capabilities: pagination and date-based filtering. It distinguishes itself from siblings like discourse_list_chat_channels (which lists channels) and discourse_search (which searches content) by focusing specifically on retrieving messages within a channel.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The description explicitly lists four usage scenarios (pagination, date-based query, message-based query, last-read fetch), providing clear context for when to use this tool. However, it does not mention when NOT to use it or explicitly name alternatives (e.g., discourse_search for broader content search), which prevents a perfect score.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
discourse_get_draftGet DraftA
Retrieve a specific draft by its key. Common keys: "new_topic" for new topic drafts, "topic_" for reply drafts.
| Name | Required | Description | Default |
|---|---|---|---|
| draft_key | Yes | Draft key (e.g., "new_topic", "topic_123", "new_private_message") | |
| sequence | No | Expected sequence number (optional) |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
No annotations are provided, so the description carries the full burden. It discloses the tool's behavior as a retrieval operation, but lacks details on permissions, error handling, rate limits, or what happens if the draft key is invalid. The mention of 'sequence' as optional in the schema is not explained in the description, leaving gaps in behavioral context.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is front-loaded with the core purpose in the first sentence, followed by helpful examples in the second sentence. Every sentence adds value without redundancy, making it efficient and well-structured for quick understanding.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
Given the tool's low complexity (simple retrieval), no annotations, and no output schema, the description is adequate but incomplete. It covers the purpose and basic usage but lacks details on behavioral aspects like return format, error cases, or prerequisites, which are important for a tool without structured annotations or output schema.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 100%, so the schema fully documents both parameters (draft_key and sequence). The description adds minimal value by providing examples of common keys ('new_topic', 'topic_<id>'), but does not explain parameter interactions or semantics beyond what the schema already covers, aligning with the baseline for high schema coverage.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description clearly states the verb ('Retrieve') and resource ('a specific draft'), and distinguishes it from siblings by specifying retrieval by key rather than listing all drafts (like discourse_list_drafts). The examples of common keys ('new_topic', 'topic_<id>') further clarify the scope and differentiate it from other retrieval tools.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The description provides clear context for when to use this tool (to retrieve a specific draft by key) and implies an alternative (discourse_list_drafts for listing drafts). However, it does not explicitly state when not to use it or compare it to all sibling tools, such as discourse_get_user or discourse_read_post, which might handle different resources.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
discourse_get_userGet UserC
Get basic user info.
| Name | Required | Description | Default |
|---|---|---|---|
| username | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
No annotations are provided, so the description carries the full burden of behavioral disclosure. 'Get basic user info' implies a read-only operation but doesn't specify permissions required, rate limits, error conditions (e.g., invalid username), or what 'basic' entails (e.g., public vs. private data). This leaves significant gaps for an agent to understand the tool's behavior.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is extremely concise with a single sentence ('Get basic user info.'), which is front-loaded and wastes no words. However, it borders on under-specification, as it could benefit from slightly more detail without losing efficiency.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
Given the tool's low complexity (1 parameter, no nested objects) but lack of annotations and output schema, the description is incomplete. It doesn't explain what 'basic user info' returns, potential errors, or usage context, leaving the agent with insufficient information to use the tool effectively beyond its basic purpose.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
The description doesn't mention the 'username' parameter at all, and schema description coverage is 0%, so it adds no meaning beyond the schema. With only one parameter and no schema descriptions, the baseline is 3, as the simple parameter is self-explanatory but undocumented. No compensation is provided for the coverage gap.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description 'Get basic user info' clearly states the verb ('Get') and resource ('user info'), making the purpose understandable. However, it's vague about what 'basic user info' includes and doesn't differentiate from potential sibling tools like 'discourse_list_user_posts' or 'discourse_list_user_chat_channels' that might also retrieve user-related data.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The description provides no guidance on when to use this tool versus alternatives. It doesn't mention prerequisites (e.g., needing a valid username), exclusions, or comparisons to sibling tools like 'discourse_search' that might also find user information. Usage is implied but not explicitly stated.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
discourse_list_categoriesList CategoriesB
List categories visible to the current auth context.
| Name | Required | Description | Default |
|---|---|---|---|
No parameters | |||
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
With no annotations, the description carries full burden but only states visibility based on auth context. It doesn't disclose behavioral traits like pagination, rate limits, sorting, or what 'visible' entails (e.g., public vs. private categories), which are critical for a list operation.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is a single, efficient sentence with no wasted words. It's front-loaded with the core purpose and includes essential context about auth visibility, making it appropriately sized for a simple tool.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a zero-parameter list tool with no annotations or output schema, the description is minimally adequate. It covers the basic purpose and auth scope but lacks details on behavior (e.g., output format, limitations), leaving gaps in completeness.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
There are 0 parameters, and schema description coverage is 100%, so no parameter documentation is needed. The description adds value by clarifying the auth context scope, which isn't in the schema, earning a baseline 4 for zero-param tools.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description clearly states the action ('List') and resource ('categories'), specifying they are 'visible to the current auth context'. It distinguishes from siblings like 'discourse_list_tags' or 'discourse_list_drafts' by focusing on categories, but doesn't explicitly differentiate beyond that.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
No guidance is provided on when to use this tool versus alternatives. It doesn't mention prerequisites, exclusions, or compare with other listing tools (e.g., 'discourse_list_tags'), leaving the agent to infer usage based on the name alone.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
discourse_list_chat_channelsList Chat ChannelsB
List all public chat channels visible to the current user. Returns channel information including title, description, and member counts.
| Name | Required | Description | Default |
|---|---|---|---|
| filter | No | Filter channels by name/slug | |
| limit | No | Number of channels to return (default: 25, max: 100) | |
| offset | No | Pagination offset (default: 0) | |
| status | No | Filter by channel status (e.g., 'open', 'closed', 'archived') |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
With no annotations provided, the description carries full burden for behavioral disclosure. It mentions that the tool returns 'channel information including title, description, and member counts', which provides some output context. However, it doesn't address important behavioral aspects like pagination behavior (implied by offset/limit parameters but not explained), rate limits, authentication requirements, or whether this is a read-only operation. For a listing tool with zero annotation coverage, this leaves significant gaps.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is perfectly concise with two sentences that each earn their place. The first sentence states the purpose and scope, while the second describes the return format. There's zero wasted language, and the most important information (what the tool does) is front-loaded.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a listing tool with comprehensive parameter documentation (100% schema coverage) but no annotations and no output schema, the description provides adequate but incomplete context. It covers the basic purpose and return format, but lacks behavioral details that would be important for an AI agent. The absence of output schema means the description should ideally provide more detail about the return structure, but it only mentions three fields without specifying format or completeness.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 100%, so the schema already fully documents all 4 parameters. The description adds no parameter-specific information beyond what's in the schema. According to scoring rules, when schema_description_coverage is high (>80%), the baseline is 3 even with no param info in the description. The description doesn't compensate with additional parameter context, but doesn't need to given the comprehensive schema.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description clearly states the verb 'List' and resource 'all public chat channels visible to the current user', making the purpose immediately understandable. It distinguishes from siblings like 'discourse_list_user_chat_channels' by specifying 'public' channels rather than user-specific ones. However, it doesn't explicitly contrast with other listing tools like 'discourse_list_categories' or 'discourse_list_tags'.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The description implies usage for retrieving public chat channels, but provides no explicit guidance on when to use this versus alternatives like 'discourse_list_user_chat_channels' or 'discourse_search'. It mentions 'visible to the current user' which provides some context about access permissions, but lacks clear when/when-not scenarios or named alternatives.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
discourse_list_draftsList DraftsA
List all drafts for the current user. Returns draft keys, sequences, and preview content. Use this to find existing drafts before updating them.
| Name | Required | Description | Default |
|---|---|---|---|
| offset | No | Pagination offset (default: 0) |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
With no annotations provided, the description carries the full burden. It discloses the return content ('draft keys, sequences, and preview content') and implies read-only behavior through 'List', but lacks details on permissions, rate limits, or pagination beyond the schema's offset parameter. This is adequate but has gaps for a tool with no annotations.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is two concise sentences that are front-loaded with the core purpose and efficiently add usage guidance. Every word contributes value, with no wasted text or redundancy.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
Given the tool's low complexity (1 parameter, no output schema, no annotations), the description covers the purpose and usage well. However, it lacks details on behavioral aspects like authentication needs or error handling, which would be beneficial for a tool with no annotations, making it minimally complete but not thorough.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 100%, so the schema fully documents the single parameter (offset). The description adds no parameter-specific information beyond what's in the schema, resulting in the baseline score of 3 for adequate coverage without extra value.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description clearly states the action ('List all drafts') and resource ('for the current user'), distinguishing it from siblings like discourse_get_draft (singular) or discourse_list_user_posts (different resource). However, it doesn't explicitly differentiate from all list-type siblings (e.g., discourse_list_categories), making it a 4 rather than a 5.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The description provides clear context for usage ('Use this to find existing drafts before updating them'), which implicitly suggests when to use it. However, it doesn't explicitly state when NOT to use it or name specific alternatives among siblings, so it falls short of a perfect 5.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
discourse_list_tagsList TagsC
List tags (if enabled).
| Name | Required | Description | Default |
|---|---|---|---|
No parameters | |||
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
No annotations are provided, so the description carries the full burden. It mentions 'if enabled,' which implies a conditional availability, but doesn't disclose other behavioral traits like whether this is a read-only operation, pagination behavior, rate limits, or what happens if tags are disabled. For a tool with zero annotation coverage, this leaves significant gaps.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is extremely concise at just three words, with zero wasted text. It's front-loaded with the core action and resource, and the parenthetical adds necessary context without verbosity. Every word earns its place.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
Given the tool's simplicity (0 parameters, no output schema, no annotations), the description is incomplete. It doesn't explain what 'tags' are in this Discourse context, what 'if enabled' entails, or what the return value looks like. For a list operation, even with no parameters, more context on output and behavior would be helpful.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
The tool has 0 parameters, and schema description coverage is 100%, so there's no need for parameter details in the description. The baseline for 0 parameters is 4, as the description doesn't need to compensate for missing schema information. No additional parameter semantics are required.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description 'List tags (if enabled)' states the verb ('List') and resource ('tags'), making the basic purpose clear. However, it's vague about what 'tags' are in this context and doesn't differentiate from sibling tools like 'discourse_list_categories' or 'discourse_list_user_posts' beyond the resource name. The parenthetical '(if enabled)' adds some context but doesn't fully specify scope.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The description provides no guidance on when to use this tool versus alternatives. There's no mention of prerequisites (e.g., needing authentication), comparison to similar list tools, or exclusions. The '(if enabled)' hints at a conditional context but doesn't explain what enables tags or when this tool would fail.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
discourse_list_user_chat_channelsList User's Chat ChannelsA
List all chat channels for the currently authenticated user, including both public channels they're a member of and direct message channels. Includes unread tracking information.
| Name | Required | Description | Default |
|---|---|---|---|
No parameters | |||
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
With no annotations provided, the description carries the full burden of behavioral disclosure. It mentions authentication requirement and unread tracking information, which are useful behavioral traits. However, it doesn't cover other important aspects like pagination, rate limits, error conditions, or response format, leaving gaps for a tool that likely returns a list of channels.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is a single, well-structured sentence that efficiently conveys the tool's purpose, scope, and key feature (unread tracking). Every element earns its place with no redundant information, making it appropriately front-loaded and concise.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
Given the tool's moderate complexity (listing user-specific channels with tracking), no annotations, and no output schema, the description is somewhat complete but has gaps. It covers authentication context and what's included, but doesn't describe the response structure, potential limitations, or how unread tracking is presented, which would help the agent use it effectively.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
The tool has 0 parameters with 100% schema description coverage, so the baseline is 4. The description appropriately doesn't discuss parameters since none exist, and it adds value by explaining what the tool returns (channels with unread tracking) beyond what the empty schema provides.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description clearly states the tool's purpose: listing all chat channels for the authenticated user, specifying both public channels they're a member of and direct message channels. It distinguishes from siblings like 'discourse_list_chat_channels' by focusing on user-specific channels rather than all channels, though it doesn't explicitly name this distinction.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The description implies usage context by mentioning 'currently authenticated user' and including unread tracking, suggesting it's for personal chat management. However, it lacks explicit guidance on when to use this tool versus alternatives like 'discourse_list_chat_channels' or 'discourse_get_chat_messages', leaving the agent to infer based on scope.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
discourse_list_user_postsList User PostsA
Get a list of user posts and replies from a Discourse instance, with the most recent first. Returns 30 posts per page by default. Use the page parameter to paginate (page 0 = offset 0, page 1 = offset 30, etc.).
| Name | Required | Description | Default |
|---|---|---|---|
| username | Yes | ||
| page | No |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
With no annotations provided, the description carries the full burden of behavioral disclosure. It effectively describes key behaviors: the ordering ('most recent first'), pagination details (30 posts per page, page parameter usage), and that it returns a list. It doesn't cover aspects like rate limits, authentication needs, or error handling, but for a read-only list tool, this is reasonably transparent.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is front-loaded with the core purpose, followed by essential behavioral details (ordering, pagination). Every sentence adds value: the first states what the tool does, the second specifies default behavior and pagination mechanics. There's no wasted text, making it highly efficient.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
Given the tool's moderate complexity (list operation with pagination), no annotations, and no output schema, the description is fairly complete. It covers purpose, ordering, pagination, and parameter usage. It doesn't describe the return format (e.g., structure of posts) or error cases, but for a list tool, this is a minor gap.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
The input schema has 0% description coverage, so the description must compensate. It explains the 'page' parameter's semantics in detail (pagination logic with offsets), which adds significant value beyond the schema's type constraints. It doesn't explicitly describe the 'username' parameter, but its purpose is implied by the tool name and context. This partial coverage is adequate given the low schema coverage.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description clearly states the tool's purpose: 'Get a list of user posts and replies from a Discourse instance, with the most recent first.' It specifies the verb ('Get'), resource ('user posts and replies'), and scope ('from a Discourse instance'). However, it doesn't explicitly differentiate from sibling tools like discourse_get_user or discourse_read_post, which might also retrieve user-related content.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The description implies usage by mentioning pagination ('Use the page parameter to paginate'), suggesting this tool is for retrieving multiple posts. However, it doesn't provide explicit guidance on when to use this tool versus alternatives like discourse_search or discourse_read_topic, nor does it mention any prerequisites or exclusions.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
discourse_read_postRead PostC
Read a specific post.
| Name | Required | Description | Default |
|---|---|---|---|
| post_id | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
With no annotations provided, the description carries the full burden of behavioral disclosure. It states 'Read a specific post,' which implies a read-only operation but does not specify details like authentication requirements, rate limits, error handling, or what data is returned (e.g., content, author, timestamps). This leaves significant gaps for a tool with no annotation coverage.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is extremely concise with a single sentence ('Read a specific post.'), which is front-loaded and wastes no words. It efficiently conveys the core action without unnecessary elaboration, making it easy to parse.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
Given the tool's simplicity (one parameter, no annotations, no output schema), the description is incomplete. It does not explain what 'reading' entails (e.g., output format), potential errors, or usage context. For a tool with no structured support, more detail is needed to guide effective use.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
The input schema has one parameter (post_id) with 0% description coverage, so the schema provides no semantic details. The description adds no information about the parameter, such as what post_id represents or how to obtain it. However, with only one parameter and a straightforward tool, the baseline is 3 as the schema minimally defines the requirement.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description states the basic action ('Read') and resource ('a specific post'), which is clear but minimal. It distinguishes from siblings like 'discourse_read_topic' by specifying 'post' rather than 'topic', but lacks detail on what reading entails (e.g., retrieving content, metadata). The purpose is vague beyond the basic verb-noun pairing.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
No guidance is provided on when to use this tool versus alternatives. It does not mention prerequisites (e.g., needing a valid post_id), exclusions, or comparisons to siblings like 'discourse_read_topic' or 'discourse_list_user_posts'. The description offers no contextual usage information.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
discourse_read_topicRead TopicC
Read a topic metadata and first N posts.
| Name | Required | Description | Default |
|---|---|---|---|
| topic_id | Yes | ||
| post_limit | No | ||
| start_post_number | No | Start from this post number (1-based) |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
With no annotations provided, the description carries full burden for behavioral disclosure. It mentions reading metadata and posts, implying a read-only operation, but doesn't specify authentication requirements, rate limits, error conditions, or what 'first N posts' means in practice (e.g., ordering, pagination). The description is minimal and lacks critical operational context.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is extremely concise—a single sentence that directly states the tool's function without any fluff. It's front-loaded with the core action and resource, making it efficient and easy to parse. Every word earns its place.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
Given 3 parameters with low schema coverage (33%), no annotations, and no output schema, the description is insufficient. It doesn't explain return values, error handling, or important behavioral aspects like what 'metadata' includes or how posts are ordered. For a tool with this complexity and lack of structured data, more descriptive context is needed.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 33% (only start_post_number has a description). The description mentions 'first N posts' which hints at post_limit, but doesn't explain topic_id or provide additional context beyond the schema. Since schema coverage is low (<50%), the description should compensate more but only adds marginal value, warranting a baseline 3.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description clearly states the verb 'Read' and the resource 'topic metadata and first N posts', making the purpose unambiguous. It distinguishes from siblings like discourse_read_post (which reads individual posts) and discourse_filter_topics (which filters topics rather than reading a specific one). However, it doesn't explicitly contrast with all siblings, keeping it at 4 rather than 5.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The description provides no guidance on when to use this tool versus alternatives. It doesn't mention when to choose this over discourse_read_post for reading posts within a topic, or when to use discourse_filter_topics for topic discovery. There's no context about prerequisites, permissions, or typical use cases.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
discourse_searchDiscourse SearchC
Search site content.
| Name | Required | Description | Default |
|---|---|---|---|
| query | Yes | Search query | |
| with_private | No | ||
| max_results | No |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
No annotations are provided, so the description carries the full burden. It only states the action ('search') without disclosing behavioral traits such as authentication requirements, rate limits, whether results are paginated, or what the output format looks like. For a search tool with no annotation coverage, this leaves significant gaps in understanding its behavior.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is extremely concise with just three words, front-loaded and zero waste. It efficiently conveys the core purpose without unnecessary elaboration, making it easy to scan and understand quickly.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
Given the tool's complexity (search functionality with 3 parameters), no annotations, no output schema, and low schema description coverage, the description is incomplete. It doesn't cover key aspects like result format, error handling, or behavioral constraints, leaving the agent with insufficient information to use the tool effectively.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 33% (only 'query' has a description), with 3 parameters total. The description adds no meaning beyond the schema—it doesn't explain what 'with_private' does (e.g., include private content) or how 'max_results' affects pagination. It fails to compensate for the low coverage, leaving most parameters undocumented in both schema and description.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description 'Search site content' clearly indicates the verb 'search' and resource 'site content', but it's vague about what 'site content' encompasses (topics, posts, users, etc.) and doesn't distinguish from siblings like discourse_filter_topics or discourse_select_site. It states what the tool does but lacks specificity.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
No guidance on when to use this tool versus alternatives is provided. It doesn't mention when to prefer this over siblings like discourse_filter_topics for filtering or discourse_select_site for site selection, nor does it specify any prerequisites or exclusions. The description offers no usage context.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
discourse_select_siteSelect SiteA
Validate and select a Discourse site for subsequent tool calls.
| Name | Required | Description | Default |
|---|---|---|---|
| site | Yes | Base URL of the Discourse site |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
No annotations are provided, so the description carries the full burden of behavioral disclosure. It mentions 'validate and select,' which hints at setup and verification, but doesn't explain what validation entails (e.g., checking site accessibility, permissions), whether it stores state for subsequent calls, or any error handling. For a tool with no annotations, this leaves significant gaps in understanding its behavior.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is a single, efficient sentence: 'Validate and select a Discourse site for subsequent tool calls.' It is front-loaded with the core purpose and wastes no words, making it highly concise and well-structured for quick understanding.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
Given the tool's low complexity (1 parameter, no output schema, no annotations), the description is minimally adequate. It covers the basic purpose and usage context but lacks details on behavioral aspects like validation specifics or state management. Without annotations or output schema, it should do more to compensate, but it meets the minimum for a simple setup tool.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
The input schema has 100% description coverage, with the 'site' parameter documented as 'Base URL of the Discourse site.' The description doesn't add any meaning beyond this, such as format examples or validation rules. With high schema coverage, the baseline score of 3 is appropriate, as the schema handles the parameter documentation adequately.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description clearly states the tool's purpose: 'Validate and select a Discourse site for subsequent tool calls.' It specifies the action (validate and select) and the resource (Discourse site), making it easy to understand. However, it doesn't explicitly differentiate from sibling tools, which are all for interacting with Discourse sites but serve different functions like listing or reading content.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The description provides clear context on when to use this tool: 'for subsequent tool calls,' implying it should be called first to set up the site for other operations. It doesn't specify when not to use it or name alternatives, but the context is sufficient for basic guidance without being explicit about exclusions.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
TDQS
Every tool has a clearly distinct purpose targeting specific resources and actions in the Discourse platform. For example, discourse_filter_topics handles topic filtering, discourse_get_chat_messages retrieves chat messages, and discourse_list_categories lists categories—each serves a unique function with no overlap that would cause confusion.
All tool names follow a consistent verb_noun pattern with the 'discourse_' prefix, such as discourse_list_categories, discourse_get_user, and discourse_read_post. This uniformity makes the tool set predictable and easy to navigate, with no deviations in naming conventions.
With 14 tools, this server is well-scoped for interacting with a Discourse forum, covering key areas like topics, posts, chats, users, categories, tags, drafts, and search. Each tool earns its place by addressing a specific need without being overly broad or sparse.
The tool set provides comprehensive coverage for reading, listing, filtering, and searching core Discourse resources, with minor gaps. For instance, it lacks tools for creating or updating content (e.g., posting new topics or replies), which agents might need for full CRUD operations, but existing tools support most common workflows effectively.
Maintenance
Resources
Unclaimed servers have limited discoverability.
Looking for Admin?
If you are the server author, to access and configure the admin panel.
Related MCP Connectors
Search or monitor any Flarum-powered forum for discussions, replies, participants, dates, and…
Read-only Reddit search API for AI agents: posts, comments, comment trees, subreddit rules.
Control your Discord community: send/read messages, manage channels and forums, and handle webhook…
Connect AI agents to 1000+ apps with managed authentication and tool-calling.
Related MCP Servers
- AlicenseAqualityCmaintenanceEnables interaction with USCardForum, a Discourse-based community for US credit cards and points. Supports topic discovery, content reading, user research, forum search, and authenticated actions like notifications and bookmarks.22MIT
- AlicenseAqualityDmaintenanceEnables AI assistants to interact with USCardForum, a Discourse-based community focused on US credit cards and points, providing access to topics, user profiles, search, and authenticated actions like notifications and bookmarks.22MIT
- AlicenseAqualityCmaintenanceEnables AI agents to interact with Discourse forums through search, reading topics/posts, managing categories and tags, chat channels, and optionally creating content with safeguarded write operations.153,156MIT
- FlicenseAqualityDmaintenanceConnects AI assistants to thousands of Tapatalk-enabled forums, enabling browsing, reading, searching, and optional posting.14
Latest Blog Posts
- Who's Calling? MCP Hosts Are an Identity Blind Spot (And the Spec Knows It)By Om-Shree-0709 on .mcpAgent IdentityOAuth 2.1
- Your AI Chatbot Just Exposed Your CEO's Salary to an InternBy Om-Shree-0709 on .Agent IdentityMCP SecurityOAuth Delegation
- Why MCP Servers Need Execution Sandboxing (And Why Your Current Stack Isn't Enough)By Om-Shree-0709 on .Agentic AiPrompt InjectionWebAssembly
MCP directory API
We provide all the information about MCP servers via our MCP API.
curl -X GET 'https://glama.ai/api/mcp/v1/servers/discourse/discourse-mcp'
If you have feedback or need assistance with the MCP directory API, please join our Discord server