discord-mcp について
discord-mcpは、エージェントや他のMCPクライアントからDiscord REST APIを利用するための参照実装です。読み取りツール5つは常時利用でき、書き込みツール7つは環境変数による明示的な有効化が必要です。
主な機能
- 01サーバーのチャンネル、ロール、カテゴリの読み取り
- 02チャンネルの権限上書き設定の確認
- 03メンバーのロールIDの取得
- 04チャンネル、ロール、サーバー情報の作成と編集
- 05メッセージ投稿とチャンネル削除
- 06構造化エラーと書き込みゲート
活用シーン
- 01サーバーのチャンネルとロール設定の確認
- 02メンバーに割り当てられたロールの検索
- 03書き込み有効化後のサーバー構成の管理
- 04書き込みツール有効化後のチャンネル投稿
MCP 対応
Discord REST APIの操作をMCPツールとして公開します。読み取りツールは常時有効で、書き込みツールにはDISCORD_MCP_ENABLE_WRITE=1の設定が必要です。
設定
ローカルサーバーの設定はどのクライアントでも共通です。コピーしてクライアントの MCP 設定ファイルに貼り付けてください。
ツール
12掲載時のツール一覧 · 2 週間前に更新
| パラメーター | 型 | 必須 | 説明 |
|---|---|---|---|
| guild_id | string | はい |
詳細
discord-mcp
実際の Discord REST API を使う MCP サーバーです。
そのため、Claude エージェントは認証付きの httpx リクエストを独自に作る代わりに、list_channels(guild_id="...") を呼び出せます。このポートフォリオにおける
github-mcp/bus-mcp
と同じ標準(独自の pyproject、fastmcp サーバー、型付きエラー、実際のテストスイート、誠実な README)に基づいて構築されています。5 つ目の主要プロジェクトであり、Discord を対象とする初のプロジェクトです。
読み取り専用ツール 5 個は常時有効 + 書き込みツール 7 個はデフォルトで無効
で、DISCORD_MCP_ENABLE_WRITE=1 により有効化します。詳しくは下記の「書き込みツール」をご覧ください。
クイックスタート(60 秒)
python -m venv .venv
.venv/Scripts/python.exe -m pip install -e ".[test]"
Claude Desktop/Code の MCP 設定に追加します。
{
"mcpServers": {
"discord-mcp": {
"command": "python",
"args": ["C:/path/to/discord-mcp/run_server.py"],
"env": { "DISCORD_BOT_TOKEN": "your-bot-token-here" }
}
}
}
DISCORD_BOT_TOKEN を設定しなくても、すべてのツール呼び出しはクラッシュせず、Discord 自身の 401 を含む明確な構造化エラーを返します。詳しくは下記の「型付きエラー」をご覧ください。書き込みツールを使用するには、同じ env ブロックに DISCORD_MCP_ENABLE_WRITE=1 も必要です。設定されていない場合は、ローカルで policy_refusal を返して処理を拒否します。詳しくは下記の「書き込みツール」をご覧ください。
これは何か/何ではないか
これは、実際の外部 SaaS API(Discord)を使う MCP サーバーを実演するポートフォリオ用のリファレンス実装です。Discord 公式の MCP サーバーではなく、Discord Inc. との提携もありません。別個の姉妹プロジェクトにあった HttpDiscordClient を出発点としています。これは、そのプロジェクト独自のサーバープロビジョニングツール用に構築され、実際に稼働するギルドを使って検証された、標準ライブラリの urllib による Discord トランスポートです。このリポジトリでは、そのクライアントのリクエスト構築、ヘッダー構成(意図的に詳細な User-Agent を含みます。下記参照)、エラー処理を httpx に手作業で移植し、このフリートの他の MCP サーバーと揃えたうえで、姉妹プロジェクトに依存しない独立したクライアントとして実装しています。discord-mcp は、他方のパッケージをインポートも参照もしていません。
当初は読み取り専用ツール 5 個のみで、書き込み機能は一切ありませんでした。現在は、同じポートフォリオの github-mcp/bus-mcp/desktop-mcp ですでに採用されている *_MCP_ENABLE_WRITE 形式のゲートに倣い、デフォルトで無効の書き込みツール 7 個も提供しています。詳しくは下記の「書き込みツール」をご覧ください。
ツール
読み取り(常時有効、ゲートなし)
| ツール | Discord エンドポイント | 目的 |
|---|---|---|
list_channels | GET /guilds/{guild_id}/channels | ギルド内のすべてのチャンネル(全タイプ) |
list_roles | GET /guilds/{guild_id}/roles | ギルド内のすべてのロール |
list_categories | GET /guilds/{guild_id}/channels (filtered) | カテゴリチャンネルのみ(Discord のタイプ 4)。Discord にはカテゴリ専用のエンドポイントがないため、同じチャンネルのペイロードをクライアント側でフィルタリングします |
get_channel_permission_overwrites | GET /channels/{channel_id} (permission_overwrites field) | 1 つのチャンネルに設定されたロール/メンバーの許可・拒否ビットフィールド |
get_member_roles | GET /guilds/{guild_id}/members/{member_id} (roles field) | 1 人のギルドメンバーに現在割り当てられているロール ID |
書き込み(DISCORD_MCP_ENABLE_WRITE=1 で有効化、デフォルトは無効)
| ツール | Discord エンドポイント | 目的 |
|---|---|---|
create_channel | POST /guilds/{guild_id}/channels | テキスト/ボイス/カテゴリチャンネルを作成 |
edit_channel | PATCH /channels/{channel_id} | 既存のチャンネル名、トピック、親チャンネル、並び順を変更 |
create_role | POST /guilds/{guild_id}/roles | ロールを作成 |
edit_role | PATCH /guilds/{guild_id}/roles/{role_id} | 既存のロールを編集 |
edit_guild | PATCH /guilds/{guild_id} | ギルドの基本情報(名前/アイコン/バナー/説明)を更新 |
delete_channel | DELETE /channels/{channel_id} | 破壊的操作。 チャンネルを削除 |
create_message | POST /channels/{channel_id}/messages | テキストチャンネルにメッセージを投稿(content は空ではなく、2000 文字以内) |
書き込みツール
元の読み取り専用ツール5個に加えて、書き込みツールを7個追加しました。このポートフォリオですでに提供されている、まったく同じ書き込みゲートのパターン(github-mcp の GITHUB_MCP_ENABLE_WRITE、desktop-mcp の DESKTOP_MCP_ENABLE_*、そして最も近い bus-mcp の BUS_MCP_ENABLE_WRITE と gated_write デコレーターを参照テンプレートとしてコピー)を踏襲しています。create_message は残り6個より一晩遅れて追加しました。create_channel/edit_channel/create_role/edit_role/edit_guild/delete_channel のどれも、実際にはチャンネルにコンテンツを投稿できないことが明らかになったためです。client.py にすでに追加されていた create_channel/create_role とまったく同じゲートと post() ヘルパーを再利用しており、新たな HTTP 処理は追加していません。
- デフォルトでは無効です。 書き込みツール群を有効にするには、サーバーの環境で
DISCORD_MCP_ENABLE_WRITE=1(またはtrue/yes/on)を設定します。未設定(またはそれ以外の値)の場合、すべての書き込みツール呼び出しは Discord API を一切呼び出すことなくローカルで拒否され、構造化されたpolicy_refusalエラーを返します。{"ok": false, "error": {"type": "policy_refusal", "message": "Tool group 'write' is disabled. Set DISCORD_MCP_ENABLE_WRITE=1 in the server's environment to enable it.", "group": "write", "tool": "create_channel", "required_env": "DISCORD_MCP_ENABLE_WRITE"}} - MCP ツール層だけでなく、ルート層で適用されます。
@config.gated_writeデコレーターは、server.pyの@mcp.toolラッパーだけに適用されるのではなく、discord_mcp/routes.py内の各書き込み関数を直接ラップします。そのため、fastmcp や実際のトランスポートを起動せずにゲートを単体テストでき、routes.pyへの別の呼び出し経路から迂回することもできません。 - 呼び出しのたびに環境から新たに読み込みます。 インポート時にキャッシュされることはありません。オペレーターはサーバープロセスを再起動せずに書き込みツール群を有効化/無効化でき、テストではテストごとにモンキーパッチを適用できます。
delete_channelに個別の、あるいは低い基準は設けていません。 実際のギルドに対して本当に破壊的で取り消せない操作ですが、他の書き込みツールとまったく同じDISCORD_MCP_ENABLE_WRITE環境変数でゲートされています。test_routes.pyの専用テストで確認済みです(DISCORD_MCP_ENABLE_DELETEのような、もっともらしいものの誤った変数名を設定しても有効にはなりません)。- 読み取りツールと同じ認証経路を使用します。 書き込み呼び出しは、すべての読み取りツールと同じく、
client.pyの_headers()/プールされたhttpx.AsyncClientの構成を再利用します。このリポジトリの認証コード経路は一つだけです。 create_messageは Discord 自身の 400 エラーに頼らず、コンテンツをローカルで検証します。contentは空でない文字列で、最大2000文字(Discord の実際のメッセージ長の上限)である必要があります。スノーフレーク ID のチェックと同じく、「呼び出しを試みることすらしない」方針に従い、リクエストを構築する前にclient.validate_message_contentで検証します。違反した場合は、生の Discord 400 ではなくvalidation_errorを返します。
edit_guild の icon_base64/banner_base64 — 注意事項のある前提
Discord のドキュメントでは、PATCH /guilds/{id} の icon/banner フィールドは「画像データ」文字列と説明されています。つまり、単なる base64 ペイロードではなく、完全なデータ URI(data:image/png;base64,<base64>)です。edit_guild は次のいずれかを受け付けます。
- 完全な
data:...;base64,...URI。変更せずそのまま渡します。 - 生の base64 バイト列。PNG と仮定して、
data:image/png;base64,<value>でラップします。
この PNG という仮定は、実際の Discord の応答で検証されていません。このリポジトリでは、image/png として送信された非 PNG 画像(JPEG、ブースト済みサーバーのアイコンに使うアニメーション GIF など)を Discord が受け付けるのか、拒否するのか、あるいは誤って表示するのかを確認していません。PNG 以外の画像を使う場合は、デフォルトに頼らず、完全な data:image/...;base64,... URI を自分で渡してください。黙って推測するのではなく、ここに注意事項として明記しています。
型付きエラーを返し、予期しないクラッシュは起こしません
各ツールは、成功時には {"ok": true, ...} を、失敗時には {"ok": false, "error": {...}} を返します。未処理の例外やスタックトレースが返されることはありません。
network_error— 接続拒否、タイムアウト、DNS エラー、または不正なリクエスト URL。Discord の API にまったく到達できませんでした。auth_error(401)— Bot トークンがないか、無効です。permission_error(403、Discord 独自の JSON エラーボディを含みます。例:{"message": "Missing Access", "code": 50001})— Bot にこの呼び出しに必要な権限/スコープがないか、ギルドに参加していません。cloudflare_blocked(403、JSON エラーボディなし)— Discord の API ドキュメントでは、説明的なUser-Agentを指定するよう求めています。これを指定しないと、クライアントのデフォルト UA はよく知られた Bot のフィンガープリントとなり、Discord の Cloudflare エッジが、リクエストがルートレベルの権限チェックに到達する前に、本文のない 403 で拒否することがあります。この状態は実際のpermission_errorである 403 と、それ以外では区別できません。Discord の実際の権限拒否レスポンスには必ず JSON ボディが含まれるため、403 に JSON ボディがないことが、この種別の判定基準です。 これは最善努力によるヒューリスティックであり、確実ではありません。プロキシやロードバランサー、または将来の Discord の変更により、別種の 403 のボディが削除された場合も、この種別に分類されます。discord-mcp は、このヒューリスティックの説明にもあるとおり、同じ説明的なUser-Agentを送信します(client.pyの_headers()を参照)。そのため、実際にはこのサーバー自身の呼び出しでこの種別が発生することはほとんどないはずです。「正直な制限事項」も参照してください。not_found(404)— ギルド、チャンネル、ロール、またはメンバーの ID が不正か、存在しません。rate_limited(429)— Discord のレート制限です。Discord 独自の JSON ボディに含まれるretry_after(秒単位)をそのまま解析したretry_after_sを含みます。このサーバーは自動再試行を行いません。制限を構造化エラーとしてすぐに返し、バックオフ/再試行の判断は呼び出し元に委ねます。decode_error— 2xx レスポンスのボディが有効な JSON ではありません(実際の API では発生しないはずですが、不正なプロキシ/モックへの対策です)。discord_api_error— 上記に該当しない、その他の 4xx/5xx。policy_refusal(書き込みツールのみ)— 書き込みツールのグループが無効になっています。上記の「書き込みツール」を参照してください。Discord API の呼び出しは試行されません。invalid_id— リクエストを組み立てる前に、ギルド/チャンネル/ロール/メンバーの ID がスノーフレーク形式の検証に失敗しました(パスインジェクション対策)。validation_error(create_messageのみ)—contentが空か、Discord の実際のメッセージ上限である 2000 文字を超えています。リクエストを組み立てる前に検出されます。
内部では、discord_mcp/client.py が型付きの DiscordUnreachable/DiscordApiError(2xx で JSON 以外が返された場合の DiscordDecodeError サブクラスを含む)例外を発生させます。discord_mcp/routes.py は両方を捕捉し、ツールが返す前に上記の辞書形式へ正規化します。テストでは、すべての HTTP メソッド(GET/POST/PATCH/DELETE)について、各エラー種別の両レイヤーを検証します。
正直な制約事項
cloudflare_blockedとpermission_errorの振り分けは、Discord が文書化または保証しているものではなく、ヒューリスティック(JSON 本文の有無に基づく)です。これは、説明対象として実装された特定の失敗モード(User-Agent が欠落または汎用的な場合のエッジでの拒否)には正確ですが、無関係な原因(途中のプロキシの不具合など)で本文が削除された 403 もcloudflare_blockedに分類されます。get_channel_permission_overwritesとget_member_rolesは、ライブスモークテストでは、実環境で起こり得るすべてのエッジケース(ロールが 0 個のメンバー、オーバーライドが 0 個のチャンネルなど)を検証していません。検証したのは respx でモックしたユニットテストのみです。ライブスモークテストではlist_channels/list_rolesのみを呼び出し、書き込みツールは一切使用しません。- ページネーションはどこにも実装されていません。
list_channels/list_rolesはページネーションのない単一リクエストの Discord エンドポイントです(Discord でもこれらのエンドポイントはページネーションされません)。そのため、対象範囲のツールでは問題ありません。ただし、メンバー一覧のページネーションが必要なほど大きなギルドは対象外です(ここにはlist_membersツールはありません)。 edit_guildにおけるicon_base64/banner_base64の「PNG と仮定する」デフォルト値は、実際の Discord のレスポンスを使って検証されていません。上記の「書き込みツール」を参照してください。- 実際の Discord API に対して書き込みツールを実行したことは一度もありません(設計上、このタスクの制約により実際のネットワーク呼び出しは一切行わないためです。ライブスモークテストも読み取り専用です)。これには
create_messageも含まれます。このリポジトリ独自のビルド/テストプロセスで、実際に稼働中のギルドへメッセージを投稿したことは一度もありません。これは意図的に分離されており、オペレーターが別途実行する操作です。
環境変数
| 変数 | デフォルト | 用途 |
|---|---|---|
DISCORD_BOT_TOKEN | 未設定 | Authorization: Bot <token> として送信されるボットトークンです。ユニットテストスイートでは、読み取り専用ツールはトークンがなくても動作します(完全にモックされています)。実際の API に対しては、トークンがないと Discord 自体から通常の auth_error(401)が返されます。 |
DISCORD_MCP_ENABLE_WRITE | 未設定(OFF) | 1/true/yes/on を設定すると、7 つの書き込みツールが有効になります。上記の「書き込みツール」を参照してください。 |
DISCORD_MCP_TIMEOUT_S | 10.0 | リクエストごとのタイムアウト(秒) |
DISCORD_MCP_LIVE | 未設定 | 1 を設定すると、実ネットワークを使ったスモークテストが実行されます(「テスト」を参照)。 |
DISCORD_MCP_SMOKE_GUILD_ID | 未設定 | ライブスモークテストの対象となるギルド ID です。未設定の場合、テストは正常にスキップされます。 |
DISCORD_MCP_SMOKE_ENV_PATH | 未設定 | ライブスモークテストで使用する DISCORD_BOT_TOKEN を読み込む外部 .env ファイルのパスです。環境変数に設定済みの場合は不要です。 |
DISCORD_API_BASE(https://discord.com/api/v10)は固定定数であり、環境変数では上書きできません。bus-mcp のセルフホスト可能な BUS_MCP_BASE_URL とは異なり、Discord の REST API に実在するベース URL は 1 つだけです。
使用例
Claude のセッションに接続すると、エージェントは次の操作を行えます。
list_channels(guild_id="123456789012345678")
list_roles(guild_id="123456789012345678")
list_categories(guild_id="123456789012345678")
get_channel_permission_overwrites(channel_id="...")
get_member_roles(guild_id="123456789012345678", member_id="...")
DISCORD_MCP_ENABLE_WRITE=1 を設定すると、次の操作も行えます。
create_channel(guild_id="...", name="general", type=0)
edit_channel(channel_id="...", name="renamed", position=3)
create_role(guild_id="...", name="Mod", color=1752220, hoist=True)
edit_role(guild_id="...", role_id="...", name="Senior Mod", permissions="8")
edit_guild(guild_id="...", name="New Server Name")
delete_channel(channel_id="...")
create_message(channel_id="...", content="hello from discord-mcp")
テスト
.venv/Scripts/python.exe -m pytest -q
163 件のテスト:ユニットテスト 162 件(respx でモックし、実ネットワークへのアクセスはゼロ)と、ライブスモークテスト 1 件(以下を参照。条件を満たした場合のみ実行)。12 個すべてのツールの正常系を網羅しています。上記のすべてのエラータイプを、クライアント層(test_client.py)とルート正規化層(test_routes.py)の両方でテストしています。また、test_server.py は list_tools() を調べるだけでなく、routes モジュールを monkeypatch したうえで、各 @mcp.tool 非同期ラッパー関数を実際に asyncio.run() で実行します。そのため、server.py と routes.py の間で引数名が一致しない場合や、キーワード引数が渡されない場合も検出できます。
書き込みツールのテストカバレッジ(test_config.py と test_routes.py)では、7 個の書き込みツールそれぞれについて、ゲートがオフの場合の拒否時に len(respx.calls) == 0 を検証しています(Discord への呼び出しが試行すらされないことを確認)。また、Discord のレスポンスをモックしたゲートがオンの場合の成功パス(リクエスト本文の検証を含む)、ゲートがオンの場合の Discord API エラーのそのままの伝達(権限/見つからない/レート制限/ネットワーク/5xx)もテストしています。特に delete_channel については、ほかの書き込みツールと同じように拒否されることに加え、もっともらしいものの誤った環境変数名(DISCORD_MCP_ENABLE_DELETE など)で誤って有効にならないことも二重に確認しています。
さらに、create_message には、コンテンツ長の境界値を専用にテストするケースがあります(test_client.py/test_routes.py):ちょうど 2000 文字は受け付けられ(respx の呼び出し 1 回)、2001 文字と空文字列はどちらも validation_error としてローカルで拒否され、respx の呼び出しはゼロです。
ライブスモークテスト
tests/test_live_smoke.py::test_live_list_channels_and_roles_against_a_real_guild は、DISCORD_MCP_LIVE=1 と DISCORD_MCP_SMOKE_GUILD_ID の設定が条件です(デフォルトでは未設定のため、実行には両方の設定が必要です)。指定されたギルドに対して、実際の Discord API の list_channels/list_roles を呼び出します。DISCORD_BOT_TOKEN がこのプロセスの環境変数にすでに設定されていればそこから取得し、設定されていない場合は、DISCORD_MCP_SMOKE_ENV_PATH が外部の .env ファイルを指していれば、そのファイルから読み込みます(読み込みは 1 回だけで、値が表示またはログ出力されることはありません)。このリポジトリには独自の .env ファイルはなく、今後も作成されません。実際のトークンは、運用担当者が管理する別の場所にあります。テストは引き続き読み取り専用で、ライブスモークテストの対象となる書き込みツールはありません。
DISCORD_MCP_LIVE=1 DISCORD_MCP_SMOKE_GUILD_ID=<your-guild-id> \
DISCORD_MCP_SMOKE_ENV_PATH=<path-to-.env-with-token> \
.venv/Scripts/python.exe -m pytest tests/test_live_smoke.py -v
執筆時点で、実際のギルドに対してテストが成功することを確認済みです。実際のチャンネル、ロール、カテゴリーが、いずれも空でないリストとして返されました。
インストール/接続
python -m venv .venv
.venv/Scripts/python.exe -m pip install -e ".[test]"
~/.claude.json の mcpServers.discord-mcp に、絶対パスで run_server.py を実行する stdio サーバーとして登録してください(エントリーポイントが独自にディレクトリを sys.path に追加するため、cwd は不要です)。実際の登録では env: {} とし、実際のボットトークンを ~/.claude.json に埋め込まないでください。認証付きの呼び出しを行う場合は、サーバーを実際に起動するプロセスで DISCORD_BOT_TOKEN を設定し、書き込みツール群を有効にする場合は DISCORD_MCP_ENABLE_WRITE=1 を設定してください。実際の登録で書き込みゲートを有効にするのは、意図的に行う別個の運用担当者による操作であり、このリポジトリのデフォルト設定には含まれません。
ハンドシェイクの確認
.venv/Scripts/python.exe scripts/list_tools.py
トランスポートを起動せずに、登録済みの 12 個のツール名を出力します。純粋なイントロスペクションであり、変更後にサーバーが正常に接続されていることを確認するのに役立ちます。
対象外
- ページネーション/
list_members(上記の「正直な制限事項」を参照)。 - レート制限されたリクエスト(429)の再試行。構造化エラーとして返し、呼び出し元に委ねます。
- 本文のない 403 が発生するあらゆる原因を確実に見分けること(上記の
cloudflare_blockedヒューリスティックの正直な制限事項を参照)。 - 上記 7 個のツールを超える書き込み操作(メンバーのロール割り当て/キック/BAN、メッセージの編集/削除、埋め込み/添付ファイル/リアクション、Webhook 管理など)。
create_messageが扱うのはプレーンテキストのcontentのみで、埋め込み/ファイル/リアクションは扱いません。この作者のポートフォリオには、apply_server_structureスタイルのツールを備えた、より広範で構成の異なる書き込み対応プロジェクトがほかにありますが、ここでは対象外です。
商用サポート
Jaimen Bell がメンテナンスしています。本番環境での MCP 連携、カスタムサーバー、エージェントの信頼性向上に関する取り組みについては、 jaimenbell.dev をご覧ください。
独自の MCP サーバーを構築していますか?MCP Starter Kit には、テンプレート、ビルド手順書、そしてこのサーバーをリリースする際に得たパッケージ化の実体験がまとめられています。
mcp-name: io.github.jaimenbell/discord-mcp
コメント
AI アプリケーションが外部のツールやデータソースに統一的な方法で接続するためのオープンなプロトコルです。クライアントはモデルとの対話を担い、MCP サーバーはツール・リソース・プロンプトテンプレートを提供します。標準メッセージでやり取りするため、1 つのサーバーを複数のクライアントで再利用できます。