discord-mcp
by L-Neil
README.md
# discord-mcp
**読み取り専用の Discord MCP サーバー**(Streamable HTTP)。各メンバーのAIエージェント
(Claude Code / Codex CLI / Gemini CLI)からリモート接続して、このDiscordサーバーの会話を
読み込み、要約・レポート化するためのものです。
- **読み取り専用。** ツールは「チャンネル一覧」「メッセージ取得」「画像添付の取得」のみ。
送信・削除系のツールは意図的に作っていません。
- **リモート。** Streamable HTTP トランスポート(stdioではない)。Bearerトークン認証付き。
- **複数人。** 1人1トークンを配布(14個)。一致すれば通す方式。トークンの追加・削除は
環境変数1つを編集するだけ。
---
## まずはここだけ — 変えるのは3つだけ
| 何を | どこで | どうやって |
| --- | --- | --- |
| **イメージ名** | `k8s/kustomization.yaml`(`images:`) | `cd k8s && kustomize edit set image REGISTRY_PLACEHOLDER/discord-mcp=<自分のレジストリ>/discord-mcp:<タグ>` |
| **ホスト名** | `k8s/patches/host-patch.yaml` | `value:` を自分のドメインに編集 |
| **トークン類** | k8s Secret | `kubectl create secret …`(手順4) |
それ以外は動くデフォルトが入っています。
---
## しくみ・構成(要点)
- **Node.js + TypeScript。** `@discordjs/rest`(RESTのみ)+ 公式 MCP TypeScript SDK
(`@modelcontextprotocol/sdk`)+ Express。
- **Discord Gateway への常時接続なし。** 履歴はオンデマンドで Discord REST API から取得。
Bot が Developer Portal で **MESSAGE CONTENT** 特権インテントを有効にしていれば、REST
でも本文が返るため、常時 WebSocket セッションは不要。これによりサーバーはステートレスで
軽く、`replicas: 1` で安定。
- **ステートレスな MCP トランスポート。** リクエストごとに server + transport を生成
(`sessionIdGenerator: undefined`, `enableJsonResponse: true`)。14人がサーバー側の
セッション管理なしで独立して接続できる。
### Discord Bot の前提
1. **Developer Portal → Bot → Privileged Gateway Intents** で **MESSAGE CONTENT INTENT**
を有効化する。
2. 要約したいチャンネルに対し、最低でも **View Channel** + **Read Message History** 権限を
持たせて Bot を招待する。
3. 必要なもの: **Botトークン**、**ギルド(サーバー)ID**、(任意で)チャンネルID。
チャンネルはエージェントが `list_channels` で発見することもできる。
---
## エージェントに見えるツール
| ツール | 入力 | 返り値 |
| --- | --- | --- |
| `list_channels` | `guild_id?`(省略時は設定済みサーバー) | `[{ id, name, type, parent_id, topic }]` |
| `read_messages` | `channel_id`, `limit?`(1〜`MAX_MESSAGE_LIMIT`、既定50), `before?`, `after?` | 新しい順のメッセージ: `{ id, author{id,username,display_name,bot}, timestamp, content, attachments[], reply_to_id, edited_timestamp }`。REST を自動ページング(100件/回)。 |
| `get_attachment_image` | `channel_id`, `message_id`, `attachment_id` | 画像を base64 の MCP image コンテンツで返す。メッセージを再取得して**新しい署名付きURL**を得るため、`read_messages` が返したURLが期限切れでも読める。 |
`read_messages` の画像添付には署名付き `url`(`filename`, `content_type`, `is_image`)が
含まれます。エージェントが確実にバイト列を読む必要があるときは `get_attachment_image` を
使ってください。
---
## デプロイ(イメージをビルドする人=適用する人)
> **イメージは CI が自動ビルド。** `main` への push / `v*` タグで GitHub Actions
> ([`.github/workflows/docker-publish.yml`](./.github/workflows/docker-publish.yml))が
> amd64/arm64 のマルチアーキイメージを `ghcr.io/<owner>/discord-mcp` に push します。
> 自分のレジストリに置きたいときだけ手順2の手動 buildx を使ってください。
>
> **Helm でも入れられます。** kustomize(下記)の代わりに
> [`charts/discord-mcp/`](./charts/discord-mcp/README.md) を `helm install` する方法もあります。
### 1. clone
```bash
git clone <このリポジトリ> discord-mcp
cd discord-mcp
```
### 2. マルチアーキのイメージをビルドして自分のレジストリにpush
OCI Ampere A1 ノードは **arm64**、x86 ノードは **amd64** です。スケジュールされる先がどちらでも
動くよう、`buildx` で両対応イメージを作ります:
```bash
# 初回のみ: マルチプラットフォーム対応のビルダーを用意
docker buildx create --use --name multi || docker buildx use multi
# linux/amd64 と linux/arm64 を1つのマニフェストにまとめてビルド&push
docker buildx build \
--platform linux/amd64,linux/arm64 \
-t <自分のレジストリ>/discord-mcp:<タグ> \
--push .
```
> マルチプラットフォームビルドには `--push` が必須です(ローカルの `--load` はマルチアーキ
> マニフェストを保持できない)。OCIR の場合、レジストリは
> `<region-key>.ocir.io/<tenancy-namespace>/discord-mcp` の形になります。
### 3. kustomize に自分のイメージを指定
```bash
cd k8s
kustomize edit set image REGISTRY_PLACEHOLDER/discord-mcp=<自分のレジストリ>/discord-mcp:<タグ>
cd ..
```
`images:` の項目が書き換わります。Deployment 側は `REGISTRY_PLACEHOLDER/...` のプレース
ホルダのままで、kustomize が値を差し替えます。
### 4. namespace と Secret(Botトークン + 接続トークン)を作成
マニフェストは `discord-mcp-secrets` という名前の Secret からすべて読み込みます。秘密の値は
git に入りません。
まず接続トークンを生成します(下の[トークン](#接続トークン)参照):
```bash
npm install # 初回のみ。生成スクリプトの依存を取得
npm run gen-tokens # tokens-env.txt と tokens-map.csv を出力(git-ignored)
```
次に namespace と Secret を作成します:
```bash
kubectl create namespace discord-mcp
kubectl -n discord-mcp create secret generic discord-mcp-secrets \
--from-literal=DISCORD_BOT_TOKEN='<Botトークン>' \
--from-literal=DISCORD_GUILD_ID='<ギルドID>' \
--from-literal=MCP_AUTH_TOKENS='<tok1,tok2,...,tok14>'
```
> `MCP_AUTH_TOKENS` には `tokens-env.txt` の `MCP_AUTH_TOKENS=` 以降のカンマ区切り値を入れ
> ます。後でメンバーを追加・削除するときはこの Secret を更新して Pod を再起動:
> `kubectl -n discord-mcp rollout restart deploy/discord-mcp`。
#### private レジストリの pull secret(OCIR は通常 private)
pull に認証が必要なレジストリなら、docker-registry secret を作って紐づけます:
```bash
kubectl -n discord-mcp create secret docker-registry ocir-pull-secret \
--docker-server='<region-key>.ocir.io' \
--docker-username='<tenancy-namespace>/<oci-username>' \
--docker-password='<auth-token>' \
--docker-email='<任意のメール>'
```
その後、`k8s/serviceaccount.yaml` か `k8s/deployment.yaml` の `imagePullSecrets` の
コメントを外して参照します(名前 `ocir-pull-secret` はコメント内に記入済み)。
### 5. 適用
```bash
kustomize build k8s | kubectl apply -f -
```
### 6. ホスト名を設定
`k8s/patches/host-patch.yaml` の `value:` を自分のドメイン(例:
`discord-mcp.example.org`)に編集して、手順5を再適用します。ホスト名はこのパッチ1箇所
だけにあります。
**TLS**(どちらか選び、`k8s/ingress.yaml` 内にコメントで示した `tls:` ブロックを追加):
- **cert-manager + Let's Encrypt:** `ingress.yaml` の
`cert-manager.io/cluster-issuer` アノテーションのコメントを外し、`secretName:
discord-mcp-tls` の `tls:` ブロックを追加して再適用。cert-manager が secret を
埋めます。(TLSを有効にする場合は `host-patch.yaml` に
`/spec/tls/0/hosts/0` を置換する op も追加してください。)
- **既存のワイルドカード証明書を持っている場合:** 証明書/鍵から TLS secret を作り、その
名前で `tls:` ブロックを追加:
```bash
kubectl -n discord-mcp create secret tls wildcard-tls \
--cert=fullchain.pem --key=privkey.pem
```
### 7. 接続確認
```bash
# ヘルスチェック(認証不要)
curl https://<自分のホスト>/healthz
# -> {"status":"ok"}
# MCP tools/list(認証必須)。正しいトークンなら JSON-RPC の結果、誤りなら 401
curl -sS https://<自分のホスト>/mcp \
-H 'Authorization: Bearer <配ったトークンのどれか>' \
-H 'Content-Type: application/json' \
-H 'Accept: application/json, text/event-stream' \
-d '{"jsonrpc":"2.0","id":1,"method":"tools/list","params":{}}'
```
正しいトークンならツール一覧が返り、無し/不正なら HTTP 401 が返ります。
---
## 各エージェントからの接続
運用中のインスタンスは **`https://discord-mcp.ryskn.com`** です(`/mcp` が MCP
エンドポイント、`/healthz` は認証不要のヘルスチェック)。`<TOKEN>` を配布された自分の
トークンに置き換えてください。self-host する場合はホスト名を自分のドメインに読み替えます。
```bash
# 疎通確認(認証不要)
curl https://discord-mcp.ryskn.com/healthz
# -> {"status":"ok"}
```
### Claude Code
```bash
claude mcp add --transport http discord https://discord-mcp.ryskn.com/mcp \
--header "Authorization: Bearer <TOKEN>"
```
### Codex CLI
`~/.codex/config.toml`:
```toml
[mcp_servers.discord]
url = "https://discord-mcp.ryskn.com/mcp"
http_headers = { Authorization = "Bearer <TOKEN>" }
```
### Gemini CLI
`~/.gemini/settings.json`:
```json
{
"mcpServers": {
"discord": {
"httpUrl": "https://discord-mcp.ryskn.com/mcp",
"headers": { "Authorization": "Bearer <TOKEN>" }
}
}
}
```
> フラグ名・フィールド名は CLI のバージョンで変わることがあります。ヘッダ指定の書式で弾か
> れる場合は、その CLI の「リモート/HTTP MCP サーバー」の最新ドキュメントを確認してくださ
> い。サーバー側が必要とするのは `POST /mcp` への `Authorization: Bearer <token>` だけです。
---
## 接続トークン
1人1トークンを生成して配ります。
```bash
npm run gen-tokens # 14トークン、名前は person01..person14
npm run gen-tokens -- 20 # 個数を変える
npm run gen-tokens -- alice bob carol # 名前を明示
```
git-ignored の2ファイルが出力されます(秘密として扱い、配り終えたら削除):
- **`tokens-env.txt`** — Secret に貼る用の1行 `MCP_AUTH_TOKENS=tok1,tok2,...`(手順4)。
- **`tokens-map.csv`** — `name,token` の対応表。誰にどれを配ったか管理する用。
**配布の流れ:** 生成 → env行を Secret に貼る → 各自に *その人のトークンだけ* をプライベートな
経路で送る → 失効・ローテーションに備えて `tokens-map.csv` を安全に保管(または削除)。
誰か1人を失効させたいときは、その人のトークンを `MCP_AUTH_TOKENS` から外して Secret を更新し、
`rollout restart` します。
---
## ローカル開発
```bash
npm install
cp .env.example .env # DISCORD_BOT_TOKEN, DISCORD_GUILD_ID, MCP_AUTH_TOKENS を記入
npm run dev # tsx watch、http://localhost:3000
# または
npm run build && npm start
```
## 環境変数
[`.env.example`](./.env.example) を参照。必須: `DISCORD_BOT_TOKEN`,
`DISCORD_GUILD_ID`, `MCP_AUTH_TOKENS`。任意: `PORT`(3000),
`MAX_MESSAGE_LIMIT`(500)。
This server cannot be deployed
Maintenance
ActivityInactive
ResponsivenessNo issues