わたしたちは、SlackでClaude Codeと対話できるボットを開発しました。このボットは、Cloudflare Workersのサイズ制限を回避するために分散アーキテクチャを採用し、MCP(Model Context Protocol)を通じてNotion、GitHub、Google Driveといった外部サービスと連携します。
特筆すべきは、GitHub Actionsを使用することで、Claude Code SDKとMCPの実行環境をゼロから構築する必要がないことです。MCPサーバーのプロセス管理、標準入出力の制御、複数ツールの同時実行など、サーバーレス環境では実装が困難な機能を、GitHub Actionsが提供する環境で簡単に実現できます。本記事では、その設計と実装について詳しく解説します。

アーキテクチャ概要
分散処理の仕組み
システムは3つの主要コンポーネントで構成されています:
- Cloudflare Worker: Slackイベントの受信と初期応答
- GitHub Actions: Claude Codeの実行とMCPツール処理
- Slack MCP: GitHub ActionsからSlackへの直接メッセージ投稿
[Slack] → [Cloudflare Worker] → [GitHub Actions + Claude Code SDK]
↑ ↓
└────────────────────────────────────┘
(Slack MCPによる直接更新)
技術スタック
- Cloudflare Worker: Hono + TypeScript + Zod
- GitHub Actions: Claude Code SDK (@anthropic-ai/claude-code)
- ストレージ: Cloudflare KV(スレッドコンテキストキャッシュ)
- MCPサーバー: 公式MCPサーバー(@modelcontextprotocol/server-*)
Cloudflare Worker実装
エントリーポイント
// src/index.ts
import { OpenAPIHono } from "@hono/zod-openapi";
import { cors } from "hono/cors";
import { logger } from "hono/logger";
import { secureHeaders } from "hono/secure-headers";
import { requestId } from "hono/request-id";
import { slackRoutes } from "./routes/slack";
import { healthRoutes } from "./routes/health";
import { docsRoutes } from "./routes/docs";
import type { Env } from "./types/env";
const app = new OpenAPIHono<{ Bindings: Env }>();
// グローバルミドルウェア
app.use("*", requestId());
app.use("*", logger());
app.use("*", secureHeaders());
app.use("*", cors({
origin: (origin) => origin,
credentials: true,
}));
// ルートマウント
app.route("/", slackRoutes);
app.route("/", healthRoutes);
app.route("/", docsRoutes);
Slack署名検証ミドルウェア
セキュリティのため、Slackからのリクエストは署名検証を実装しています:
// src/middleware/auth.ts
export const verifySlackSignature: MiddlewareHandler = async (c, next) => {
const signature = c.req.header("x-slack-signature");
const timestamp = c.req.header("x-slack-request-timestamp");
if (!signature || !timestamp) {
throw createError.slackAuthError("署名またはタイムスタンプがありません");
}
// タイムスタンプの検証(5分以内)
const currentTime = Math.floor(Date.now() / 1000);
if (Math.abs(currentTime - parseInt(timestamp)) > 300) {
throw createError.slackAuthError("リクエストが古すぎます");
}
// HMAC-SHA256による署名検証
const sigBasestring = `v0:${timestamp}:${rawBody}`;
const mySignature = await generateHmacSignature(
sigBasestring,
c.env.SLACK_SIGNING_SECRET,
);
if (!verifySignatures(signature, mySignature)) {
throw createError.slackAuthError("署名の検証に失敗しました");
}
};
GitHub Actions連携
ワークフローディスパッチ
Cloudflare WorkerからGitHub Actionsにタスクを委譲する実装:
// src/services/github-dispatcher.ts
export class GitHubDispatcher {
async dispatch(params: DispatchParams): Promise<void> {
// MCPツールを質問内容に基づいて決定
const mcpTools = this.determineMCPTools(params.question);
// GitHub Actionsワークフローへのペイロード
const payload = {
ref: "main",
inputs: {
question: params.question,
mcp_tools: mcpTools.join(","),
slack_channel: params.slackChannel,
slack_ts: params.slackTs,
slack_thread_ts: params.slackThreadTs || "",
system_prompt: this.generateSystemPrompt(
params.threadContext,
params.customInstructions,
),
},
};
const url = `https://api.github.com/repos/${this.env.GITHUB_OWNER}/${this.env.GITHUB_REPO}/actions/workflows/${this.env.GITHUB_WORKFLOW_FILE}/dispatches`;
const response = await fetch(url, {
method: "POST",
headers: {
Authorization: `Bearer ${this.env.GITHUB_TOKEN}`,
Accept: "application/vnd.github.v3+json",
"Content-Type": "application/json",
},
body: JSON.stringify(payload),
});
}
}
GitHub Actionsワークフロー
GitHub Actions採用の決定的な利点
GitHub Actionsを採用した最大の理由は、MCPの実行環境を自作する必要がないことです。MCPサーバーを動作させるには以下の要件があります:
- プロセス管理: 複数のMCPサーバープロセスの起動と終了
- 標準入出力の制御: 各サーバーとのstdio通信
- 並行処理: 複数のツールの同時実行
- 長時間実行: タイムアウトなしでの処理継続
これらをサーバーレス環境で実装するのは非常に困難ですが、GitHub Actionsの環境では標準的なNode.js環境として簡単に実現できます。
claude-code-base-actionの採用理由
重要な技術的選択として、anthropics/claude-code-action
ではなくanthropics/claude-code-base-action@beta
を使用しています。これは、claude-code-actionがworkflow_dispatchからの呼び出しに対応していないためです。
claude-code-base-actionを使用することで、外部API(今回はCloudflare Worker)からworkflow_dispatchを通じてGitHub Actionsを起動し、Claude Codeを実行できます。これにより、Slackイベントをトリガーとした非同期処理が可能になります。
Claude Codeを実行するワークフロー設定:
# .github/workflows/claude-code-processor.yml
name: Claude Code Processor
on:
workflow_dispatch:
inputs:
question:
description: 'User question from Slack'
required: true
type: string
mcp_tools:
description: 'MCP tools to allow (comma-separated)'
required: false
type: string
slack_channel:
description: 'Slack channel ID'
required: true
type: string
slack_ts:
description: 'Slack message timestamp'
required: true
type: string
slack_thread_ts:
description: 'Slack thread timestamp (if in thread)'
required: false
type: string
system_prompt:
description: 'System prompt for Claude'
required: true
type: string
jobs:
process-with-claude:
runs-on: ubuntu-latest
timeout-minutes: 20
steps:
- name: Checkout repository (sparse)
uses: actions/checkout@v4
with:
sparse-checkout: |
.github
sparse-checkout-cone-mode: false
- name: Prepare MCP configuration # MCPサーバーの設定ファイルを動的生成
run: .github/scripts/prepare-mcp-config.sh
env:
NOTION_API_KEY: ${{ secrets.NOTION_API_KEY }} # Notion APIキー
SLACK_BOT_TOKEN: ${{ secrets.SLACK_BOT_TOKEN }} # Slack Botトークン
GH_TOKEN: ${{ secrets.GH_TOKEN }} # GitHubアクセストークン
- name: Run Claude Code with MCP # Claude Code SDKでMCPツールを使ってAI処理を実行
uses: anthropics/claude-code-base-action@beta
with:
timeout_minutes: 30 # タイムアウト設定(分)
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }} # Anthropic APIキー
mcp_config: "mcp-config.json" # MCPサーバー設定ファイル
system_prompt: ${{ github.event.inputs.system_prompt }} # システムプロンプト
allowed_tools: "${{ github.event.inputs.mcp_tools }},Bash,Grep,Glob,Read,Write,Edit,MultiEdit,LS,Task,TodoRead,TodoWrite,WebSearch,WebFetch" # 使用許可ツール一覧
prompt: |
${{ github.event.inputs.question }}
すべての処理が終わったら、Slack MCPツール(mcp__slack__chat_update)を使って以下のメッセージを更新してください:
- channel: ${{ github.event.inputs.slack_channel }}
- ts: ${{ github.event.inputs.slack_ts }}
MCP設定の動的生成
MCPサーバーの設定を環境変数から動的に生成:
#!/bin/bash
# .github/scripts/prepare-mcp-config.sh
cat > mcp-config.json << EOF
{
"mcpServers": {
"notionApi": { // Notionデータベースとページへのアクセス
"command": "npx",
"args": ["-y", "@notionhq/notion-mcp-server"],
"env": {
"OPENAPI_MCP_HEADERS": "{\"Authorization\": \"Bearer ${NOTION_API_KEY}\", \"Notion-Version\": \"2022-06-28\" }" // Notion APIキーとバージョン指定
}
},
"slack": { // Slackチャンネルへのメッセージ投稿
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-slack"],
"env": {
"SLACK_BOT_TOKEN": "${SLACK_BOT_TOKEN}", // Slack Botトークン
"SLACK_TEAM_ID": "${SLACK_TEAM_ID}" // Slackワークスペースの組織ID
}
},
"github": { // GitHubリポジトリへのアクセス
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-github"],
"env": {
"GITHUB_TOKEN": "${GH_TOKEN}" // GitHub Personal Access Token
}
},
}
}
EOF
スレッドコンテキストの実装
Slackスレッド内の会話履歴を取得し、コンテキストとして提供:
// src/services/slack-client.ts
export class SlackClient {
async getThreadContext(
channel: string,
threadTs: string,
maxMessages: number = 50,
): Promise<ThreadContext> {
const result = await this.client.conversations.replies({
channel,
ts: threadTs,
limit: maxMessages,
});
if (!result.ok || !result.messages) {
throw createError.slackApiError("conversations.replies", result.error);
}
// ユーザー情報を取得してメッセージを整形
const messages = await Promise.all(
result.messages.map(async (msg) => {
const user = await this.getUserInfo(msg.user || "");
return {
text: msg.text || "",
user: user?.real_name || user?.name || "Unknown",
ts: msg.ts || "",
isBot: msg.bot_id !== undefined,
};
}),
);
return { messages };
}
}
非同期処理による高速レスポンス
Slackの3秒タイムアウトに対応するため、即座に応答し、バックグラウンドで処理:
// src/services/event-handler.ts
export class EventHandler {
async handleAppMention(event: AppMentionEvent): Promise<void> {
// 即座に「考えています...」と応答
const initialResponse = await this.slackClient.postMessage({
channel: event.channel,
text: ":thinking_face: 考えています...",
thread_ts: event.thread_ts || event.ts,
});
// GitHub Actionsにディスパッチ(非同期)
await this.githubDispatcher.dispatch({
question,
threadContext,
customInstructions,
slackChannel: event.channel,
slackTs: initialResponse.ts!,
slackThreadTs: event.thread_ts || event.ts,
workspaceUrl: this.env.SLACK_WORKSPACE_URL,
});
}
}
実際の使用例
基本的な使い方
@claude
この議事録の内容を要約して、Slackに投稿して
https://notion.so/xxxxx
ボットは自動的にNotion MCPツールを有効化し、ドキュメントを検索します。
スレッドコンテキスト
スレッド内での会話:
User: 新機能の実装について議論しましょう
User: TypeScriptで型安全にしたいです
@claude この実装にはどのようなパターンが適していますか?
ボットはスレッド全体のコンテキストを理解して応答します。
まとめ
分散アーキテクチャを採用することで、Cloudflare Workersのサイズ制限を回避しながら、高度なAI機能とMCP統合を実現しました。主な成果は以下の通りです:
- 高度な統合: MCP経由でNotion、GitHub、Google Driveと連携
- 開発効率: GitHub Actionsの既存環境を活用し、MCPランタイムの自作を回避
- 拡張性: 新しいMCPツールの追加が容易
- 知識共有: SlackでAIの活用方法が公開され、メンバー同士のノウハウ共有を促進
このアーキテクチャの最大の価値は、GitHub Actionsという既存のインフラを活用することで、複雑なMCP実行環境の構築を避けられたことです。もしこれをゼロから実装しようとすると、プロセス管理、stdio通信、並行処理などの実装に多大な工数がかかり、サーバーレス環境では実現困難でしょう。
GitHub ActionsとCloudflare Workersを組み合わせることで、それぞれの強みを活かした実用的なClaude Code SDKを用いたAIボットを実現できました!