Discord bot for the MDN MCP https://developer.mozilla.org/en-US/blog/introducing-mdn-mcp-server/
mdn-discord
A Discord bot that exposes the MDN MCP server's tools as slash commands. It discovers the tools at runtime over the Model Context Protocol and generates one slash command per tool, so when MDN adds, removes, or changes a tool, the bot picks it up on its own, with no code changes or redeploys.
Out of the box you get:
| Command | MDN tool | What it does |
| --- | --- | --- |
| /search query:<terms> | search | Search MDN documentation |
| /get-doc path:<path or URL> | get-doc | Fetch an MDN page as markdown |
| /get-compat key:<BCD key> | get-compat | Fetch browser compatibility data |
These three are not hard-coded; they are generated from the server's tools/list.
How it works
- On startup the bot connects to the MDN MCP server (Streamable HTTP transport).
- It calls
tools/listand translates each tool's JSON Schema input into a Discord slash command (schema.ts), then registers the commands with Discord. - When a command is used, the bot maps the options back to the tool's arguments,
calls
tools/call, and renders the result as readable Discord markdown (format.ts): browser compatibility data becomes a per-feature support digest, other JSON becomes a key/value tree, and documentation markdown keeps its formatting with relative MDN links rewritten to absolute (clickable) ones. Search results show a ready-to-run/get-doccommand for each hit. - Long output is split across multiple messages, kept whole at heading/section boundaries, with link embeds suppressed. Nothing is hidden in a file; a safety cap of 20 messages prevents flooding (with an explicit notice if exceeded).
- It re-checks the tool list on an interval (and on
tools/list_changednotifications) and re-registers commands only when the tool set has changed.
Output formatting
format.ts turns each tool result into one or more Discord messages. Three
behaviors are worth calling out.
Section-aware message splitting
Discord caps a message at 2000 characters. Output is split into messages that
fill up to just under that limit, but break points are chosen to keep content
coherent (splitIntoBlocks, packMessages, chunkText):
- The text is divided into blocks at Markdown heading lines (
#through######); each block is a heading and the content beneath it. - Blocks are packed greedily, and a block that does not fit moves to the next message whole, so a section like "Technical reference" is never cut in half.
- Only a single block larger than one message is split internally, and then evenly by line so the pieces are balanced rather than one full message plus a tiny scrap.
- At most 20 messages are sent. If output exceeds that, the last message says so explicitly rather than dropping data silently.
Link rewriting
MDN markdown uses root-relative links, which Discord cannot render as clickable.
fixMarkdownLinks rewrites them:
[text](/en-US/docs/...)becomes[text](https://developer.mozilla.org/en-US/docs/...)so Discord shows a clickable link.- In-page anchor links such as
[See also](#see_also)cannot resolve in Discord and are reduced to their plain text. - Absolute external links (spec or Wikipedia URLs, etc.) are left untouched.
Search paths as commands
A /search result includes each document's path. rewriteSearchPaths turns that
path line into a ready-to-run command:
`path`: `/en-US/docs/Glossary/Grid` -> `/get-doc path:/en-US/docs/Glossary/Grid`
The command and option names are resolved from the live command registry, so this
stays correct if you set COMMAND_PREFIX (it renders /mdn-get-doc ...) or if
MDN renames the tool.
Setup
Requires Bun.
bun install
cp .env.example .env # then fill in DISCORD_TOKEN and DISCORD_CLIENT_ID
bun run dev # or: bun run start
Invite the bot with the applications.commands scope. Set DISCORD_GUILD_ID.
Configuration
| Variable | Required | Default | Description |
| --- | --- | --- | --- |
| DISCORD_TOKEN | yes | none | Bot token |
| DISCORD_CLIENT_ID | yes | none | Application (client) ID |
| DISCORD_GUILD_ID | no | none | Deploy to one guild for instant updates |
| MDN_MCP_URL | no | https://mcp.mdn.mozilla.net/ | MCP endpoint |
| COMMAND_PREFIX | no | none | Prefix on every command, e.g. mdn- |
| MDN_OPT_OUT | no | false | Send MDN's analytics opt-out header |
| REFRESH_INTERVAL_MS | no | 900000 | Tool-list re-check interval |
| LOG_LEVEL / NODE_ENV | no | info / none | Logging level / pretty vs JSON logs |
Scripts
bun run dev # watch mode
bun run start # run once
bun run typecheck # tsc --noEmit
bun run check # biome lint + format check
bun test # unit tests
Deployment
docker compose up -d --build
Attribution and acceptable use
MDN, MDN Web Docs, and related names and logos are trademarks or property of Mozilla. This project is an unofficial, community-built bot and is not affiliated with, endorsed by, or sponsored by Mozilla.
All documentation and browser compatibility data the bot displays belong to Mozilla and its contributors and remain subject to MDN's licensing and attribution terms.
The bot talks to Mozilla's experimental MDN MCP server. Please use it
responsibly and within Mozilla's
Acceptable Use Policy:
do not hammer the endpoint, keep REFRESH_INTERVAL_MS reasonable, and note the
service is experimental and may change or be withdrawn at any time. See the
MDN MCP docs for privacy details, and
set MDN_OPT_OUT=1 to send the documented analytics opt-out header.
License
MIT. This license covers this bot's source code only, not any MDN content it retrieves or displays.