
Your agent needs to work inside Discord. It answers questions in a customer's support channel, opens threads for bug reports, or checks which servers a user belongs to before unlocking a feature. Search for a Discord MCP server and you find dozens of repositories, none of them from Discord. The real decision sits one layer down: bot token or OAuth2 user token, and who holds that credential when the agent serves a hundred customer servers instead of one.
A production Discord agent reaches the platform one of three ways. They differ in who wrote the code that holds the credential and in which Discord identity the agent acts with.
Community servers are open-source MCP servers written by individual developers. Many run locally over stdio: the MCP client launches the process and passes a token such as DISCORD_TOKEN through the env block of its config file. Others run as a local HTTP service in a container.
They come in two families. Bot-token servers expose tools for sending and reading messages, managing channels and roles, forum posts, reactions, and webhooks across the servers the bot has joined. Self-bot servers ask for a personal account token, sometimes extracted from a logged-in browser session, and several of their own READMEs warn that this violates Discord's terms and can get the account banned.
The Discord API has three surfaces: a versioned REST API (v10 today), the Gateway WebSocket for real-time events, and interactions for slash commands and message components.
Authentication splits into two token types. A bot token, sent as Authorization: Bot <token>, authenticates the bot user; Discord's OAuth2 documentation states that bot users have access to most API routes without bearer tokens and can connect to the Gateway. OAuth2 bearer tokens come from the Authorization Code grant, expire after 604,800 seconds (seven days), and ship with a refresh token. The bot authorization flow, which uses the bot scope and a permissions integer, is how a server admin installs your bot.
Scalekit has two connectors for Discord. The Discord connector runs OAuth 2.0 with PKCE against each user's Discord account and ships 34 tools; Scalekit stores and refreshes the tokens. The Discord Bot connector stores a bot token per connected account and ships 221 tools across messages, threads, members, roles, moderation, webhooks, and interactions.
Your agent calls either one with execute_tool in Python or executeTool in Node.js, or reaches both through a Virtual MCP server that Scalekit hosts.
Three questions decide the path: what the agent can do, which identity it acts with, and how far one credential reaches.
Coverage on Discord follows the token type, not the transport.
Discord's OAuth2 scopes describe identity and installation. The only message scope, messages.read, applies to the local RPC server, and dm_channels.read is reserved for approved partners. Reading conversations, and nearly everything that writes to them, runs on the bot token; an incoming webhook can post to one channel but cannot read.
That is why the discord connector has no message tools and discordbot has dozens. Treat per-user OAuth as account linking: confirm a user's Discord identity, check server membership before granting access, sync role connections, or read entitlements. Treat the bot as the actor that reads and writes in channels.
Community MCP servers put a long-lived token in a file on the machine running the agent. The direct API gives you both token types and nothing around them. Scalekit puts both behind connected accounts: the OAuth path handles consent, storage, and refresh, and the bot path stores one token per connected account and injects it on every call.
The token types behave differently. An OAuth2 access token expires in seven days, refreshes with a refresh token, and revoking one token revokes every token from that authorization. A bot token has no expiry and no OAuth scopes. It stays valid until it is reset, and its reach is the role permissions the bot holds in each server.
If you run one Discord application for all customers, its token works in every server the bot has joined. An agent acting for tenant A can post into tenant B's channel the moment it gets B's channel ID, through a prompt injection or a bug.
Neither transport fixes that. Isolation comes from one of two designs: pin server and channel IDs from the tenant record and treat model-supplied IDs as untrusted, or have each customer register its own Discord application so every tenant has a separate token. The single-tenant vs multi-tenant tool calling guide covers the general pattern.
Discord's OAuth2 documentation tells developers to refrain from automating standard user accounts, and the Discord Developer Policy prohibits requesting passwords or login tokens from users. A product that runs a self-bot MCP server is asking customers for their account token. Flagged accounts can be terminated, and the policy gives Discord grounds to remove your application.
Whichever path you choose, Discord's platform rules apply to your agent the same way they apply to any bot. Four of them shape agent design.
Discord's Rate Limits documentation sets a global limit of 50 requests per second per bot, independent of per-route limits; interaction endpoints are exempt. Separately, an IP address that makes 10,000 invalid requests (401, 403, or 429) within 10 minutes is temporarily restricted from the API.
Agents hit the second limit in ways scripts rarely do. A loop that retries a revoked token, or keeps calling a channel the bot lost access to, burns the budget fast. When every tenant's agent egresses from the same IP, one misbehaving loop can lock out all of them. Stop on 401, check permissions before acting, and honor retry_after.
Discord gates three data types behind privileged intents: Guild Presences, Guild Members, and Message Content. Without Message Content, the content, embeds, attachments, components, and poll fields arrive empty, apart from a few exceptions such as messages that mention the bot.
Since June 10, 2026, apps that reach fewer than 10,000 unique users toggle these intents in the Developer Portal. Past that threshold you get 90 days to apply, and approved apps reapply every year. A support agent that reads channel history needs Message Content, so document the use case from day one. Agents that act only on slash command input can skip it.
The Discord Developer Policy limits API Data to what your stated functionality needs and prohibits using message content to train machine learning or AI models, including LLMs, without Discord's express permission. Sending channel messages to a model to answer a question can fall within stated functionality; piping stored transcripts into a fine-tuning set is training. The Developer Terms also require you to notify Discord and affected users of potential unauthorized access to API Data, which can turn a leaked bot token into a disclosure event.
Community servers change when their maintainer changes them, often without versioning. The direct API is versioned, but you own every adapter, pagination rule, and deprecation. Scalekit maintains tool schemas against Discord's API; the connector already marks discord_get_invite_deprecated and points agents to discord_resolve_invite. On any path, pin the tool names your agent depends on and test them before each release.
Community servers are a reasonable way to explore what a Discord agent should do. Running one in front of customers moves six specific risks into your product.
The bot token sits in plaintext in an MCP client config file or a shell environment. Any process, extension, or synced backup that can read that file can take it. A bot token never expires on its own, so a copied token stays valid until someone notices and resets it, and resetting it breaks every deployment that shares it. Whoever holds the token controls the bot.
The MCP server process holds the token and decides which API calls to make. An unpinned npx -y launch command can pick up a newly published release without review, so one compromised version can exfiltrate the token or post into every server the bot has joined. Tool descriptions are model input too, and a malicious update can rewrite them to steer the agent. The MCP security risks breakdown covers these supply-chain patterns.
Discord content is untrusted input. Any member of any server the bot reads can post text designed to redirect the agent toward banning a user or posting a link. A community server that exposes kick, ban, and delete tools next to message reading gives that injected text real reach. Keep destructive tools out of any agent role that reads open channels.
Most community servers have one maintainer and no support commitment. Discord keeps moving: the privileged intent threshold switched from 100 servers to 10,000 users in June 2026, and endpoints get deprecated. When a server lags, your agent fails at runtime, often with a 401 or 403 that also counts against the invalid-request limit.
Calls go from the MCP process straight to Discord, and nothing records which of your users prompted which action. Discord's server audit log attributes moderation actions to the bot account, not to the person or tenant behind the request. When a customer asks why the bot deleted a thread, there is no answer. Audit trails for agent auth explains what that record needs to contain.
Self-bot servers violate Discord's terms outright. Bot-token servers leave every Developer Policy obligation with you: data minimization, no training on message content, and breach notification. If unreviewed code logs message content to disk or forwards it elsewhere, the violation is yours, not the maintainer's.
Each path has a legitimate use. The deciding questions are whose servers the agent touches and how many.
Pick any path and you still hold Discord credentials on behalf of other people. The transport never manages them for you.
A B2B agent serving 40 customer communities holds up to 40 bot tokens if customers bring their own application, plus one OAuth grant per linked user. Each bot token needs encrypted storage and a rotation path. Each user grant needs a refresh before its seven-day expiry and cleanup when the user revokes the app. That work is identical whether the agent calls tools over MCP or REST. Token vaults for agent workflows covers the storage model.
A user deauthorizes your app, or a tenant churns. The agent has to stop acting instead of discovering the failure mid-task through a burst of 401s that also count toward the invalid-request ban. For OAuth grants, Scalekit emits connected_account.status_updated and connected_account.token_refresh_failed webhooks so your system can pause the agent and request re-authorization. A bot removed from a server keeps a valid token but loses access, so treat a 403 on a tenant's channel as a stop signal, not a retry.
Scalekit's Discord connectors handle the OAuth flow, token storage, and refresh for user grants, and vault one bot token per connected account, so the MCP vs API decision doesn't change your credential infrastructure. Credentials are encrypted with AES-256, resolved at request time, and never placed in the model's context.
The target is a community support agent for a tenant called org_acme: a bot that reads a support channel and replies, plus optional user identity linking. Install the SDK with pip install scalekit-sdk-python, then set SCALEKIT_ENVIRONMENT_URL, SCALEKIT_CLIENT_ID, and SCALEKIT_CLIENT_SECRET from Developers > API Credentials in the Scalekit dashboard.
Create one application in the Discord Developer Portal. In the Scalekit dashboard, open AgentKit > Connections > Create Connection, pick Discord, and paste the redirect URI it shows into the application's OAuth2 redirects. Discord matches the URI exactly, so a trailing slash fails the flow with invalid_redirect_uri. Add the client ID, client secret, and only the scopes you need, such as identify and guilds. If you also install the bot through this flow, set Bot Permissions explicitly; an empty value grants zero permissions.
On the application's Bot page, reset and copy the token, invite the bot to your test server with the URL generator's bot scope and the smallest permission set that works, and enable the Message Content intent if the agent reads messages. Then create a Discord Bot connection in Scalekit. Every connection_name you pass must match the connection name in the dashboard exactly. The connection names here are discord, discordbot, and linear.
get_or_create_connected_account returns the user's connected account. If it isn't active, the user completes Discord's consent screen through the authorization link. Once it is active, tools run against that user's token.
The bot connector has no consent screen. You write the token to a connected account keyed by your tenant ID, through the API in production or the connection's Connected Accounts tab for testing.
The agent does not load the 221-tool catalog. actions.langchain.get_tools returns the tools authorized for this tenant's connected account, filtered to the two the support role needs, as native LangChain tools. A pre_modifier overwrites channel_id before every execution, so the model cannot redirect the bot to another server's channel. Install langchain-anthropic alongside the SDK.
Modifiers register on the client and run inside the SDK's execute_tool path. In a multi-tenant server, resolve the channel from request context inside the modifier instead of a module-level constant. The LangChain tool calling guide and the LangChain example go deeper.
A Virtual MCP server declares which connections and tools an agent role can see. Create it once per role; its mcp_server_url is static and shared across tenants. This one exposes three Discord Bot tools and one Linear tool, so the tenant also needs an active Linear connected account under the same identifier.
Before each run, confirm the tenant's connections are active and mint a short-lived session token bound to that tenant. The URL is shared; the token carries the identity. Claude's MCP connector (beta) calls the server directly, so there is no tool loop to write. Install anthropic alongside the SDK.
Virtual MCP scopes which tools exist; it does not rewrite arguments. Pair it with a per-tenant bot token so a wrong channel ID can only reach that tenant's own servers.
The Node.js SDK does not mint Virtual MCP session tokens yet, so mint the token on a Python backend with create_session_token and pass the URL and token to the Mastra process. Install @mastra/core@1, @mastra/mcp@2, @ai-sdk/openai@4, and dotenv, then run the file with npx tsx agent.mts.
The Mastra tool calling guide covers the full setup.
Two properties matter once a Discord agent leaves the prototype: seeing what it did, and limiting what it can reach.
Every execute_tool call returns an execution_id. In the dashboard, AgentKit > Connected Accounts shows each account's status, token refresh history, and tool execution logs, so a ticket about a deleted message traces back to a tenant, a connected account, and a specific call. The Discord connector page lists a 90-day audit trail. That is the record Discord's own audit log cannot give you, because it only names the bot.
Tool surface is a cost problem and an accuracy problem. At the roughly 200 tokens per tool that Scalekit's Virtual MCP docs use as a rule of thumb, exposing all 221 Discord Bot tools spends about 44,000 tokens of context before the agent does any work. A model choosing among ban, kick, prune, and delete tools on every turn is one bad inference from an incident. The discord-community-support server exposes four tools.
One Virtual MCP server definition serves every tenant, and each run gets a session token scoped to one identifier. Adding GitHub or Zendesk to the same role is a config change, not another server to host and patch. Virtual MCP servers explained and when to use a Virtual MCP server cover the model in depth.
The support triage agent and incident response agent templates use the same connected-account pattern with other tools; Discord Bot slots in as the chat surface. The free tier on the pricing page includes 5,000 tool calls a month and unlimited connected accounts.
If the agent lives on your own test server and touches nobody else's data, a community MCP server with a minimal-permission bot is a fine way to learn. Never run it with a self-bot token. If you run one bot for one community and need Gateway events end to end, the direct API gives you full control and full operational ownership.
If your agent works inside customers' Discord servers, the question stops being MCP versus REST. It becomes how many bot tokens and user grants you hold, how each one is isolated, and what record exists when the bot acts. That is credential infrastructure, and it is the same work on every path.
Building a Discord agent for customer communities? Talk to us for help with the auth model, or start from the Discord connector and the Discord Bot connector.