はじめに

毎日の日報作成、正直めんどくさいと思ったことはありませんか?

自分はその日やったことをSlackに箇条書きで投稿しているので、「それをそのまま日報に変換できたら楽じゃないか」と思ったのがきっかけです。

ちゃんとした日報の文章を書かなければというハードルがあると、書き忘れてしまうこともあります。でも箇条書きのメモを渡すだけで整形してもらえるなら、そのハードルはぐっと下がります。

今回はGoogle GenAI SDK(@google/genai)とSlack MCPサーバーを組み合わせて、Slackの投稿を読み取り自動で日報を生成・投稿するエージェントを作りました。

この記事の対象読者

  • Google GenAI SDKやVertex AIを使ってみたい人
  • MCPを使ってAIと外部ツールを連携させてみたい人
  • GCPを業務で使っている、または興味がある人
  • 日報など定型業務の自動化に興味がある人

今回使った技術

SDK(Software Development Kit)とは?

SDKとは、特定のサービスやプラットフォームを利用するためのツール・ライブラリのセットです。たとえば「Slack SDKを使う」とは、SlackのAPIを簡単に呼び出せるライブラリを使うことを意味します。自分でHTTPリクエストを書かなくてもよくなります。

ADK(Agent Development Kit)もSDKの一種ですが、AIエージェント(自律的に判断して動くAI)を作ることに特化したフレームワークです。今回はADKは使わず、Geminiを直接呼び出す @google/genai(Google GenAI SDK)を使いました。ADKを使わなかった理由は記事末尾の補足セクションに記載しています。

MCP(Model Context Protocol)とは?

MCPはAnthropicが提唱したオープンな規格で、AIエージェントが外部のツール(Slack・GitHub・データベースなど)と連携するための共通インターフェースです。

「AIがSlackを操作できるようにする橋渡し役」だと思えばOKです。

ADK(Agent Development Kit)とは?

GoogleがリリースしたAIエージェントを開発するためのフレームワークです。MCPに対応しており、GeminiなどのAIモデルと外部ツールを簡単に組み合わせることができます。

今回の構成

agent.js(Node.js)
  ↓ @google/genai(Gemini API)
    ↓ Vertex AI経由でGeminiを呼び出す
  ↓ @modelcontextprotocol/server-slack(Slack MCP)
    ↓ Slack API
      ↓ チャンネルの読み取り・投稿

完成イメージ

  1. 19時前に自分のSlackチャンネルに「日報」という文字を含む投稿をする
  2. 19時になると自動でエージェントが起動
  3. 「日報」を含む最新の投稿(+スレッド)を読み取る
  4. Geminiが日報テンプレートに整形して投稿する
日報
・〇〇の実装をした
・△△のバグを調査した(原因はAPIのレスポンス形式の変更)
・MTGで〇〇の仕様が決定した
・明日は△△のPRレビュー予定

↓ 自動で以下のような日報が投稿される

お疲れ様です!
本日の日報です。

【今日あったこと】
* 〇〇機能の実装が完了した
* チームMTGにて△△の方針が決定した

【明日の予定】
* △△のPRレビュー予定

【良かった点】
*(自分で記入)

「今日あったこと」と「明日の予定」はSlackの投稿をもとにAIが自動で埋めます。「良かった点」は空欄のまま投稿されるので、自分で記入します。事実ベースの内容まとめだけをAIに任せる形です。


前提条件

  • Node.js がインストールされていること(v18以上推奨)
  • Slack App を作成済みであること
  • Google Cloud ProjectでVertex AI APIが有効化されていること

Vertex AIのセットアップ

Geminiのモデルを使うために、Google Cloud上でVertex AIを有効化する必要があります。

1. Google Cloudプロジェクトの準備

Google Cloud Console にアクセスして、プロジェクトを作成または選択します。

2. Vertex AI APIを有効化

gcloud services enable aiplatform.googleapis.com

または Google Cloud Console の「APIとサービス」→「ライブラリ」から「Vertex AI API」を検索して有効化します。

3. 認証の設定

gcloud auth application-default login

ブラウザが開いてGoogleアカウントでログインします。これによりローカル環境からVertex AIを使えるようになります。

4. プロジェクトIDの確認

gcloud config get-value project

表示されたプロジェクトIDを .envGOOGLE_CLOUD_PROJECT に設定します。

5. GOOGLE_CLOUD_LOCATIONについて

GOOGLE_CLOUD_LOCATION はVertex AIのリージョン(データセンターの場所)を指定する設定です。

説明
global グローバルエンドポイント。特定リージョンに縛られない
us-central1 アメリカ中部
asia-northeast1 東京

今回は global を使用します。ただし、利用するGCPプロジェクトによっては使えるリージョンが異なる場合があります。global で動かない場合は us-central1 などを試してみてください。

セットアップ手順

1. Slack Appの作成

api.slack.com/apps にアクセスして「Create New App」→「From scratch」でアプリを作成します。

Bot Token Scopesに以下を追加します。

スコープ 用途
channels:history パブリックチャンネルの履歴読み取り
channels:read チャンネル情報の取得
chat:write メッセージの投稿
users:read ユーザー情報の取得

「OAuth & Permissions」→「Install to Workspace」でインストールし、Bot User OAuth Token(xoxb-で始まる)をコピーします。

2. Botをチャンネルに追加

日報を投稿したいSlackチャンネルを開いて、以下を実行します。

/invite @your-bot-name

3. フォルダ構成

daily-report-agent/
├── agent.js        # メインスクリプト
├── package.json
└── .env            # 環境変数

4. package.jsonの作成

{
"name": "daily-report-agent",
"version": "1.0.0",
"type": "module",
"dependencies": {
"@google/genai": "^0.7.0",
"@modelcontextprotocol/sdk": "^1.10.0",
"dotenv": "^16.0.0"
}
}

npm install

5. .envの作成

SLACK_BOT_TOKEN=xoxb-your-slack-bot-token
SLACK_TEAM_ID=TXXXXXXXX
MY_CHANNEL_ID=CXXXXXXXX
GOOGLE_CLOUD_PROJECT=your-gcp-project-id
GOOGLE_CLOUD_LOCATION=global

環境変数の取得方法は以下の通りです。

変数名 取得方法
SLACK_BOT_TOKEN Slack App管理画面 → OAuth & Permissions
SLACK_TEAM_ID SlackのURL https://app.slack.com/client/TXXXXXXXX/...` のT〜` 部分
MY_CHANNEL_ID チャンネルを右クリック → チャンネル詳細 → 下部のID(C〜
GOOGLE_CLOUD_PROJECT GCPのプロジェクトID

6. agent.jsの作成

import "dotenv/config";
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
import { GoogleGenAI } from "@google/genai";

const SLACK_BOT_TOKEN = process.env.SLACK_BOT_TOKEN;
const SLACK_TEAM_ID = process.env.SLACK_TEAM_ID;
const MY_CHANNEL_ID = process.env.MY_CHANNEL_ID;
const GOOGLE_CLOUD_PROJECT = process.env.GOOGLE_CLOUD_PROJECT;
const GOOGLE_CLOUD_LOCATION = process.env.GOOGLE_CLOUD_LOCATION || "global";

// 昨日の日付(JST)
const now = new Date();
const jstOffset = 9 * 60 * 60 * 1000;
const jstNow = new Date(now.getTime() + jstOffset);
const yesterday = new Date(jstNow);
yesterday.setDate(yesterday.getDate() - 1);
const REPORT_DATE = `${yesterday.getFullYear()}/${String(yesterday.getMonth() + 1).padStart(2, "0")}/${String(yesterday.getDate()).padStart(2, "0")}`;

async function main() {
  console.log("日報を生成して Slack に投稿中...");

  // MCP Slack サーバーを起動
  const transport = new StdioClientTransport({
    command: "npx",
    args: ["-y", "@modelcontextprotocol/server-slack"],
    env: {
      ...process.env,
      SLACK_BOT_TOKEN,
      SLACK_TEAM_ID,
    },
  });

  const mcpClient = new Client({ name: "daily-report-agent", version: "1.0.0" });
  await mcpClient.connect(transport);

  // Step1: 「日報」を含む最新投稿を取得
  console.log(`[1/3] 「日報」を含む最新投稿を取得中...`);
  let messagesText = "";
  const result = await mcpClient.callTool({
    name: "slack_get_channel_history",
    arguments: { channel_id: MY_CHANNEL_ID, limit: 100 },
  });
  const content = result.content?.[0]?.text || "";

  const parsed = JSON.parse(content);
  const messages = parsed.messages || parsed || [];
  let dailyReportTs = null;

  for (const msg of messages) {
    if (msg.text && msg.text.includes("日報")) {
      messagesText = `【親メッセージ】\n${msg.text}`;
      dailyReportTs = msg.ts;
      break;
    }
  }

  // スレッドの返信も取得
  if (dailyReportTs) {
    const threadResult = await mcpClient.callTool({
      name: "slack_get_thread_replies",
      arguments: { channel_id: MY_CHANNEL_ID, thread_ts: dailyReportTs },
    });
    const threadContent = threadResult.content?.[0]?.text || "";
    if (threadContent) {
      messagesText += `\n\n【スレッドの返信】\n${threadContent}`;
    }
  }

  // Step2: Geminiで日報を生成
  console.log("[2/3] Geminiで日報を生成中...");
  const ai = new GoogleGenAI({
    vertexai: true,
    project: GOOGLE_CLOUD_PROJECT,
    location: GOOGLE_CLOUD_LOCATION,
    httpOptions: { baseUrl: "https://aiplatform.googleapis.com/" },
  });

  const prompt = `以下のSlack投稿を元に日報を作成してください。
振り返り項目は空欄のまま出力し、AIで推測して埋めないこと。

--- 投稿内容 ---
${messagesText}
--- ここまで ---

お疲れ様です!
本日の日報です。

【今日あったこと】
*(投稿内容から読み取った作業の事実を箇条書きで)

【明日の予定】
*(投稿内容から読み取った明日の予定を箇条書きで)

【良かった点】
*

注意:フォーマットのみ出力。前置きや説明は不要。箇条書きは * で始める。`;

  const response = await ai.models.generateContent({
    model: "gemini-2.5-pro",
    contents: prompt,
  });
  const report = response.text;
  console.log(report);

  // Step3: Slackに投稿
  console.log("[3/3] Slackに投稿中...");
  await mcpClient.callTool({
    name: "slack_post_message",
    arguments: { channel_id: MY_CHANNEL_ID, text: report },
  });

  await mcpClient.close();
  console.log("完了しました!");
}

main().catch(console.error);

7. 動作確認

node agent.js

8. 毎日19時に自動実行する設定(macOS)

毎日決まった時間に自動実行するために、macOS標準のジョブスケジューラである launchd を使います。

最初はClaude Codeの /loop 機能(定期実行機能)の利用も検討しましたが、以下の理由でlaunchdを選びました。

  • /loopClaude Codeのセッション内でのみ動く ため、新しいセッションを立ち上げると設定が消える
  • 7日間で自動期限切れ になるため、毎週再設定が必要
  • 「毎日19時」のような 時刻固定の指定ができない(「毎X分ごと」という間隔指定のみ)

launchdを使う理由は以下の通りです。

  • macOS標準機能 のため追加インストール不要
  • Mac起動後は常に有効 で、特定のアプリが起動していなくても動く
  • 毎日19時など時刻を固定 して実行できる
  • ログの出力先を指定 できるため、エラー確認が簡単

~/Library/LaunchAgents/com.yourname.daily-report.plist を作成します。

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
    <key>Label</key>
    <string>com.yourname.daily-report</string>

    <key>ProgramArguments</key>
    <array>
        <string>/path/to/node</string>
        <string>/path/to/daily-report-agent/agent.js</string>
    </array>

    <key>WorkingDirectory</key>
    <string>/path/to/daily-report-agent</string>

    <key>StartCalendarInterval</key>
    <dict>
        <key>Hour</key>
        <integer>19</integer>
        <key>Minute</key>
        <integer>0</integer>
    </dict>

    <key>StandardOutPath</key>
    <string>/path/to/daily-report-agent/launchd.log</string>

    <key>StandardErrorPath</key>
    <string>/path/to/daily-report-agent/launchd.log</string>
</dict>
</plist>

launchdに登録します。

launchctl load ~/Library/LaunchAgents/com.yourname.daily-report.plist

登録を確認します。

launchctl list | grep yourname

使い方

  1. 19時前に日報を投稿したいSlackチャンネルに以下のように書く
日報
・〇〇の実装をした
・△△で詰まった(原因は〇〇だった)
・MTGで△△が決定した
・明日は〇〇の予定
  1. 19時になると自動でエージェントが起動して日報を生成・投稿する

まとめ

  • MCP・SDKという概念を組み合わせることで、AIがSlackを操作するエージェントが作れた。
  • 箇条書きのメモを渡すだけでGeminiが日報テンプレートに整形してくれる。
  • launchdで毎日自動実行することで、完全に手間がなくなった。

日報作成の自動化、ぜひ試してみてください!

参考リンク

補足:ADKを採用しなかった理由

当初はGoogle ADK(Agent Development Kit)を使った実装を検討していました。ADKはGoogleが提供するAIエージェント開発フレームワークで、MCPとの連携も公式にサポートされています。

しかし、企業ネットワーク環境(SSL検査プロキシが導入されている場合)では動作しないことがあります。個人のPCや自宅ネットワークであれば問題なく動作するはずです。

ADKはPythonで動作しており、内部で aiohttp という非同期HTTPライブラリを使っています。このライブラリはSSL証明書の検証を環境変数で無効化することができず、プロキシが挿入する自己署名証明書を検証しようとしてエラーになってしまいます。

SSLCertVerificationError: certificate verify failed: self-signed certificate in certificate chain

Node.jsであれば NODE_TLS_REJECT_UNAUTHORIZED=0 という環境変数一つでSSL検証を無効化できるため、今回は @google/genai(Google GenAI SDK)と @modelcontextprotocol/sdk を使った構成に切り替えました。
ネットワーク環境に制限がない場合はADKを使った実装も可能です。