このガイドでは、Cloudflare Workers 上に AI 搭載の Slack ボットを構築してデプロイします。ボットは次ができます。
- ダイレクトメッセージに応答する
- チャネルでメンションされたときに返信する
- スレッド内の会話コンテキストを維持する
- AI で知的な応答を生成する
Slack エージェントはマルチテナントアプリケーションです。1 回のデプロイで複数の Slack ワークスペースに対応できます。各ワークスペースは、Agents SDK により、専用ストレージを持つ隔離されたエージェントインスタンスを持ちます。
この例の完全なコードは こちら ↗ で確認できます。
始める前に、次が必要です。
- Cloudflare アカウント ↗
- インストール済みの Node.js ↗(v18 以降)
- アプリをインストールする権限がある Slack ワークスペース ↗
- OpenAI API キー ↗(または別の LLM プロバイダー)
まず、エージェントが Slack と連携するための新しい Slack アプリを作成します。
- api.slack.com/apps ↗ に移動し、Create New App を選択します。
- From scratch を選択します。
- アプリ名を付け(例: "My AI Assistant")、ワークスペースを選びます。
- Create App を選択します。
Slack アプリの設定で OAuth & Permissions を開き、次の Bot Token Scopes を追加します。
chat:write— ボットとしてメッセージを送信するchat:write.public— 参加せずにチャネルへメッセージを送信するchannels:history— 公開チャネルのメッセージを表示するapp_mentions:read— メンションを受け取るim:write— ダイレクトメッセージを送信するim:history— ダイレクトメッセージの履歴を表示する
Event Subscriptions の URL は、エージェントのデプロイ後に設定します。いまは Slack アプリ設定の Event Subscriptions を開き、有効化の準備だけします。
次のボットイベントを購読します。
app_mention— ボットが @メンションされたときmessage.im— ボットへのダイレクトメッセージ
まだ有効にしないでください。デプロイ後に有効にします。
Slack アプリ設定から、次の値を集めます。
- Basic Information > App Credentials:
- Client ID
- Client Secret
- Signing Secret
手元に控えておきます。次のステップで使います。
- Slack エージェント用の新しいプロジェクトを作成します。
npm create cloudflare@latest -- my-slack-agentyarn create cloudflare my-slack-agentpnpm create cloudflare@latest my-slack-agent- プロジェクトへ移動します。
cd my-slack-agent- 必要な依存関係をインストールします。
npm install agents openai- ローカル開発用のシークレットとして、プロジェクトルートに
.envファイルを作成します。
touch .env- 認証情報を
.envに追加します。
SLACK_CLIENT_ID="your-slack-client-id"
SLACK_CLIENT_SECRET="your-slack-client-secret"
SLACK_SIGNING_SECRET="your-slack-signing-secret"
OPENAI_API_KEY="your-openai-api-key"
OPENAI_BASE_URL="https://gateway.ai.cloudflare.com/v1/YOUR_ACCOUNT_ID/YOUR_GATEWAY/openai"- エージェントを設定するため、
wrangler.jsoncを更新します。
{
"$schema": "./node_modules/wrangler/config-schema.json",
"name": "my-slack-agent",
"main": "src/index.ts",
// Set this to today's date
"compatibility_date": "2026-09-20",
"compatibility_flags": [
"nodejs_compat"
],
"durable_objects": {
"bindings": [
{
"name": "MyAgent",
"class_name": "MyAgent",
"script_name": "my-slack-agent"
}
]
},
"migrations": [
{
"tag": "v1",
"new_classes": [
"MyAgent"
]
}
]
}"$schema" = "./node_modules/wrangler/config-schema.json"
name = "my-slack-agent"
main = "src/index.ts"
# Set this to today's date
compatibility_date = "2026-09-20"
compatibility_flags = [ "nodejs_compat" ]
[[durable_objects.bindings]]
name = "MyAgent"
class_name = "MyAgent"
script_name = "my-slack-agent"
[[migrations]]
tag = "v1"
new_classes = [ "MyAgent" ]-
まず、ベースの
SlackAgentクラスをsrc/slack.tsに作成します。このクラスは OAuth、リクエスト検証、イベントルーティングを扱います。GitHub 上の完全な実装 ↗ を確認できます。 -
次に、エージェント実装を
src/index.tsに作成します。
import { env } from "cloudflare:workers";
import { SlackAgent } from "./slack";
import { OpenAI } from "openai";
const openai = new OpenAI({
apiKey: env.OPENAI_API_KEY,
baseURL: env.OPENAI_BASE_URL,
});
type SlackMsg = {
user?: string;
text?: string;
ts: string;
thread_ts?: string;
subtype?: string;
bot_id?: string;
};
function normalizeForLLM(msgs: SlackMsg[], selfUserId: string) {
return msgs.map((m) => {
const role = m.user && m.user !== selfUserId ? "user" : "assistant";
const text = (m.text ?? "").replace(/<@([A-Z0-9]+)>/g, "@$1");
return { role, content: text };
});
}
export class MyAgent extends SlackAgent {
async generateAIReply(conversation: SlackMsg[]) {
const selfId = await this.ensureAppUserId();
const messages = normalizeForLLM(conversation, selfId);
const system = `You are a helpful AI assistant in Slack.
Be brief, specific, and actionable. If you're unsure, ask a single clarifying question.`;
const input = [{ role: "system", content: system }, ...messages];
const response = await openai.chat.completions.create({
model: "gpt-4o-mini",
messages: input,
});
const msg = response.choices[0].message.content;
if (!msg) throw new Error("No message from AI");
return msg;
}
async onSlackEvent(event: { type: string } & Record<string, unknown>) {
// Ignore bot messages and subtypes (edits, joins, etc.)
if (event.bot_id || event.subtype) return;
// Handle direct messages
if (event.type === "message") {
const e = event as unknown as SlackMsg & { channel: string };
const isDM = (e.channel || "").startsWith("D");
const mentioned = (e.text || "").includes(
`<@${await this.ensureAppUserId()}>`,
);
if (!isDM && !mentioned) return;
const conversation = await this.fetchConversation(e.channel);
const content = await this.generateAIReply(conversation);
await this.sendMessage(content, { channel: e.channel });
return;
}
// Handle @mentions in channels
if (event.type === "app_mention") {
const e = event as unknown as SlackMsg & {
channel: string;
text?: string;
};
const thread = await this.fetchThread(e.channel, e.thread_ts || e.ts);
const content = await this.generateAIReply(thread);
await this.sendMessage(content, {
channel: e.channel,
thread_ts: e.thread_ts || e.ts,
});
return;
}
}
}
export default MyAgent.listen({
clientId: env.SLACK_CLIENT_ID,
clientSecret: env.SLACK_CLIENT_SECRET,
slackSigningSecret: env.SLACK_SIGNING_SECRET,
scopes: [
"chat:write",
"chat:write.public",
"channels:history",
"app_mentions:read",
"im:write",
"im:history",
],
});開発サーバーを起動します。
npm run devエージェントは http://localhost:8787 で動きます。
エージェントがローカルで動いているので、Slack から到達できるようにします。安全なトンネルを作るには Cloudflare Tunnel を使います。
npx cloudflared tunnel --url http://localhost:8787https://random-subdomain.trycloudflare.com のような公開 URL が出力されます。
Slack アプリ設定に戻ります。
-
Event Subscriptions を開きます。
-
Enable Events を On に切り替えます。
-
Request URL に
https://random-subdomain.trycloudflare.com/slackを入力します。 -
Slack が検証リクエストを送ります。エージェントが正しく動いていれば Verified と表示されます。
-
Subscribe to bot events で次を追加します。
app_mentionmessage.im
-
Save Changes を選択します。
ブラウザーで http://localhost:8787/install を開きます。Slack の認可ページへリダイレクトされます。ワークスペースへアプリをインストールするには Allow を選択します。
認可後、ブラウザーに "Successfully registered!" と表示されます。
Slack を開きます。次を試します。
- ボットへ DM を送ります。AI が生成したメッセージで応答するはずです。
- チャネルでボットをメンションします(例:
@My AI Assistant hello)。スレッドで返信するはずです。
問題なければ、本番へのデプロイ準備は完了です。
- デプロイ前に、シークレットを Cloudflare へ追加します。
npx wrangler secret put SLACK_CLIENT_ID
npx wrangler secret put SLACK_CLIENT_SECRET
npx wrangler secret put SLACK_SIGNING_SECRET
npx wrangler secret put OPENAI_API_KEY
npx wrangler secret put OPENAI_BASE_URL- エージェントをデプロイします。
npx wrangler deployデプロイ後、次のような本番 URL を受け取ります。
https://my-slack-agent.your-account.workers.devSlack アプリ設定に戻ります。
- Event Subscriptions を開きます。
- Request URL を本番 URL に更新します:
https://my-slack-agent.your-account.workers.dev/slack。 - Save Changes を選択します。
エージェントをデプロイしたら、ほかの人とも共有できます。
- 単一ワークスペース:
https://my-slack-agent.your-account.workers.dev/installからインストールします。 - 公開配布: Slack App Directory ↗ にアプリを申請します。
アプリをインストールした各ワークスペースは、専用ストレージを持つ隔離されたエージェントインスタンスを受け取ります。
Slack エージェントは Durable Objects を使い、Slack ワークスペースごとに隔離されたステートフルインスタンスを提供します。
- 各ワークスペースの
team_idを Durable Object ID として使います。 - 各エージェントインスタンスは、自身の Slack アクセストークンを KV ストレージに保存します。
- 会話は、必要になったときに Slack API から取得します。
- エージェントロジックはすべて、隔離された一貫した環境で走ります。
エージェントは Slack の OAuth 2.0 フローを処理します。
- ユーザーが
/installを開く > Slack 認可へリダイレクトされます。 - ユーザーが Allow を選択 > Slack が認可コード付きで
/acceptへリダイレクトします。 - エージェントがコードをアクセストークンに交換します。
- エージェントがトークンを、ワークスペースの Durable Object に保存します。
Slack がイベントを送ると、次のように処理します。
- リクエストが
/slackエンドポイントに到着します。 - エージェントが HMAC-SHA256 でリクエスト署名を検証します。
- エージェントがイベントを、正しいワークスペースの Durable Object へルーティングします。
onSlackEventメソッドがイベントを処理し、応答を生成します。
src/index.ts のモデルを更新します。
const response = await openai.chat.completions.create({
model: "gpt-4o", // or any other model
messages: input,
});会話履歴を Durable Object ストレージに保存します。
async storeMessage(channel: string, message: SlackMsg) {
const history = await this.ctx.storage.kv.get(`history:${channel}`) || [];
history.push(message);
await this.ctx.storage.kv.put(`history:${channel}`, history);
}onSlackEvent にカスタムロジックを追加します。
async onSlackEvent(event: { type: string } & Record<string, unknown>) {
if (event.type === "message") {
const e = event as unknown as SlackMsg & { channel: string };
if (e.text?.includes("help")) {
await this.sendMessage("Here's how I can help...", {
channel: e.channel
});
return;
}
}
// ... rest of your event handling
}OpenAI を Workers AI に置き換えます。
import { Ai } from "@cloudflare/ai";
export class MyAgent extends SlackAgent {
async generateAIReply(conversation: SlackMsg[]) {
const ai = new Ai(this.ctx.env.AI);
const response = await ai.run("@cf/meta/llama-3-8b-instruct", {
messages: normalizeForLLM(conversation, await this.ensureAppUserId()),
});
return response.response;
}
}- Slack Interactive Components ↗(ボタン、モーダル)を追加する
- エージェントを MCP サーバー に接続する
- 不正利用を防ぐレート制限を追加する
- 会話状態の管理を実装する
- Workers Analytics Engine で利用状況を追跡する
- スケジュール済みタスクに schedules を使う