MCP × Gemini を活用したサーバーレス Slack チャットボット構築を試してみた
はじめに
こんにちは、クラウドエースのジンです。
この記事では、MCP (Model Context Protocol) を活用して、Google の生成 AI である Gemini と連携するサーバーレスな Slack チャットボットを構築する方法を説明します。
1. Slack チャットボットデモ
今回完成したチャットボットはどのようなものになるのでしょうか? 次にデモを使って説明します。
Slack チャンネルでチャットボットにメンションすると、Gemini が応答を生成し、スレッドに返信してくれます。これにより、チーム内での簡単な質問応答やブレインストーミングの壁打ち相手として AI を手軽に活用できます。
2. 技術背景
2.1 MCP とは?
MCP(Model Context Protocol)は、生成AIモデルが外部のツールやサービスと連携するための標準化された通信規約(プロトコル)です。AI アプリケーションと、AI が利用する様々なツール(関数や API など)との間のやり取りを仲介する役割を担います。
MCPの基礎的な概念については、弊社のメンバーが執筆した以下の記事で詳しく解説されていますので、ぜひご参照ください。
参考記事:Model Context Protocol(MCP)とは?生成 AI の可能性を広げる新しい標準
2.2 なぜ MCP を使うのか?
「Slack チャットボットから Gemini を呼び出すだけなら、直接 API を叩けば良いのでは?」と思われるかもしれません。
以前は、類似システムでは Pub/Sub を介して非同期で処理を連携する方法をよく使いました。 Pub/Sub を用いたアーキテクチャでは、リクエストとレスポンスの対応が複雑になりがちで、ツールの管理も分散しやすいという課題がありました。
しかし、今回はあえて MCP を中間に挟みました。MCP を利用することで、以下のようなメリットがあります。
- ロジックの分離: AI モデルを呼び出すロジックをサーバーとして分離できるため、クライアント側(今回は Slack Bot)は、ツールの呼び出し方さえ知っていればよく、サーバー内部の実装を意識する必要がなくなります。
- ツールの集中管理: AI が利用するツールを MCP サーバーで一元管理できます。新しいツールを追加したり修正したりする際も、クライアント側に改修する必要がなく、サーバー側の修正だけで完結します。
- 拡張性: 一度この MCP サーバーを構築すれば、Slack Bot だけでなく、将来他のクライアント(Web アプリ、CLI ツールなど)を追加したい場合、同じ MCP サーバーを再利用できます。
このように、MCP は AI アプリケーションをより疎結合でスケーラブルな設計にするための強力なプロトコルです。
2.3 今回使用する技術スタック
使用技術スタック
技術分類 | 名称・サービス名 | 主な役割・使用ライブラリ |
---|---|---|
AI ツール連携 | MCP (Model Context Protocol) | AI モデルと外部ツールを連携させるためのプロトコル。 ( fastmcp ライブラリを使用) |
生成 AI モデル | Gemini (Google Generative AI) | Google の生成 AI モデル。Slack Bot の応答生成に使用。 ( Vertex AI SDK 経由で利用) |
チャット連携 | Slack API | Slack App(Bot)を開発するためのAPI。 ( slack_sdk ライブラリを使用) |
Web フレームワーク | FastAPI | Slack からのイベント(メンション)を受け取る API サーバーを構築。 |
ホスティング | Cloud Run | 作成したクライアントとサーバーをホスティングするサーバーレス環境。 |
3. システム構成概要
3.1 全体構成図
システム全体のデータの流れは以下のようになります。
3.2 各コンポーネントの役割
コンポーネント | 主な役割 |
---|---|
Slack App | ユーザーからのメンションをトリガーに、MCP Client へ HTTP リクエストを送信します。 |
MCP Client (FastAPI) | Slack からのリクエストを受け取るエンドポイントとして機能します。署名検証後、メッセージ内容を MCP Tool Server へ問い合わせ、応答を Slack に返信します。 |
MCP Tool Server | AI が利用するツール(Gemini 呼び出し関数)を登録・公開するサーバーです。Client からのリクエストに応じてツールを実行し、結果を返します。 |
Cloud Run | MCP Client と MCP Tool Server をホスティングするサーバーレス環境です。自動でスケールするため、コスト効率に優れています。 |
4. 実装ステップ
4.1 MCP Tool Server の構築
まず、Gemini を呼び出すツールを公開するサーバーを構築します。
4.1.1 @mcp.tool による Gemini Tool 定義
fastmcp
ライブラリの @mcp.tool()
デコレータを使うことで、Python 関数を簡単に MCP ツールとして公開できます。
server.py
:
import os
import asyncio
import sys
from fastmcp import FastMCP
import vertexai
from vertexai.generative_models import GenerativeModel
# Vertex AIの初期化処理
try:
gcp_project = os.getenv("GCP_PROJECT")
if not gcp_project:
print("FATAL: GCP_PROJECT environment variable is not set.", file=sys.stderr)
sys.exit(1)
print(f"Initializing Vertex AI for project: {gcp_project}")
vertexai.init(project=gcp_project, location="asia-northeast1")
chat_model = GenerativeModel("gemini-1.5-pro-002")
print("Vertex AI initialized successfully.")
except Exception as e:
print(f"FATAL: Failed to initialize Vertex AI: {e}", file=sys.stderr)
sys.exit(1)
# Gemini Tool定義します。
mcp = FastMCP("Gemini Tool Server")
@mcp.tool()
def generate_reply(prompt: str) -> str:
# プロンプトに対してVertex AIで応答を生成します。
print(f": Check_right: [TOOL CALLED] prompt={prompt}")
try:
chat = chat_model.start_chat()
resp = chat.send_message(prompt)
return resp.text
except Exception as e:
print(f"Error Context Vertex AI: {e}")
return "申し訳ありません、AIの応答生成でエラーが発生しました。"
if __name__ == "__main__":
port = int(os.getenv("PORT", 8080))
print(f"Starting server on port {port}")
asyncio.run(mcp.run_async(
transport="streamable-http", host="0.0.0.0", port=port
))
generate_reply
関数が prompt
を引数に取り、Vertex AI の Gemini モデルを呼び出して結果を返すシンプルなツールです。
4.1.2 Cloud Runへのデプロイ方法
サーバーはコンテナイメージとして Cloud Run にデプロイします。Dockerfile
を作成し、gcloud run deploy
コマンドでデプロイします。デプロイ時には環境変数 GCP_PROJECT
を設定する必要があります。
# Cloud Build でコンテナをビルドし、Artifact Registry にプッシュ
gcloud builds submit --tag asia-northeast1-docker.pkg.dev/$GCP_PROJECT_ID/docker-repo/mcp-tool-server
# Cloud Runにデプロイ
gcloud run deploy mcp-tool-server \
--image asia-northeast1-docker.pkg.dev/$GCP_PROJECT_ID/docker-repo/mcp-tool-server \
--set-env-vars="GCP_PROJECT=$GCP_PROJECT_ID" \
--service-account=$SERVICE_ACCOUNT_EMAIL \
--region asia-northeast1 \
--no-allow-unauthenticated
4.1.3 セキュリティ設定 (全ユーザー許可 or IDトークン)
Cloud Run の Ingress 設定で、誰からのアクセスを許可するかを設定します。 今回は Client からのアクセスのみを想定しているため、本来は ID トークンによる認証をセキュアにするべきです。(詳細は 5.1 で後述)
4.2 MCP Client の構築 (FastAPI)
次に、Slack と MCP Tool Server を仲介するクライアントを構築します。
4.2.1 Slackイベントハンドリング
FastAPI を使い、Slack からの app_mention
イベントを受け取るエンドポイントを作成します。Slack からのリクエストは署名検証を行い、正規のリクエストであることを確認します。AI への問い合わせは時間がかかる可能性があるため、Background Tasks を使い、Slack には即座に 200 OK
を返しつつ、実際の処理はバックグラウンドで行います。
client.py
:
@app.post("/slack/events")
async def slack_events(req: Request, background_tasks: BackgroundTasks):
body = await req.body()
if not verifier.is_valid_request(body, req.headers):
return {"error": "invalid signature"}
data = await req.json()
if "challenge" in data:
return {"challenge": data["challenge"]}
event = data.get("event", {})
# "message" イベントの代わりに "app_mention" イベントを処理する
if event.get("type") == "app_mention":
# Slackからのテキストにはメンション(<@Uxxxxxxx>)が含まれているため、それを取り除く
text = event["text"].split(">", 1)[-1].strip()
channel = event["channel"]
# AI処理をバックグラウンドタスクとして登録
background_tasks.add_task(process_message_and_reply, text, channel)
# SlackにはすぐにOKを返す
return {"ok": True}
4.2.2 fastmcp.Client による Tool 呼び出し
fastmcp.Client
を使い、MCP Tool Server にリクエストを送信します。 MCP_SERVER_URL
(Tool Server の URL) を指定し、call_tool
メソッドでツール名と引数を渡します。
client.py
:
async def process_message_and_reply(text: str, channel: str):
"""AIへの問い合わせとSlackへの返信を行う非同期タスク"""
async with Client(MCP_URL) as mcp:
try:
resp = await mcp.call_tool("generate_reply", {"prompt": text})
reply = resp[0].text
except Exception as e:
print(f"Error Context MCP server: {e}")
reply = "すみません、サーバーとの通信でエラーが発生しました。"
slack.chat_postMessage(channel=channel, text=reply)
4.2.3 Slack への返信
Tool Server からの応答を slack.chat_postMessage
で Slack に投稿します。
4.2.4 Cloud Run へのデプロイ
こちらも Tool Server と同様に Cloud Run にデプロイします。SLACK_BOT_TOKEN
、SLACK_SIGNING_SECRET
、MCP_SERVER_URL
の3つの環境変数を設定する必要があります。
# Cloud Buildでコンテナをビルド
gcloud builds submit --tag asia-northeast1-docker.pkg.dev/$GCP_PROJECT_ID/docker-repo/mcp-client
# Cloud Runにデプロイ
gcloud run deploy mcp-client \
--image asia-northeast1-docker.pkg.dev/$GCP_PROJECT_ID/docker-repo/mcp-client \
--set-env-vars="MCP_SERVER_URL=YOUR_MCP_SERVER_URL" \
--set-secrets="SLACK_BOT_TOKEN=slack-bot-token:latest,SLACK_SIGNING_SECRET=slack-signing-secret:latest" \
--service-account=$SERVICE_ACCOUNT_EMAIL \
--region asia-northeast1 \
--allow-unauthenticated
4.3 Slack App の準備
- App の作成: Slack API の管理サイトで新しいAppを作成します。
-
OAuth スコープの設定: チャットボットに必要な権限 (Scope) を設定します。 今回は
chat:write
(メッセージ投稿)とapp_mentions:read
(メンション読み取り)が必要です。 -
Event Subscription の有効化:
Event Subscriptions
を有効にし、Request URL
にデプロイした MCP Client の URL (/slack/events
エンドポイント)を登録します。URL を登録すると、Slack が疎通確認を行います。
4.4 動作確認
全ての準備が整ったら、Slack App を任意のチャンネルにインストールし、@<chatbot名>
にメンションして質問を投げかけ、Bot から適切な応答が返ってきたら成功です。
5. セキュリティとベストプラクティス
5.1 サービスアカウントと ID トークンの扱い
本番環境では、Cloud Run サービス間の通信をセキュアにする必要があります。
-
サーバーの認証: MCP Tool Server の Cloud Run サービスは、Ingress 設定を「内部」に限定し、認証されたリクエストのみを受け付けるように設定します。
-
クライアントの認証: MCP Client は、Tool Server を呼び出す際に、自身のサービスアカウントに紐づく ID トークンを生成し、
Authorization
ヘッダーに含めてリクエストを送信する必要があります。これにより、意図しない第三者からのアクセスを防ぎます。
5.2 社内用チャットボットとしてのアクセス管理
Slack App の配布設定を調整し、特定の Slack ワークスペースやチャンネル内でのみ利用できるように制限することは、セキュリティ、コスト管理、そして意図しないトラブルを防ぐ上で非常に重要です。
Slack App のインストール先チャンネルを限定して利用範囲を制限します。さらにコード内でチャンネルIDを判定し、許可された場所以外では動作しないように制御することも可能です。
6. まとめ
本記事では、Slack からのメンションをトリガーに、MCP(Model Context Protocol)を介して Gemini を呼び出すサーバーレスチャットボットの構築方法を解説しました。
MCP を採用することで、Slack と通信するクライアントと、AI モデルを呼び出すツールサーバーを分離しました。この疎結合なアーキテクチャにより、ツールの一元管理が可能になり、将来的に他のクライアントへも容易に拡張できる、スケーラブルなシステムを実現しました。Cloud Run を活用することで、これらをコスト効率良く運用できます。
7. 今後改善できるポイント
このチャットボットは非常にシンプルですが、MCP のアーキテクチャを活かして以下のような機能拡張が考えられます。
- Web 情報を検索できる回答を作る: Google 検索など、外部 API を呼び出すツールを Tool Server に追加し、最新情報に基づいた回答を生成できるようにする。
- 社内情報を調べて回答を生成する: RAG (Retrieval-Augmented Generation) の仕組みをツールとして実装し、社内ドキュメントやデータベースを検索して回答を生成できるようにする。
これらの改善も、MCP の利点を活かせば、Tool Server 側の修正のみで実現可能です。 ぜひ、皆さんも独自の Slack チャットボット構築に挑戦してみてください。
Discussion