Slack MCPでチーム分析を自動化する完全ガイド

Slack MCPでチーム分析を自動化する完全ガイド

Model Context Protocol活用でSlackデータから感情分析・エンゲージメント測定・自動インサイト生成を実現。実装から応用まで網羅的に解説します。

概要

企業のコミュニケーションハブとして機能するSlackには、チームの健全性、生産性、文化を理解するための貴重なデータが蓄積されています。しかし、このデータを効果的に分析し、アクションにつなげることは容易ではありません。

Model Context Protocol (MCP) は、Anthropicが開発した新しい標準プロトコルで、AI エージェントが外部システムと統合する方法を統一します。Slack MCPを活用することで、これまで複雑だったSlackデータの分析が劇的に簡素化されます。

この記事で扱う内容:

  • Slack MCPの核心概念とアーキテクチャ
  • 3つの実装オプション (公式TypeScript、Python、Browser Token方式)
  • 8つの核心MCPツールの実践活用法
  • 感情分析、エンゲージメント測定、絵文字パターン分析
  • 実際の企業事例 (Salesforce、カスタマーサポート、リモートチーム)
  • パフォーマンス最適化とセキュリティのベストプラクティス

Slack MCPとは何か?

核心概念

Model Context Protocol (MCP) は、LLMアプリケーションとデータソース間の標準化されたインターフェースを提供します。従来のSlack APIが開発者にRESTエンドポイントを提供するのに対し、Slack MCPはAIエージェントが自然言語でSlackと対話できるようにします。

graph LR
    A[Claude AI] -->|MCP Protocol| B[Slack MCP Server]
    B -->|Slack API| C[Slack Workspace]
    B -->|Data Processing| D[Analysis Tools]
    D -->|Insights| A

    style A fill:#4A90E2
    style B fill:#E94B3C
    style C fill:#611F69
    style D fill:#2EB67D

従来のSlack APIとの主な違い:

側面従来のSlack APISlack MCP
インターフェースREST API標準化プロトコル
対象ユーザー開発者AIエージェント
統合の複雑さ高 (認証、ページネーション、エラー処理)低 (MCPが抽象化)
拡張性個別実装標準ツールセット
AI統合カスタム実装必要ネイティブサポート

主要構成要素

Slack MCPエコシステムは3つの主要要素で構成されます:

  1. MCP Hosts: AIエージェント実行環境 (Claude Code、Claude Desktop、カスタムアプリケーション)
  2. MCP Servers: Slackとの通信を処理する中間層 (公式/コミュニティサーバー)
  3. プロトコル標準: Resources (データソース)、Tools (操作)、Prompts (テンプレート)

インストールと設定

Slack MCPには3つの実装オプションがあり、それぞれ異なる要件とユースケースに対応します。

Option 1: 公式TypeScriptサーバー (推奨)

Anthropic公式のTypeScript実装は、最も安定しており、企業環境に適しています。

インストール手順:

# NPMからインストール
npm install -g @modelcontextprotocol/server-slack

# または特定プロジェクトに追加
npm install @modelcontextprotocol/server-slack

Claude Desktop設定 (claude_desktop_config.json):

{
  "mcpServers": {
    "slack": {
      "command": "npx",
      "args": ["-y", "@modelcontextprotocol/server-slack"],
      "env": {
        "SLACK_BOT_TOKEN": "xoxb-your-bot-token",
        "SLACK_TEAM_ID": "T01234567"
      }
    }
  }
}

環境変数設定:

# .envファイルに追加
SLACK_BOT_TOKEN=xoxb-your-bot-token-here
SLACK_TEAM_ID=T01234567

Option 2: コミュニティPythonサーバー

Python開発者やデータサイエンティストには、コミュニティが提供するPython実装が便利です。

インストール:

# pipを使用
pip install slack-mcp-server

# または詩 (Poetry) を使用
poetry add slack-mcp-server

設定例 (config.yaml):

mcp:
  servers:
    slack:
      command: python
      args:
        - -m
        - slack_mcp_server
      env:
        SLACK_BOT_TOKEN: ${SLACK_BOT_TOKEN}
        SLACK_TEAM_ID: ${SLACK_TEAM_ID}

メリットとデメリット:

メリット:

  • Pythonエコシステムとの統合が容易
  • データ分析ライブラリ (pandas、numpy) との親和性
  • Jupyter Notebookでの実験が可能

デメリット:

  • 公式サポートではない
  • 一部機能が制限される可能性
  • アップデートが遅れる場合がある

Option 3: 高度なサーバー (Browser Token方式)

管理者権限がない環境や、Bot Token取得が困難な場合の代替手段です。

インストール:

npx -y @modelcontextprotocol/create-server slack-unofficial

Browser Token取得方法:

  1. Slack Webアプリにログイン
  2. 開発者ツール (F12) を開く
  3. Application → Cookies → d 値をコピー
  4. 設定ファイルに SLACK_COOKIE_TOKEN として設定

設定例:

{
  "mcpServers": {
    "slack-unofficial": {
      "command": "npx",
      "args": ["-y", "@modelcontextprotocol/server-slack-unofficial"],
      "env": {
        "SLACK_COOKIE_TOKEN": "xoxd-your-cookie-token",
        "SLACK_TEAM_ID": "T01234567"
      }
    }
  }
}

使用シナリオ:

  • 個人的な実験やプロトタイピング
  • 管理者権限のないワークスペース
  • 一時的なデータ分析

⚠️ 注意: Browser Tokenは公式にサポートされておらず、セキュリティリスクがあります。本番環境では使用しないでください。

認証と権限設定

Slack App作成手順:

  1. Slack API Portal にアクセス
  2. “Create New App” → “From scratch” を選択
  3. アプリ名とワークスペースを選択
  4. OAuth & Permissions で必要なスコープを追加
  5. Bot User OAuth Token をコピー

必須OAuthスコープ:

# チャンネル操作
channels:history     # パブリックチャンネルの履歴読取
channels:read        # チャンネル情報取得
groups:history       # プライベートチャンネルの履歴読取
groups:read          # プライベートチャンネル情報取得

# メッセージ操作
chat:write           # メッセージ送信
reactions:read       # リアクション読取
reactions:write      # リアクション追加

# ユーザー情報
users:read           # ユーザー情報取得
users:read.email     # メールアドレス取得 (オプション)

# 検索
search:read          # メッセージ検索

Bot Token vs User Token vs Browser Token:

トークンタイプ用途セキュリティ推奨度
Bot Token自動化、分析⭐⭐⭐⭐⭐
User Tokenユーザー代理操作⭐⭐⭐
Browser Token開発・テスト

利用可能なMCPツール

Slack MCPは8つの核心ツールを提供し、チーム分析の基盤となります。

1. slack_list_channels - チャンネルリスト取得

用途: ワークスペース内のすべてのチャンネルを取得し、分析対象を特定します。

パラメータ例:

// TypeScriptでの使用例
const channels = await mcp.callTool('slack_list_channels', {
  types: 'public_channel,private_channel',
  exclude_archived: true,
  limit: 100
});

レスポンス形式:

{
  "channels": [
    {
      "id": "C01234567",
      "name": "general",
      "is_channel": true,
      "is_private": false,
      "is_archived": false,
      "num_members": 150,
      "topic": {
        "value": "会社全体のアナウンス"
      },
      "purpose": {
        "value": "全社コミュニケーション"
      }
    }
  ]
}

実践活用事例:

  • アクティブなチャンネルの識別
  • チャンネル命名規則の監査
  • 休眠チャンネルのクリーンアップ

2. slack_conversations_history - メッセージ履歴

用途: 特定チャンネルのメッセージ履歴を取得し、時系列分析を実行します。

パラメータ例:

const messages = await mcp.callTool('slack_conversations_history', {
  channel: 'C01234567',
  limit: 100,
  oldest: '1609459200', // Unix timestamp
  latest: '1640995200'
});

実践活用事例:

  • ピーク時間帯の分析
  • トピックトレンドの追跡
  • 問題解決時間の測定

3. slack_post_message - メッセージ送信

用途: 分析結果やアラートをチャンネルに自動投稿します。

パラメータ例:

await mcp.callTool('slack_post_message', {
  channel: 'C01234567',
  text: '週次レポート: 今週のエンゲージメントは前週比15%増加しました',
  blocks: [
    {
      type: 'section',
      text: {
        type: 'mrkdwn',
        text: '*週次コミュニケーションレポート*\n• メッセージ数: 1,234\n• アクティブユーザー: 87\n• 平均応答時間: 2.3時間'
      }
    }
  ]
});

4. slack_reply_to_thread - スレッド返信

用途: 既存のスレッドにコンテキストを保持して返信します。

パラメータ例:

await mcp.callTool('slack_reply_to_thread', {
  channel: 'C01234567',
  thread_ts: '1640995200.123456',
  text: 'この問題は類似のケース3件で平均解決時間が4.5時間でした'
});

5. slack_add_reaction - 絵文字リアクション

用途: メッセージに絵文字を追加し、非言語的フィードバックを提供します。

パラメータ例:

await mcp.callTool('slack_add_reaction', {
  channel: 'C01234567',
  timestamp: '1640995200.123456',
  name: 'white_check_mark'
});

実践活用事例:

  • 自動承認システム
  • タスク完了の可視化
  • チーム士気の測定

6. slack_get_thread_replies - スレッド返信取得

用途: スレッド内のすべての返信を取得し、会話の深さを分析します。

パラメータ例:

const replies = await mcp.callTool('slack_get_thread_replies', {
  channel: 'C01234567',
  ts: '1640995200.123456'
});

// 会話の深さ分析
const threadDepth = replies.messages.length;
const avgResponseTime = calculateAvgResponseTime(replies.messages);

7. slack_list_users - ユーザーリスト

用途: ワークスペースのすべてのユーザー情報を取得します。

レスポンス形式:

{
  "members": [
    {
      "id": "U01234567",
      "name": "tanaka.taro",
      "real_name": "田中太郎",
      "profile": {
        "email": "tanaka@example.com",
        "title": "シニアエンジニア",
        "status_text": "在宅勤務中",
        "status_emoji": ":house:"
      },
      "is_bot": false,
      "is_admin": false
    }
  ]
}

8. slack_search_messages - メッセージ検索

用途: 高度なクエリでメッセージを検索します。

パラメータ例:

const results = await mcp.callTool('slack_search_messages', {
  query: 'エラー OR 障害 in:#engineering after:2025-01-01',
  count: 50,
  sort: 'timestamp',
  sort_dir: 'desc'
});

高度な検索クエリ例:

  • from:@tanaka has::thumbsup: - 田中さんの投稿で👍が付いたもの
  • in:#general during:January - 1月のgeneralチャンネル投稿
  • "デプロイ完了" has:link - リンク付きのデプロイ完了メッセージ

データ分析手法

1. メッセージボリューム分析

メッセージ数の推移を分析することで、チームの活動パターンを理解できます。

実装例 (TypeScript):

interface MessageVolume {
  date: string;
  hour: number;
  count: number;
  channelId: string;
}

async function analyzeMessageVolume(
  channelId: string,
  days: number = 30
): Promise<MessageVolume[]> {
  const now = Date.now() / 1000;
  const oldest = now - (days * 24 * 60 * 60);

  const messages = await mcp.callTool('slack_conversations_history', {
    channel: channelId,
    oldest: oldest.toString(),
    limit: 1000
  });

  const volumeMap = new Map<string, number>();

  for (const msg of messages.messages) {
    const timestamp = parseFloat(msg.ts);
    const date = new Date(timestamp * 1000);
    const dateHour = `${date.toISOString().split('T')[0]}-${date.getHours()}`;

    volumeMap.set(dateHour, (volumeMap.get(dateHour) || 0) + 1);
  }

  return Array.from(volumeMap.entries()).map(([key, count]) => {
    const [date, hour] = key.split('-');
    return { date, hour: parseInt(hour), count, channelId };
  });
}

// 使用例
const volume = await analyzeMessageVolume('C01234567', 30);
const peakHours = volume
  .sort((a, b) => b.count - a.count)
  .slice(0, 5);

console.log('ピーク時間帯:', peakHours);

分析結果の活用:

  • ミーティング時間の最適化
  • サポート人員配置の調整
  • 異常なアクティビティの検出

2. 感情分析

Claude AIを活用して、メッセージの感情を分析し、チーム士気をモニタリングします。

実装例:

interface SentimentScore {
  message: string;
  score: number; // -1 (非常にネガティブ) 〜 1 (非常にポジティブ)
  confidence: number;
  timestamp: string;
}

async function analyzeSentiment(
  messages: any[]
): Promise<SentimentScore[]> {
  const results: SentimentScore[] = [];

  for (const msg of messages) {
    // Claude APIで感情分析
    const sentiment = await claude.analyze({
      prompt: `以下のメッセージの感情を-1から1の範囲で評価してください。
      -1: 非常にネガティブ
      0: 中立
      1: 非常にポジティブ

      メッセージ: "${msg.text}"

      JSON形式で返してください: {"score": 0.5, "confidence": 0.9, "reason": "理由"}`,
      model: 'claude-3-5-sonnet-20241022'
    });

    const parsed = JSON.parse(sentiment.content);
    results.push({
      message: msg.text,
      score: parsed.score,
      confidence: parsed.confidence,
      timestamp: msg.ts
    });
  }

  return results;
}

// チーム士気スコア計算
function calculateTeamMorale(sentiments: SentimentScore[]): number {
  const weightedSum = sentiments.reduce((sum, s) =>
    sum + (s.score * s.confidence), 0
  );
  const totalWeight = sentiments.reduce((sum, s) =>
    sum + s.confidence, 0
  );

  return weightedSum / totalWeight;
}

実践活用:

  • 週次チーム士気レポート
  • ネガティブトレンドの早期検出
  • マネージャーへの自動アラート

3. スレッドと会話分析

スレッドのエンゲージメントを測定し、効果的なコミュニケーションパターンを特定します。

実装例:

interface ThreadMetrics {
  threadId: string;
  replyCount: number;
  participantCount: number;
  avgResponseTimeMinutes: number;
  resolutionStatus: 'resolved' | 'ongoing' | 'abandoned';
  engagementScore: number;
}

async function analyzeThreadEngagement(
  channelId: string,
  threadTs: string
): Promise<ThreadMetrics> {
  const replies = await mcp.callTool('slack_get_thread_replies', {
    channel: channelId,
    ts: threadTs
  });

  const messages = replies.messages;
  const participants = new Set(messages.map(m => m.user));

  // 応答時間計算
  const responseTimes: number[] = [];
  for (let i = 1; i < messages.length; i++) {
    const prevTime = parseFloat(messages[i-1].ts);
    const currTime = parseFloat(messages[i].ts);
    responseTimes.push((currTime - prevTime) / 60); // 分単位
  }

  const avgResponseTime = responseTimes.reduce((a, b) => a + b, 0) / responseTimes.length;

  // 解決状態判定
  const lastMessage = messages[messages.length - 1];
  const hasResolutionReaction = lastMessage.reactions?.some(r =>
    ['white_check_mark', 'heavy_check_mark', 'ok'].includes(r.name)
  );

  const hoursSinceLastReply = (Date.now() / 1000 - parseFloat(lastMessage.ts)) / 3600;

  let resolutionStatus: 'resolved' | 'ongoing' | 'abandoned';
  if (hasResolutionReaction) {
    resolutionStatus = 'resolved';
  } else if (hoursSinceLastReply > 72) {
    resolutionStatus = 'abandoned';
  } else {
    resolutionStatus = 'ongoing';
  }

  // エンゲージメントスコア (0〜100)
  const engagementScore = Math.min(100,
    (messages.length * 10) +
    (participants.size * 5) +
    (hasResolutionReaction ? 20 : 0)
  );

  return {
    threadId: threadTs,
    replyCount: messages.length - 1,
    participantCount: participants.size,
    avgResponseTimeMinutes: avgResponseTime,
    resolutionStatus,
    engagementScore
  };
}

4. 絵文字とリアクションパターン分析

非言語的コミュニケーションパターンを分析し、チーム文化を理解します。

実装例:

interface EmojiAnalytics {
  emoji: string;
  count: number;
  usedBy: string[];
  contexts: string[];
  sentiment: 'positive' | 'neutral' | 'negative';
}

async function analyzeEmojiPatterns(
  channelId: string,
  days: number = 30
): Promise<EmojiAnalytics[]> {
  const messages = await fetchRecentMessages(channelId, days);
  const emojiMap = new Map<string, EmojiAnalytics>();

  for (const msg of messages) {
    if (!msg.reactions) continue;

    for (const reaction of msg.reactions) {
      const emoji = reaction.name;

      if (!emojiMap.has(emoji)) {
        emojiMap.set(emoji, {
          emoji,
          count: 0,
          usedBy: [],
          contexts: [],
          sentiment: classifyEmojiSentiment(emoji)
        });
      }

      const analytics = emojiMap.get(emoji)!;
      analytics.count += reaction.count;
      analytics.usedBy.push(...reaction.users);
      analytics.contexts.push(msg.text.substring(0, 100));
    }
  }

  return Array.from(emojiMap.values())
    .sort((a, b) => b.count - a.count);
}

function classifyEmojiSentiment(emoji: string): 'positive' | 'neutral' | 'negative' {
  const positiveEmojis = ['thumbsup', 'heart', 'tada', 'clap', 'fire', 'star'];
  const negativeEmojis = ['disappointed', 'confused', 'x', 'warning'];

  if (positiveEmojis.includes(emoji)) return 'positive';
  if (negativeEmojis.includes(emoji)) return 'negative';
  return 'neutral';
}

// チーム文化指標計算
function calculateCultureScore(emojiAnalytics: EmojiAnalytics[]): {
  positivity: number;
  engagement: number;
  diversity: number;
} {
  const totalCount = emojiAnalytics.reduce((sum, e) => sum + e.count, 0);
  const positiveCount = emojiAnalytics
    .filter(e => e.sentiment === 'positive')
    .reduce((sum, e) => sum + e.count, 0);

  return {
    positivity: (positiveCount / totalCount) * 100,
    engagement: totalCount / 30, // 1日あたり
    diversity: emojiAnalytics.length
  };
}

5. ユーザーエンゲージメント指標

多次元的にユーザーのエンゲージメントを測定します。

実装例:

interface UserEngagement {
  userId: string;
  userName: string;
  metrics: {
    messageCount: number;
    threadParticipation: number;
    reactionsGiven: number;
    reactionsReceived: number;
    avgResponseTimeMinutes: number;
    channelsActive: number;
    influenceScore: number;
  };
}

async function calculateUserEngagement(
  userId: string,
  days: number = 30
): Promise<UserEngagement> {
  const channels = await mcp.callTool('slack_list_channels', {});
  const user = await mcp.callTool('slack_users_info', { user: userId });

  let totalMessages = 0;
  let totalThreads = 0;
  let reactionsGiven = 0;
  let reactionsReceived = 0;
  let activeChannels = 0;

  for (const channel of channels.channels) {
    const messages = await fetchUserMessages(channel.id, userId, days);

    if (messages.length > 0) activeChannels++;
    totalMessages += messages.length;

    for (const msg of messages) {
      if (msg.thread_ts && msg.thread_ts !== msg.ts) {
        totalThreads++;
      }

      if (msg.reactions) {
        reactionsReceived += msg.reactions.reduce((sum, r) => sum + r.count, 0);
      }
    }
  }

  // 影響力スコア計算 (0〜100)
  const influenceScore = Math.min(100,
    (totalMessages * 0.5) +
    (totalThreads * 1.5) +
    (reactionsReceived * 2) +
    (activeChannels * 5)
  );

  return {
    userId,
    userName: user.user.real_name,
    metrics: {
      messageCount: totalMessages,
      threadParticipation: totalThreads,
      reactionsGiven,
      reactionsReceived,
      avgResponseTimeMinutes: 0, // 別途計算
      channelsActive: activeChannels,
      influenceScore
    }
  };
}

// チーム全体のインフルエンサー識別
async function identifyTopInfluencers(
  limit: number = 10
): Promise<UserEngagement[]> {
  const users = await mcp.callTool('slack_list_users', {});
  const engagements: UserEngagement[] = [];

  for (const user of users.members) {
    if (user.is_bot || user.deleted) continue;
    const engagement = await calculateUserEngagement(user.id);
    engagements.push(engagement);
  }

  return engagements
    .sort((a, b) => b.metrics.influenceScore - a.metrics.influenceScore)
    .slice(0, limit);
}

実践活用事例

事例1: 自動会議要約 (Salesforce)

背景: Salesforceのエンジニアリングチームは、毎日複数のチャンネルで技術的な議論を行っていましたが、情報が分散し、重要な決定事項を追跡することが困難でした。

実装方法:

interface MeetingSummary {
  channel: string;
  date: string;
  participants: string[];
  keyDecisions: string[];
  actionItems: Array<{
    task: string;
    assignee: string;
    dueDate: string;
  }>;
  nextSteps: string[];
}

async function generateMeetingSummary(
  channelId: string,
  startTime: string,
  endTime: string
): Promise<MeetingSummary> {
  // 会議時間中のメッセージ取得
  const messages = await mcp.callTool('slack_conversations_history', {
    channel: channelId,
    oldest: startTime,
    latest: endTime
  });

  // 全メッセージを結合
  const fullConversation = messages.messages
    .map(m => `${m.user}: ${m.text}`)
    .join('\n');

  // Claude AIで要約生成
  const summary = await claude.analyze({
    prompt: `以下のSlack会議ログを分析し、JSON形式でサマリーを作成してください:

会議ログ:
${fullConversation}

以下の形式で返してください:
{
  "keyDecisions": ["決定事項1", "決定事項2"],
  "actionItems": [
    {"task": "タスク", "assignee": "担当者", "dueDate": "期限"}
  ],
  "nextSteps": ["次のステップ1", "次のステップ2"]
}`,
    model: 'claude-3-5-sonnet-20241022'
  });

  const parsed = JSON.parse(summary.content);

  // サマリーをチャンネルに投稿
  await mcp.callTool('slack_post_message', {
    channel: channelId,
    blocks: [
      {
        type: 'header',
        text: { type: 'plain_text', text: '会議サマリー' }
      },
      {
        type: 'section',
        text: {
          type: 'mrkdwn',
          text: `*主要決定事項:*\n${parsed.keyDecisions.map(d => `• ${d}`).join('\n')}`
        }
      },
      {
        type: 'section',
        text: {
          type: 'mrkdwn',
          text: `*アクションアイテム:*\n${parsed.actionItems.map(a =>
            `• ${a.task} (@${a.assignee} - ${a.dueDate})`
          ).join('\n')}`
        }
      }
    ]
  });

  return {
    channel: channelId,
    date: new Date().toISOString(),
    participants: [...new Set(messages.messages.map(m => m.user))],
    ...parsed
  };
}

ビジネスインパクト:

  • 会議後のフォローアップ時間が60%削減
  • アクションアイテムの完了率が35%向上
  • 新メンバーのオンボーディング期間が2週間短縮

事例2: カスタマーサポートインサイト (スタートアップ)

背景: 急成長中のSaaSスタートアップが、Slack Connectを通じて顧客サポートを提供していましたが、問題の分類と優先順位付けが手動で非効率でした。

実装方法:

interface SupportTicket {
  messageId: string;
  customerId: string;
  category: string;
  priority: 'low' | 'medium' | 'high' | 'critical';
  sentiment: number;
  estimatedResolutionTime: number;
  suggestedResponse?: string;
}

async function analyzeSupport Ticket(
  channelId: string,
  messageTs: string
): Promise<SupportTicket> {
  const message = await fetchMessage(channelId, messageTs);

  // Claude AIで分類・優先順位付け
  const analysis = await claude.analyze({
    prompt: `カスタマーサポートメッセージを分析してください:

メッセージ: "${message.text}"

以下の情報をJSON形式で返してください:
{
  "category": "technical|billing|feature_request|bug|other",
  "priority": "low|medium|high|critical",
  "sentiment": -1から1の範囲,
  "estimatedResolutionTimeHours": 数値,
  "suggestedResponse": "推奨する返答"
}`,
    model: 'claude-3-5-sonnet-20241022'
  });

  const parsed = JSON.parse(analysis.content);

  // 高優先度の場合、自動エスカレーション
  if (parsed.priority === 'critical') {
    await mcp.callTool('slack_post_message', {
      channel: process.env.SUPPORT_ESCALATION_CHANNEL!,
      text: `🚨 緊急サポートチケット\n顧客: ${message.user}\nカテゴリ: ${parsed.category}\nメッセージ: ${message.text}`,
      blocks: [
        {
          type: 'section',
          text: {
            type: 'mrkdwn',
            text: `*推奨対応:*\n${parsed.suggestedResponse}`
          }
        },
        {
          type: 'actions',
          elements: [
            {
              type: 'button',
              text: { type: 'plain_text', text: '対応開始' },
              style: 'primary',
              value: messageTs
            }
          ]
        }
      ]
    });
  }

  return {
    messageId: messageTs,
    customerId: message.user,
    category: parsed.category,
    priority: parsed.priority,
    sentiment: parsed.sentiment,
    estimatedResolutionTime: parsed.estimatedResolutionTimeHours,
    suggestedResponse: parsed.suggestedResponse
  };
}

// 応答時間最適化
async function optimizeResponseTime(): Promise<void> {
  const tickets = await fetchPendingTickets();

  // 優先度とSLAに基づいてソート
  const sortedTickets = tickets.sort((a, b) => {
    const priorityWeight = { critical: 4, high: 3, medium: 2, low: 1 };
    return priorityWeight[b.priority] - priorityWeight[a.priority];
  });

  // サポートチームに最適化されたキューを通知
  await mcp.callTool('slack_post_message', {
    channel: process.env.SUPPORT_TEAM_CHANNEL!,
    text: '最適化されたサポートキュー',
    blocks: sortedTickets.slice(0, 10).map(ticket => ({
      type: 'section',
      text: {
        type: 'mrkdwn',
        text: `*${ticket.priority.toUpperCase()}* - ${ticket.category}\n予想解決時間: ${ticket.estimatedResolutionTime}時間`
      }
    }))
  });
}

ROI測定:

  • 平均応答時間: 4.2時間 → 1.8時間 (57%改善)
  • 顧客満足度: 78% → 91% (13ポイント向上)
  • サポートチーム効率: 1人あたり1日15件 → 28件対応可能

事例3: リモートチーム文化モニタリング (グローバル企業)

背景: 1,000人以上のリモート従業員を抱えるグローバル企業が、チームの孤立やバーンアウトのリスクを早期に検出する必要がありました。

実装方法:

interface TeamHealthScore {
  teamId: string;
  teamName: string;
  overallScore: number; // 0〜100
  dimensions: {
    communication: number;
    collaboration: number;
    morale: number;
    engagement: number;
    workload: number;
  };
  risks: Array<{
    type: 'isolation' | 'burnout' | 'low_morale' | 'communication_gap';
    severity: 'low' | 'medium' | 'high';
    affectedUsers: string[];
    recommendation: string;
  }>;
}

async function calculateTeamHealth(
  teamChannelId: string,
  days: number = 30
): Promise<TeamHealthScore> {
  const messages = await fetchRecentMessages(teamChannelId, days);
  const users = await getChannelMembers(teamChannelId);

  // 各次元のスコア計算
  const communicationScore = calculateCommunicationScore(messages, users);
  const collaborationScore = calculateCollaborationScore(messages);
  const moraleScore = await calculateMoraleScore(messages);
  const engagementScore = calculateEngagementScore(messages, users);
  const workloadScore = calculateWorkloadScore(messages);

  const overallScore = (
    communicationScore +
    collaborationScore +
    moraleScore +
    engagementScore +
    workloadScore
  ) / 5;

  // リスク検出
  const risks = [];

  // 孤立リスク検出
  const isolatedUsers = users.filter(user => {
    const userMessages = messages.filter(m => m.user === user.id);
    return userMessages.length < 5; // 30日で5件未満
  });

  if (isolatedUsers.length > 0) {
    risks.push({
      type: 'isolation' as const,
      severity: isolatedUsers.length > users.length * 0.2 ? 'high' : 'medium' as const,
      affectedUsers: isolatedUsers.map(u => u.id),
      recommendation: '1on1ミーティングを設定し、エンゲージメント向上の施策を検討してください'
    });
  }

  // バーンアウトリスク検出 (夜間・週末の過度な活動)
  const burnoutRiskUsers = detectBurnoutRisk(messages, users);
  if (burnoutRiskUsers.length > 0) {
    risks.push({
      type: 'burnout' as const,
      severity: 'high' as const,
      affectedUsers: burnoutRiskUsers,
      recommendation: 'ワークロードの再分配と休暇取得を促進してください'
    });
  }

  return {
    teamId: teamChannelId,
    teamName: await getChannelName(teamChannelId),
    overallScore,
    dimensions: {
      communication: communicationScore,
      collaboration: collaborationScore,
      morale: moraleScore,
      engagement: engagementScore,
      workload: workloadScore
    },
    risks
  };
}

function detectBurnoutRisk(messages: any[], users: any[]): string[] {
  const burnoutUsers: string[] = [];

  for (const user of users) {
    const userMessages = messages.filter(m => m.user === user.id);

    let afterHoursCount = 0;
    let weekendCount = 0;

    for (const msg of userMessages) {
      const timestamp = new Date(parseFloat(msg.ts) * 1000);
      const hour = timestamp.getHours();
      const day = timestamp.getDay();

      // 夜間 (22時〜6時)
      if (hour >= 22 || hour < 6) afterHoursCount++;

      // 週末
      if (day === 0 || day === 6) weekendCount++;
    }

    // 30日間で夜間メッセージ20件以上、または週末メッセージ15件以上
    if (afterHoursCount > 20 || weekendCount > 15) {
      burnoutUsers.push(user.id);
    }
  }

  return burnoutUsers;
}

// 自動介入システム
async function autoIntervention(health: TeamHealthScore): Promise<void> {
  for (const risk of health.risks) {
    if (risk.severity === 'high') {
      // マネージャーに通知
      await mcp.callTool('slack_post_message', {
        channel: process.env.MANAGER_ALERT_CHANNEL!,
        text: `⚠️ チーム健全性アラート: ${health.teamName}`,
        blocks: [
          {
            type: 'section',
            text: {
              type: 'mrkdwn',
              text: `*リスクタイプ:* ${risk.type}\n*深刻度:* ${risk.severity}\n*影響を受けるユーザー:* ${risk.affectedUsers.length}名`
            }
          },
          {
            type: 'section',
            text: {
              type: 'mrkdwn',
              text: `*推奨アクション:*\n${risk.recommendation}`
            }
          }
        ]
      });

      // 該当ユーザーに個別メッセージ (孤立リスクの場合)
      if (risk.type === 'isolation') {
        for (const userId of risk.affectedUsers) {
          await mcp.callTool('slack_post_message', {
            channel: userId,
            text: `こんにちは!最近チャンネルでの活動が少ないようですが、何かサポートできることはありますか?気軽に声をかけてください😊`
          });
        }
      }
    }
  }
}

ビジネスインパクト:

  • 従業員満足度: 72% → 86% (14ポイント向上)
  • 離職率: 18% → 9% (50%削減)
  • 早期介入成功率: 78% (リスク検出から1週間以内の改善)

可能なことと不可能なこと

Slack MCPで可能なこと

リアルタイムデータ取得と分析

  • 過去30日間のメッセージ履歴の即座の取得
  • チャンネル横断的なトレンド分析
  • ユーザーエンゲージメントのリアルタイム測定

高度な自然言語処理

  • Claude AIによる感情分析
  • トピック自動分類
  • 意図認識とコンテキスト理解

自動化とオーケストレーション

  • 定期レポートの自動生成
  • アラートとエスカレーションの自動化
  • クロスチャンネル分析と統合

カスタムインサイト生成

  • チーム固有のKPI計算
  • 業界特化型分析
  • 予測モデリング (トレンド予測、リスク予測)

双方向インタラクション

  • 分析結果のSlackへの自動投稿
  • インタラクティブなダッシュボード
  • スレッドでの詳細情報提供

エンタープライズレベルのスケーラビリティ

  • 大規模ワークスペース対応 (10,000+ ユーザー)
  • 複数チーム・部門の統合分析
  • 長期データの保存と時系列分析

他MCPサーバーとの統合

  • GitHub MCPと連携したDevOps分析
  • Postgres MCPによる永続化
  • Google Analytics MCPとのクロスプラットフォーム分析

Slack MCPで不可能なこと (および代替案)

リアルタイムストリーミング

  • 制約: WebSocket接続やEvent APIのリアルタイムストリームはサポートされていません
  • 代替案: ポーリングベースの疑似リアルタイム実装 (5〜30秒間隔)
  • 回避策: Socket Mode対応の別サーバーと併用

ファイルコンテンツの深い分析

  • 制約: PDFや画像内のテキスト抽出は直接サポートされていません
  • 代替案: Slack APIでファイルURLを取得し、外部OCR/PDFパーサーを使用
  • 回避策: Claude VisionでSlackの画像を分析 (別途実装必要)

メッセージの編集・削除

  • 制約: 既存メッセージの編集・削除機能は現バージョンでは制限されています
  • 代替案: 新しいメッセージで訂正を投稿
  • 回避策: 直接Slack APIを使用 (chat.update、chat.delete)

ワークフロー自動化の作成

  • 制約: Slack Workflow Builderのワークフロー作成はサポートされていません
  • 代替案: MCPツールの組み合わせで同等の機能を実装
  • 回避策: Slack API + Workflow Builderで事前作成

音声・ビデオ通話データの分析

  • 制約: Slack Huddles やClipsのコンテンツは取得できません
  • 代替案: テキストベースの会議要約に焦点を当てる
  • 回避策: 会議後にテキストサマリーを投稿するルールを設定

制約事項とベストプラクティス

Rate Limit管理

Slack APIには厳格なRate Limitがあり、適切な管理が必要です。

主なRate Limit:

メソッドTier 1Tier 2Tier 3Tier 4
conversations.history50/分50/分50/分100/分
users.list50/分100/分100/分100/分
search.messages20/分20/分20/分20/分
chat.postMessage1/秒1/秒1/秒1/秒

バッチ処理戦略:

class RateLimitedSlackClient {
  private requestQueue: Array<() => Promise<any>> = [];
  private processing = false;
  private lastRequestTime = 0;
  private minInterval = 1000; // 1秒

  async callTool(toolName: string, params: any): Promise<any> {
    return new Promise((resolve, reject) => {
      this.requestQueue.push(async () => {
        try {
          // Rate limit遵守
          const now = Date.now();
          const timeSinceLastRequest = now - this.lastRequestTime;
          if (timeSinceLastRequest < this.minInterval) {
            await sleep(this.minInterval - timeSinceLastRequest);
          }

          const result = await mcp.callTool(toolName, params);
          this.lastRequestTime = Date.now();
          resolve(result);
        } catch (error) {
          if (error.message.includes('rate_limited')) {
            // 再試行ロジック
            console.log('Rate limit detected, retrying after delay...');
            await sleep(60000); // 1分待機
            this.requestQueue.unshift(async () => resolve(await this.callTool(toolName, params)));
          } else {
            reject(error);
          }
        }
      });

      if (!this.processing) {
        this.processQueue();
      }
    });
  }

  private async processQueue(): Promise<void> {
    this.processing = true;

    while (this.requestQueue.length > 0) {
      const request = this.requestQueue.shift()!;
      await request();
    }

    this.processing = false;
  }
}

function sleep(ms: number): Promise<void> {
  return new Promise(resolve => setTimeout(resolve, ms));
}

キャッシング実装:

interface CacheEntry<T> {
  data: T;
  timestamp: number;
  ttl: number;
}

class SlackDataCache {
  private cache = new Map<string, CacheEntry<any>>();

  get<T>(key: string): T | null {
    const entry = this.cache.get(key);
    if (!entry) return null;

    const age = Date.now() - entry.timestamp;
    if (age > entry.ttl) {
      this.cache.delete(key);
      return null;
    }

    return entry.data as T;
  }

  set<T>(key: string, data: T, ttlSeconds: number = 300): void {
    this.cache.set(key, {
      data,
      timestamp: Date.now(),
      ttl: ttlSeconds * 1000
    });
  }

  // 使用例
  async getCachedChannels(): Promise<any[]> {
    const cacheKey = 'channels_list';
    const cached = this.get<any[]>(cacheKey);

    if (cached) {
      console.log('Using cached channel list');
      return cached;
    }

    const channels = await mcp.callTool('slack_list_channels', {});
    this.set(cacheKey, channels.channels, 600); // 10分キャッシュ
    return channels.channels;
  }
}

セキュリティ考慮事項

APIキー管理のベストプラクティス:

// ❌ 悪い例: ハードコーディング
const SLACK_TOKEN = 'xoxb-123456789-abcdefghijk';

// ✅ 良い例: 環境変数
const SLACK_TOKEN = process.env.SLACK_BOT_TOKEN;
if (!SLACK_TOKEN) {
  throw new Error('SLACK_BOT_TOKEN environment variable is required');
}

// ✅ さらに良い例: シークレット管理サービス
import { SecretsManager } from 'aws-sdk';

async function getSlackToken(): Promise<string> {
  const secretsManager = new SecretsManager();
  const secret = await secretsManager.getSecretValue({
    SecretId: 'prod/slack/bot-token'
  }).promise();

  return JSON.parse(secret.SecretString!).token;
}

最小権限原則:

// チャンネル分析用の最小スコープ
const REQUIRED_SCOPES = [
  'channels:history',  // パブリックチャンネル履歴のみ
  'channels:read',     // チャンネル情報のみ
  'users:read'         // ユーザー情報のみ
];

// ❌ 過剰な権限
const EXCESSIVE_SCOPES = [
  'channels:write',    // 不要: 読取専用分析には不要
  'users:write',       // 不要: ユーザー情報の変更は不要
  'admin'              // 危険: 管理者権限は絶対に避ける
];

PII (個人識別情報) データ保護:

interface SafeUserData {
  userId: string;          // ✅ OK: 内部ID
  userName: string;        // ✅ OK: 表示名
  // email: string;        // ❌ 除外: メールアドレス
  // phoneNumber: string;  // ❌ 除外: 電話番号
  messageCount: number;    // ✅ OK: 集計データ
}

function sanitizeUserData(user: any): SafeUserData {
  return {
    userId: user.id,
    userName: user.name,
    messageCount: 0  // 後で計算
  };
}

// データ保存時の匿名化
function anonymizeForStorage(data: any): any {
  return {
    ...data,
    userId: hashUserId(data.userId),  // ハッシュ化
    messageText: redactPII(data.messageText)  // PII除去
  };
}

パフォーマンス最適化

ページネーション戦略:

async function fetchAllMessages(
  channelId: string,
  days: number = 30
): Promise<any[]> {
  const allMessages: any[] = [];
  let cursor: string | undefined;
  const oldest = (Date.now() / 1000) - (days * 24 * 60 * 60);

  do {
    const response = await mcp.callTool('slack_conversations_history', {
      channel: channelId,
      oldest: oldest.toString(),
      limit: 200,  // 最大値
      cursor
    });

    allMessages.push(...response.messages);
    cursor = response.response_metadata?.next_cursor;

    // Rate limit回避のための遅延
    if (cursor) {
      await sleep(1000);
    }
  } while (cursor);

  return allMessages;
}

並列処理:

async function analyzeMultipleChannels(
  channelIds: string[]
): Promise<Map<string, any>> {
  const results = new Map<string, any>();

  // チャンク処理 (同時5チャンネルまで)
  const chunkSize = 5;
  for (let i = 0; i < channelIds.length; i += chunkSize) {
    const chunk = channelIds.slice(i, i + chunkSize);

    const chunkResults = await Promise.all(
      chunk.map(async channelId => {
        try {
          const analysis = await analyzeChannel(channelId);
          return { channelId, analysis };
        } catch (error) {
          console.error(`Error analyzing ${channelId}:`, error);
          return { channelId, analysis: null };
        }
      })
    );

    for (const { channelId, analysis } of chunkResults) {
      if (analysis) {
        results.set(channelId, analysis);
      }
    }

    // チャンク間の遅延
    if (i + chunkSize < channelIds.length) {
      await sleep(2000);
    }
  }

  return results;
}

スマートデータフェッチング:

// 増分更新戦略
interface ChannelState {
  channelId: string;
  lastFetchTimestamp: number;
  latestMessageTs: string;
}

class IncrementalFetcher {
  private states = new Map<string, ChannelState>();

  async fetchNewMessages(channelId: string): Promise<any[]> {
    const state = this.states.get(channelId);

    const params: any = {
      channel: channelId,
      limit: 100
    };

    // 前回取得以降のメッセージのみ取得
    if (state) {
      params.oldest = state.latestMessageTs;
    }

    const response = await mcp.callTool('slack_conversations_history', params);

    if (response.messages.length > 0) {
      this.states.set(channelId, {
        channelId,
        lastFetchTimestamp: Date.now(),
        latestMessageTs: response.messages[0].ts
      });
    }

    return response.messages;
  }
}

プライバシー保護とコンプライアンス

GDPR準拠:

interface GDPRCompliantAnalytics {
  // 個人データの最小化
  aggregateOnly: boolean;

  // データ保持期間
  retentionDays: number;

  // ユーザー同意管理
  consentRequired: boolean;
  consentedUsers: Set<string>;
}

class GDPRCompliantSlackAnalyzer {
  private config: GDPRCompliantAnalytics;

  constructor(config: GDPRCompliantAnalytics) {
    this.config = config;
  }

  async analyzeWithConsent(channelId: string): Promise<any> {
    const messages = await fetchAllMessages(channelId, this.config.retentionDays);

    // 同意済みユーザーのメッセージのみ分析
    const consentedMessages = messages.filter(msg =>
      this.config.consentedUsers.has(msg.user)
    );

    // 集計データのみ返す (個別メッセージは返さない)
    if (this.config.aggregateOnly) {
      return {
        totalMessages: consentedMessages.length,
        uniqueUsers: new Set(consentedMessages.map(m => m.user)).size,
        avgMessageLength: consentedMessages.reduce((sum, m) =>
          sum + m.text.length, 0) / consentedMessages.length
      };
    }

    return consentedMessages;
  }

  // データ削除要求対応
  async handleDeletionRequest(userId: string): Promise<void> {
    // ローカルキャッシュから削除
    this.clearUserData(userId);

    // 分析結果から除外
    this.config.consentedUsers.delete(userId);

    console.log(`User ${userId} data deleted as per GDPR request`);
  }
}

監査ログ:

interface AuditLogEntry {
  timestamp: string;
  action: string;
  userId: string;
  channelId?: string;
  dataAccessed: string[];
  purpose: string;
}

class AuditLogger {
  private logs: AuditLogEntry[] = [];

  log(entry: Omit<AuditLogEntry, 'timestamp'>): void {
    this.logs.push({
      ...entry,
      timestamp: new Date().toISOString()
    });
  }

  async exportLogs(format: 'json' | 'csv'): Promise<string> {
    if (format === 'json') {
      return JSON.stringify(this.logs, null, 2);
    }

    // CSV形式でエクスポート
    const headers = Object.keys(this.logs[0]).join(',');
    const rows = this.logs.map(log =>
      Object.values(log).map(v => `"${v}"`).join(',')
    );

    return [headers, ...rows].join('\n');
  }
}

// 使用例
const auditLogger = new AuditLogger();

async function analyzeWithAudit(channelId: string, userId: string): Promise<any> {
  auditLogger.log({
    action: 'analyze_channel',
    userId,
    channelId,
    dataAccessed: ['messages', 'reactions', 'users'],
    purpose: 'Team engagement analysis'
  });

  return await analyzeChannel(channelId);
}

ワークスペース透明性:

# Slack分析ツール使用ポリシー

## データ収集範囲
- パブリックチャンネルのメッセージ履歴 (過去30日間)
- ユーザーのエンゲージメント指標 (集計データのみ)
- 絵文字リアクションパターン

## 除外データ
- プライベートメッセージ (DM)
- 個人メールアドレス
- 機密チャンネル (#confidential-*, #legal-*)

## 用途
- チームコミュニケーション改善
- リモートワーク文化モニタリング
- 業務効率化の洞察

## データ保持
- 生データ: 30日後自動削除
- 集計レポート: 90日間保持

## ユーザー権利
- いつでもオプトアウト可能
- データ削除要求対応 (72時間以内)

高度な機能

複数MCP統合

複数のMCPサーバーを組み合わせることで、より強力な分析が可能になります。

Slack + GitHub + Postgres統合例:

interface DevOpsInsight {
  slackData: {
    engineeringChannelActivity: number;
    incidentDiscussions: number;
  };
  githubData: {
    commits: number;
    pullRequests: number;
    deployments: number;
  };
  correlations: {
    commitsToSlackActivity: number;
    deploymentsToIncidents: number;
  };
}

async function generateDevOpsInsights(
  days: number = 7
): Promise<DevOpsInsight> {
  // Slack データ取得
  const slackMessages = await mcp.callTool('slack_conversations_history', {
    channel: process.env.ENGINEERING_CHANNEL!,
    oldest: ((Date.now() / 1000) - (days * 24 * 60 * 60)).toString()
  });

  const incidentMessages = slackMessages.messages.filter(m =>
    m.text.toLowerCase().includes('incident') ||
    m.text.toLowerCase().includes('outage')
  );

  // GitHub データ取得 (GitHub MCP使用)
  const githubCommits = await mcp.callTool('github_list_commits', {
    repo: 'myorg/myrepo',
    since: new Date(Date.now() - (days * 24 * 60 * 60 * 1000)).toISOString()
  });

  const githubPRs = await mcp.callTool('github_list_pull_requests', {
    repo: 'myorg/myrepo',
    state: 'closed',
    since: new Date(Date.now() - (days * 24 * 60 * 60 * 1000)).toISOString()
  });

  // Postgres に保存 (Postgres MCP使用)
  await mcp.callTool('postgres_execute', {
    query: `
      INSERT INTO devops_metrics (date, slack_activity, github_commits, incidents)
      VALUES ($1, $2, $3, $4)
    `,
    params: [
      new Date().toISOString(),
      slackMessages.messages.length,
      githubCommits.length,
      incidentMessages.length
    ]
  });

  // 相関分析
  const commitsToSlackCorrelation = calculateCorrelation(
    githubCommits.length,
    slackMessages.messages.length
  );

  return {
    slackData: {
      engineeringChannelActivity: slackMessages.messages.length,
      incidentDiscussions: incidentMessages.length
    },
    githubData: {
      commits: githubCommits.length,
      pullRequests: githubPRs.length,
      deployments: 0 // 別途取得
    },
    correlations: {
      commitsToSlackActivity: commitsToSlackCorrelation,
      deploymentsToIncidents: 0 // 計算
    }
  };
}

自動化ワークフロー:

// 毎週月曜日9時に実行
cron.schedule('0 9 * * 1', async () => {
  console.log('Generating weekly DevOps report...');

  const insights = await generateDevOpsInsights(7);

  // Slack に投稿
  await mcp.callTool('slack_post_message', {
    channel: process.env.DEVOPS_CHANNEL!,
    blocks: [
      {
        type: 'header',
        text: { type: 'plain_text', text: '週次DevOpsレポート' }
      },
      {
        type: 'section',
        fields: [
          {
            type: 'mrkdwn',
            text: `*Slack活動:*\n${insights.slackData.engineeringChannelActivity}件`
          },
          {
            type: 'mrkdwn',
            text: `*GitHub Commits:*\n${insights.githubData.commits}件`
          },
          {
            type: 'mrkdwn',
            text: `*インシデント:*\n${insights.slackData.incidentDiscussions}件`
          },
          {
            type: 'mrkdwn',
            text: `*Pull Requests:*\n${insights.githubData.pullRequests}件`
          }
        ]
      },
      {
        type: 'section',
        text: {
          type: 'mrkdwn',
          text: `*相関分析:*\nCommits ↔ Slack活動: ${(insights.correlations.commitsToSlackActivity * 100).toFixed(1)}%`
        }
      }
    ]
  });
});

リアルタイムDevOps通知:

// GitHub デプロイ検出 → Slack通知
async function monitorDeployments(): Promise<void> {
  const latestDeployment = await mcp.callTool('github_get_latest_deployment', {
    repo: 'myorg/myrepo'
  });

  if (latestDeployment.state === 'success') {
    // デプロイ成功を Slack に通知
    await mcp.callTool('slack_post_message', {
      channel: process.env.ENGINEERING_CHANNEL!,
      text: `✅ デプロイ成功: ${latestDeployment.environment}`,
      blocks: [
        {
          type: 'section',
          text: {
            type: 'mrkdwn',
            text: `*デプロイ完了*\n環境: ${latestDeployment.environment}\nコミット: ${latestDeployment.sha.substring(0, 7)}\nデプロイ者: ${latestDeployment.creator}`
          }
        },
        {
          type: 'actions',
          elements: [
            {
              type: 'button',
              text: { type: 'plain_text', text: 'GitHub で確認' },
              url: latestDeployment.url
            }
          ]
        }
      ]
    });

    // リアクション追加でモニタリング開始
    await mcp.callTool('slack_add_reaction', {
      channel: process.env.ENGINEERING_CHANNEL!,
      timestamp: latestDeployment.slackMessageTs,
      name: 'eyes'
    });
  } else if (latestDeployment.state === 'failure') {
    // デプロイ失敗アラート
    await mcp.callTool('slack_post_message', {
      channel: process.env.INCIDENT_CHANNEL!,
      text: `🚨 デプロイ失敗: ${latestDeployment.environment}`,
      blocks: [
        {
          type: 'section',
          text: {
            type: 'mrkdwn',
            text: `*デプロイ失敗 - 即座の対応が必要*\n環境: ${latestDeployment.environment}\nエラー: ${latestDeployment.error}`
          }
        }
      ]
    });
  }
}

AI駆動インサイト生成

週次レポート自動化:

interface WeeklyReport {
  period: { start: string; end: string };
  summary: string;
  keyMetrics: {
    totalMessages: number;
    activeUsers: number;
    topChannels: Array<{ name: string; activity: number }>;
    sentimentTrend: number;
  };
  highlights: string[];
  concerns: string[];
  recommendations: string[];
}

async function generateWeeklyReport(): Promise<WeeklyReport> {
  const startDate = new Date();
  startDate.setDate(startDate.getDate() - 7);
  const endDate = new Date();

  // 全チャンネルのデータ収集
  const channels = await mcp.callTool('slack_list_channels', {});
  const allMessages: any[] = [];
  const channelActivity = new Map<string, number>();

  for (const channel of channels.channels) {
    const messages = await fetchRecentMessages(channel.id, 7);
    allMessages.push(...messages);
    channelActivity.set(channel.name, messages.length);
  }

  // 感情分析
  const sentiments = await analyzeSentiment(allMessages);
  const avgSentiment = sentiments.reduce((sum, s) => sum + s.score, 0) / sentiments.length;

  // Claude AIで包括的なサマリー生成
  const aiSummary = await claude.analyze({
    prompt: `以下のSlackデータから週次レポートを生成してください:

期間: ${startDate.toISOString()} 〜 ${endDate.toISOString()}
総メッセージ数: ${allMessages.length}
アクティブユーザー数: ${new Set(allMessages.map(m => m.user)).size}
平均感情スコア: ${avgSentiment.toFixed(2)}

トップチャンネル:
${Array.from(channelActivity.entries())
  .sort((a, b) => b[1] - a[1])
  .slice(0, 5)
  .map(([name, count]) => `- #${name}: ${count}件`)
  .join('\n')}

以下の形式でJSONを返してください:
{
  "summary": "今週の総括 (2-3文)",
  "highlights": ["ハイライト1", "ハイライト2", "ハイライト3"],
  "concerns": ["懸念事項1", "懸念事項2"],
  "recommendations": ["推奨アクション1", "推奨アクション2"]
}`,
    model: 'claude-3-5-sonnet-20241022'
  });

  const parsed = JSON.parse(aiSummary.content);

  return {
    period: {
      start: startDate.toISOString(),
      end: endDate.toISOString()
    },
    summary: parsed.summary,
    keyMetrics: {
      totalMessages: allMessages.length,
      activeUsers: new Set(allMessages.map(m => m.user)).size,
      topChannels: Array.from(channelActivity.entries())
        .sort((a, b) => b[1] - a[1])
        .slice(0, 5)
        .map(([name, activity]) => ({ name, activity })),
      sentimentTrend: avgSentiment
    },
    highlights: parsed.highlights,
    concerns: parsed.concerns,
    recommendations: parsed.recommendations
  };
}

// Slack に投稿
async function publishWeeklyReport(report: WeeklyReport): Promise<void> {
  await mcp.callTool('slack_post_message', {
    channel: process.env.WEEKLY_REPORT_CHANNEL!,
    blocks: [
      {
        type: 'header',
        text: { type: 'plain_text', text: '週次コミュニケーションレポート' }
      },
      {
        type: 'section',
        text: {
          type: 'mrkdwn',
          text: `*期間:* ${new Date(report.period.start).toLocaleDateString('ja-JP')} 〜 ${new Date(report.period.end).toLocaleDateString('ja-JP')}\n\n${report.summary}`
        }
      },
      {
        type: 'section',
        fields: report.keyMetrics.topChannels.map(ch => ({
          type: 'mrkdwn',
          text: `*#${ch.name}*\n${ch.activity}件`
        }))
      },
      {
        type: 'section',
        text: {
          type: 'mrkdwn',
          text: `*ハイライト:*\n${report.highlights.map(h => `• ${h}`).join('\n')}`
        }
      },
      {
        type: 'section',
        text: {
          type: 'mrkdwn',
          text: `*懸念事項:*\n${report.concerns.map(c => `⚠️ ${c}`).join('\n')}`
        }
      },
      {
        type: 'section',
        text: {
          type: 'mrkdwn',
          text: `*推奨アクション:*\n${report.recommendations.map((r, i) => `${i+1}. ${r}`).join('\n')}`
        }
      }
    ]
  });
}

トレンド予測:

async function predictEngagementTrend(
  channelId: string,
  historicalDays: number = 90,
  forecastDays: number = 30
): Promise<any> {
  // 過去90日間のデータ取得
  const historicalData = await fetchHistoricalEngagement(channelId, historicalDays);

  // Claude AIでトレンド分析
  const prediction = await claude.analyze({
    prompt: `過去90日間のSlackエンゲージメントデータを分析し、今後30日間のトレンドを予測してください:

${JSON.stringify(historicalData, null, 2)}

以下の情報をJSON形式で返してください:
{
  "trend": "increasing|stable|decreasing",
  "confidence": 0.0-1.0,
  "predictedDailyAverage": 数値,
  "seasonalPatterns": ["パターン1", "パターン2"],
  "riskFactors": ["リスク1", "リスク2"],
  "opportunities": ["機会1", "機会2"]
}`,
    model: 'claude-3-5-sonnet-20241022'
  });

  return JSON.parse(prediction.content);
}

アクションアイテム抽出:

async function extractActionItems(
  channelId: string,
  days: number = 7
): Promise<Array<{
  task: string;
  assignee?: string;
  dueDate?: string;
  priority: string;
  source: string;
}>> {
  const messages = await fetchRecentMessages(channelId, days);

  const actionItems = [];

  for (const msg of messages) {
    // アクションアイテムの可能性が高いメッセージを検出
    if (
      msg.text.includes('TODO') ||
      msg.text.includes('アクションアイテム') ||
      msg.text.includes('担当:') ||
      msg.text.includes('期限:')
    ) {
      // Claude AIで構造化
      const structured = await claude.analyze({
        prompt: `以下のメッセージからアクションアイテムを抽出してください:

"${msg.text}"

JSON形式で返してください:
{
  "task": "具体的なタスク",
  "assignee": "担当者名 (不明な場合null)",
  "dueDate": "期限 (YYYY-MM-DD形式、不明な場合null)",
  "priority": "high|medium|low"
}`,
        model: 'claude-3-5-sonnet-20241022'
      });

      const parsed = JSON.parse(structured.content);
      actionItems.push({
        ...parsed,
        source: msg.permalink
      });
    }
  }

  return actionItems;
}

結論

Slack MCPは、チームコミュニケーションデータを活用するための強力なツールです。この記事で紹介した技術と手法により、以下が可能になります:

主要な価値提案:

  1. データ駆動の意思決定: 客観的な指標に基づくチームマネジメント
  2. 早期問題検出: バーンアウトや孤立のリスクを事前に識別
  3. 自動化による効率化: レポート生成やアラートの手動作業を削減
  4. AIによる深い洞察: Claude AIの自然言語処理で隠れたパターンを発見
  5. スケーラビリティ: 小規模チームから大企業まで対応可能

始めるためのステップバイステップガイド:

ステップ1: 環境準備 (所要時間: 30分)

  1. Node.js 18+ をインストール
  2. Slack App を作成し、Bot Token を取得
  3. 公式TypeScript サーバーをインストール

ステップ2: 基本分析の実装 (所要時間: 2時間)

  1. メッセージボリューム分析から開始
  2. チャンネルエンゲージメント測定を追加
  3. 週次レポート自動化を設定

ステップ3: 高度な機能の追加 (所要時間: 1週間)

  1. Claude AI統合で感情分析を実装
  2. スレッド分析とユーザーエンゲージメント追加
  3. 他MCPサーバーとの統合 (GitHub、Postgres)

ステップ4: 本番運用と最適化 (継続的)

  1. Rate Limit管理とキャッシング実装
  2. セキュリティとGDPR準拠の確保
  3. チームフィードバックに基づく改善

次の学習リソース:

実践を通じてSlack MCPの可能性を探求し、チームのコミュニケーション文化を進化させましょう。

参考資料

公式ドキュメント:

オープンソースリソース:

コミュニティとサポート:

関連記事とチュートリアル:

  • “Building AI Agents with Model Context Protocol” (Anthropic Engineering Blog)
  • “Advanced Slack Analytics Techniques” (Medium)
  • “GDPR-Compliant Workplace Analytics” (Privacy Engineering Blog)

他の言語で読む

この記事は役に立ちましたか?

より良いコンテンツを作成するための力になります。コーヒー一杯で応援してください!☕

著者について

JK

Kim Jangwook

AI/LLM専門フルスタック開発者

10年以上のWeb開発経験を活かし、AIエージェントシステム、LLMアプリケーション、自動化ソリューションを構築しています。Claude Code、MCP、RAGシステムの実践的な知見を共有します。