TL;DR
- Discord has no official MCP server. Community servers run on a bot token or, in self-bot variants, a user's account token, which Discord's terms prohibit.
- OAuth2 user tokens cover identity: profile, server membership, linked accounts, and entitlements. Messages, threads, and moderation run on a bot token, so most Discord agents act as a bot.
- A bot token has no OAuth scopes and no expiry, and one bot shared across customers reaches every customer's server.
- The direct API gives you the full surface, plus the 50 requests per second limit, the invalid-request IP ban, and privileged intent review.
- Scalekit's Discord (OAuth, 34 tools) and Discord Bot (221 tools) connectors vault both token types, log every tool call, and serve over a Virtual MCP server scoped per agent role. ## The decision your Discord agent actually faces
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.
What the three Discord paths actually are
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 Discord MCP servers
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 directly
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's Discord connectors
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. The product pages for the Discord connector and the Discord Bot connector summarize both.
Comparing them where it matters for agents
Three questions decide the path: what the agent can do, which identity it acts with, and how far one credential reaches.
What your agent can actually do
Coverage on Discord follows the token type, not the transport.
| Capability | Community MCP | Discord API | Scalekit |
|---|---|---|---|
| User profile, linked accounts | Rare | OAuth2 bearer | discord_get_my_user |
| User's servers, member record | Varies | OAuth2 bearer | discord_list_my_guilds |
| Channel message history | Bot or self-bot | Bot, Message Content intent | discordbot_list_channel_messages |
| Send messages, replies | Yes | Bot | discordbot_create_message |
| Threads, forum posts | Some | Bot | discordbot_start_thread_from_message |
| Kick, ban, AutoMod | Some | Bot, role permissions | discordbot_create_guild_ban |
| Server audit log | Rare | Bot | discordbot_get_guild_audit_log |
| Slash command responses | Rare | Gateway or interactions endpoint | discordbot_create_interaction_response |
| Real-time events | Needs Gateway | Gateway | Your own listener |
| User's own DMs | Self-bot only | Approved partners | Not available |
Where the user-token ceiling sits
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.
The auth path each one puts you on
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.
One bot token can reach every customer
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.
The self-bot shortcut is a policy violation
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.
What you own in production
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.
Rate limits and the invalid-request ban
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.
Privileged intents gate what the bot can read
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.
Data handling obligations
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.
Schema and maintenance drift
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.
The risks of running an unofficial Discord MCP server
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.
Credential exposure
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.
Unreviewed code holding server-wide permissions
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.
Every server member can write into your agent's context
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.
Maintenance and breakage
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.
No audit trail
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.
Policy liability
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.
When to use each path
Each path has a legitimate use. The deciding questions are whose servers the agent touches and how many.
Use a community MCP server when
- You are prototyping against your own test server with a throwaway bot that holds only the permissions the prototype needs.
- You want to learn which tool shapes a Discord agent actually uses before designing the production surface.
-
The agent runs on one developer's machine, touches no customer servers, and never uses a self-bot token.
Use the Discord API directly when
The agent reacts to Gateway events, such as every new message or member join, or owns slash commands end to end.
You run one bot for your own community and will operate sharding, rate-limit handling, and intent review yourself.
-
Request volume sits near the 50 requests per second global limit and you need direct control of scheduling across shards.
Use Scalekit's Discord connectors when
You ship a B2B agent that works inside many customers' servers and need one credential per tenant, per-tenant logs, and clean revocation.
The same product links each user's Discord identity through OAuth and acts in channels through a bot.
The agent spans Discord plus tools like Linear, GitHub, or Zendesk and should reach them through one scoped MCP endpoint.
-
Different agent roles, such as a support responder and a moderator, need different tool surfaces from the same connectors.
The credential problem that exists on every path
Pick any path and you still hold Discord credentials on behalf of other people. The transport never manages them for you.
N tenants, N bot tokens, N user grants
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.
Revocation has to reach the agent
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.
Where Scalekit fits
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.
Connecting Discord to your agent with Scalekit
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.
Set up the Discord connection
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.
Set up the Discord Bot connection
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.
Link a Discord user identity
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.
import os
from scalekit import ScalekitClient
scalekit_client = ScalekitClient(
env_url=os.environ["SCALEKIT_ENVIRONMENT_URL"],
client_id=os.environ["SCALEKIT_CLIENT_ID"],
client_secret=os.environ["SCALEKIT_CLIENT_SECRET"],
)
actions = scalekit_client.actions
# Must match the connection name in AgentKit > Connections exactly
DISCORD_USER_CONNECTION = "discord"
user_id = "user_123" # your app's identifier for this user
account = actions.get_or_create_connected_account(
connection_name=DISCORD_USER_CONNECTION,
identifier=user_id,
).connected_account
if account.status != "ACTIVE":
link = actions.get_authorization_link(
connection_name=DISCORD_USER_CONNECTION,
identifier=user_id,
)
print("Send the user here to connect Discord:", link.link)
else:
guilds = actions.execute_tool(
tool_name="discord_list_my_guilds",
tool_input={"limit": 50},
connection_name=DISCORD_USER_CONNECTION,
identifier=user_id,
)
print(guilds.execution_id, guilds.data)
Register a tenant's bot 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.
import os
from scalekit import ScalekitClient
scalekit_client = ScalekitClient(
env_url=os.environ["SCALEKIT_ENVIRONMENT_URL"],
client_id=os.environ["SCALEKIT_CLIENT_ID"],
client_secret=os.environ["SCALEKIT_CLIENT_SECRET"],
)
actions = scalekit_client.actions
# Must match the Discord Bot connection name in AgentKit > Connections exactly
DISCORD_BOT_CONNECTION = "discordbot"
TENANT_ID = "org_acme" # one connected account per tenant bot install
actions.upsert_connected_account(
connection_name=DISCORD_BOT_CONNECTION,
identifier=TENANT_ID,
authorization_details={
"static_auth": {"api_key": os.environ["ACME_DISCORD_BOT_TOKEN"]}
},
)
bot = actions.execute_tool(
tool_name="discordbot_get_current_user",
tool_input={},
connection_name=DISCORD_BOT_CONNECTION,
identifier=TENANT_ID,
)
print(bot.data)
Scope tools and pin the channel with LangChain
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.
import os
from langchain_anthropic import ChatAnthropic
from langchain_core.messages import HumanMessage, SystemMessage, ToolMessage
from scalekit import ScalekitClient
scalekit_client = ScalekitClient(
env_url=os.environ["SCALEKIT_ENVIRONMENT_URL"],
client_id=os.environ["SCALEKIT_CLIENT_ID"],
client_secret=os.environ["SCALEKIT_CLIENT_SECRET"],
)
actions = scalekit_client.actions
DISCORD_BOT_CONNECTION = "discordbot" # must match the dashboard connection name
TENANT_ID = "org_acme"
SUPPORT_CHANNEL_ID = os.environ["ACME_SUPPORT_CHANNEL_ID"] # from your tenant record
# The channel comes from your tenant record, never from the model
@actions.pre_modifier(
tool_names=["discordbot_list_channel_messages", "discordbot_create_message"]
)
def pin_support_channel(tool_input):
tool_input["channel_id"] = SUPPORT_CHANNEL_ID
return tool_input
tools = actions.langchain.get_tools(
identifier=TENANT_ID,
connection_names=[DISCORD_BOT_CONNECTION],
tool_names=["discordbot_list_channel_messages", "discordbot_create_message"],
)
tool_map = {t.name: t for t in tools}
llm = ChatAnthropic(model="claude-sonnet-5").bind_tools(tools)
messages = [
SystemMessage(
f"You answer questions in Discord channel {SUPPORT_CHANNEL_ID}. "
"Reply to the original message using message_reference."
),
HumanMessage("Read the last 50 messages and answer any unanswered questions."),
]
while True:
response = llm.invoke(messages)
messages.append(response)
if not response.tool_calls:
print(response.content)
break
for call in response.tool_calls:
result = tool_map[call["name"]].invoke(call["args"])
messages.append(ToolMessage(content=str(result), tool_call_id=call["id"]))
Resolve the pinned channel per request
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.
Serve Discord over a Virtual MCP server
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.
import os
from scalekit import ScalekitClient
from scalekit.actions.models.mcp_config import McpConfigConnectionToolMapping
scalekit_client = ScalekitClient(
env_url=os.environ["SCALEKIT_ENVIRONMENT_URL"],
client_id=os.environ["SCALEKIT_CLIENT_ID"],
client_secret=os.environ["SCALEKIT_CLIENT_SECRET"],
)
actions = scalekit_client.actions
# Run once per agent role, not once per tenant or per user
config = actions.mcp.create_config(
name="discord-community-support",
connection_tool_mappings=[
McpConfigConnectionToolMapping(
connection_name="discordbot",
tools=[
"discordbot_list_channel_messages",
"discordbot_start_thread_from_message",
"discordbot_create_message",
],
),
McpConfigConnectionToolMapping(
connection_name="linear",
tools=["linear_issue_create"],
),
],
).config
print("SCALEKIT_MCP_CONFIG_ID=", config.id, sep="")
print("SCALEKIT_MCP_SERVER_URL=", config.mcp_server_url, sep="")
Connect Claude through the Virtual MCP server
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.
import os
from datetime import timedelta
import anthropic
from scalekit import ScalekitClient
scalekit_client = ScalekitClient(
env_url=os.environ["SCALEKIT_ENVIRONMENT_URL"],
client_id=os.environ["SCALEKIT_CLIENT_ID"],
client_secret=os.environ["SCALEKIT_CLIENT_SECRET"],
)
actions = scalekit_client.actions
config_id = os.environ["SCALEKIT_MCP_CONFIG_ID"]
mcp_url = os.environ["SCALEKIT_MCP_SERVER_URL"]
tenant = "org_acme"
# 1. Every connection on the server must be active for this tenant
state = actions.mcp.list_mcp_connected_accounts(
config_id=config_id, identifier=tenant, include_auth_link=True
)
inactive = [
a for a in state.connected_accounts
if (a.connected_account_status or "").upper() != "ACTIVE"
]
if inactive:
for a in inactive:
print(f"{a.connection_name} needs auth: {a.authentication_link}")
raise SystemExit(1)
# 2. Mint a short-lived session token bound to this tenant
token = actions.mcp.create_session_token(
mcp_config_id=config_id,
identifier=tenant,
expiry=timedelta(minutes=30),
).token
# 3. Hand the static URL and the per-run token to Claude
client = anthropic.Anthropic()
response = client.beta.messages.create(
model="claude-sonnet-5",
max_tokens=2048,
betas=["mcp-client-2025-11-20"],
mcp_servers=[
{
"type": "url",
"url": mcp_url,
"name": "scalekit",
"authorization_token": token,
}
],
tools=[{"type": "mcp_toolset", "mcp_server_name": "scalekit"}],
messages=[{
"role": "user",
"content": (
f"Support channel: {os.environ['ACME_SUPPORT_CHANNEL_ID']}. "
f"Linear team: {os.environ['ACME_LINEAR_TEAM_ID']}. "
"Find bug reports in the last 50 messages, file each one in Linear, "
"and reply in a thread under the report with the issue link."
),
}],
)
print("".join(b.text for b in response.content if b.type == "text"))
Pair tool scope with tenant scope
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 Virtual MCP setup guide covers server updates and token reminting.
Connect a Mastra agent in TypeScript
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.
import { Agent } from '@mastra/core/agent';
import { MCPClient } from '@mastra/mcp';
import { openai } from '@ai-sdk/openai';
import 'dotenv/config';
// Minted on your backend for one tenant; never shared across tenants
const mcpServerUrl = process.env.SCALEKIT_MCP_SERVER_URL;
const mcpToken = process.env.SCALEKIT_MCP_SESSION_TOKEN;
const supportChannelId = process.env.ACME_SUPPORT_CHANNEL_ID;
if (!mcpServerUrl || !mcpToken || !supportChannelId) {
throw new Error('Set SCALEKIT_MCP_SERVER_URL, SCALEKIT_MCP_SESSION_TOKEN and ACME_SUPPORT_CHANNEL_ID');
}
const mcp = new MCPClient({
servers: {
scalekit: {
url: new URL(mcpServerUrl),
requestInit: { headers: { Authorization: `Bearer ${mcpToken}` } },
},
},
});
try {
// Tools arrive namespaced by server key, e.g. scalekit_discordbot_create_message
const tools = await mcp.listTools();
const agent = new Agent({
id: 'discord-community-support',
name: 'Discord community support',
instructions: `You support the community in Discord channel ${supportChannelId}. File confirmed bugs in Linear.`,
model: openai('gpt-4o'),
tools,
});
const result = await agent.generate('Summarize the unanswered questions from the last 50 messages.');
console.log(result.text);
} finally {
await mcp.disconnect();
}
The Mastra tool calling guide and the Mastra example cover the full setup.
Why the Scalekit path holds up in production
Two properties matter once a Discord agent leaves the prototype: seeing what it did, and limiting what it can reach.
Downstream tool-calling logs
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.
Scoped tool surfaces
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 endpoint for many tenants and 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.
Start from a template
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. All other connectors live in the connector catalog.
Which one to build against
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.
Get help with your Discord agent
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.
Top comments (0)