0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Model Context Protocol完全解説 30日間シリーズ - Day 25【MCP実戦 #25】複数サービス統合:GitHubとSlackを連携させるMCP実例

Last updated at Posted at 2025-09-15

はじめに

この記事は、QiitaのModel Context Protocol(以下、MCP)解説シリーズの第25回です。

今回は、MCPのTools機能をさらに応用し、複数の外部サービスを統合する実践例を解説します。具体的には、GitHubとSlackを連携させるMCPサーバーを構築し、LLMがこれらのサービスを横断的に操作する様子を見てみましょう。


💡 なぜ複数サービス統合が重要なのか?

単一のサービスをMCPに統合するだけでも便利ですが、複数のサービスを組み合わせることで、LLMはより複雑で実用的なタスクを解決できるようになります。

例:開発チームのサポート

  • 「最新のバグ修正をSlackの#devチャンネルに通知して。」
  • 「GitHubで特定のコミットを探し、関連するSlackのディスカッションを要約して。」
  • 「プルリクエストのレビュー結果をSlackに自動投稿して。」

このようなタスクは、LLMがGitHubとSlackのToolsを組み合わせて使うことで初めて可能になります。


🛠️ ステップ1:プロジェクトのセットアップ

今回は、TypeScriptと、GitHubとSlackのAPIを扱うためのライブラリを使用します。

mkdir mcp-multi-service
cd mcp-multi-service
npm init -y
npm install typescript ts-node @modelcontextprotocol/sdk @octokit/rest @slack/web-api zod dotenv
npm install -D @types/node
npx tsc --init

tsconfig.jsonの設定

{
  "compilerOptions": {
    "target": "ES2020",
    "module": "commonjs",
    "moduleResolution": "node",
    "esModuleInterop": true,
    "allowSyntheticDefaultImports": true,
    "strict": true,
    "skipLibCheck": true,
    "forceConsistentCasingInFileNames": true
  }
}

APIトークンの取得

GitHubトークンの取得手順:

  1. GitHub > Settings > Developer settings > Personal access tokens > Tokens (classic)
  2. 「Generate new token」をクリック
  3. 必要なスコープを選択:repo, user:email

Slackトークンの取得手順:

  1. Slack APIでアプリを作成
  2. OAuth & Permissions で以下のスコープを追加:
    • chat:write
    • channels:read
    • groups:read
  3. Bot User OAuth Tokenをコピー

.envファイルの作成

# GitHubのPersonal Access Token
GITHUB_TOKEN=ghp_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

# SlackのBot Token
SLACK_BOT_TOKEN=xoxb-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

# GitHubリポジトリ情報
GITHUB_OWNER=your_github_username
GITHUB_REPO=your_repo_name

📝 ステップ2:Toolsの実装

server.tsファイルを作成し、GitHubとSlackのToolsを定義します。

import { Server } from '@modelcontextprotocol/sdk/server/index.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import {
  CallToolRequestSchema,
  ListToolsRequestSchema,
} from '@modelcontextprotocol/sdk/types.js';
import { Octokit } from '@octokit/rest';
import { WebClient } from '@slack/web-api';
import { z } from 'zod';
import 'dotenv/config';

// 環境変数の検証
const requiredEnvVars = ['GITHUB_TOKEN', 'SLACK_BOT_TOKEN', 'GITHUB_OWNER', 'GITHUB_REPO'];
for (const envVar of requiredEnvVars) {
  if (!process.env[envVar]) {
    throw new Error(`環境変数 ${envVar} が設定されていません`);
  }
}

// APIクライアントの初期化
const github = new Octokit({ auth: process.env.GITHUB_TOKEN });
const slack = new WebClient(process.env.SLACK_BOT_TOKEN);

const githubRepo = {
  owner: process.env.GITHUB_OWNER!,
  repo: process.env.GITHUB_REPO!
};

// 入力スキーマの定義
const GetLatestCommitsSchema = z.object({
  count: z.number().int().min(1).max(10).describe('取得するコミットの数(1-10)'),
});

const PostSlackMessageSchema = z.object({
  channel: z.string().describe('メッセージを投稿するSlackチャンネル名(例:#general)'),
  message: z.string().describe('投稿するメッセージの本文'),
});

const GetPullRequestsSchema = z.object({
  state: z.enum(['open', 'closed', 'all']).default('open').describe('プルリクエストの状態'),
  count: z.number().int().min(1).max(10).default(5).describe('取得するプルリクエストの数'),
});

// サーバーの作成
const server = new Server(
  {
    name: 'mcp-multi-service',
    version: '1.0.0',
  },
  {
    capabilities: {
      tools: {},
    },
  }
);

// ツールの一覧を返すハンドラー
server.setRequestHandler(ListToolsRequestSchema, async () => {
  return {
    tools: [
      {
        name: 'get_latest_commits',
        description: 'GitHubリポジトリの最新のコミット情報を取得します',
        inputSchema: GetLatestCommitsSchema,
      },
      {
        name: 'post_slack_message',
        description: '指定されたSlackチャンネルにメッセージを投稿します',
        inputSchema: PostSlackMessageSchema,
      },
      {
        name: 'get_pull_requests',
        description: 'GitHubリポジトリのプルリクエスト一覧を取得します',
        inputSchema: GetPullRequestsSchema,
      },
    ],
  };
});

// ツールの実行ハンドラー
server.setRequestHandler(CallToolRequestSchema, async (request) => {
  const { name, arguments: args } = request.params;

  try {
    switch (name) {
      case 'get_latest_commits': {
        const input = GetLatestCommitsSchema.parse(args);
        const { data } = await github.repos.listCommits({
          ...githubRepo,
          per_page: input.count,
        });

        const commits = data.map(commit => ({
          sha: commit.sha.substring(0, 8),
          message: commit.commit.message.split('\n')[0], // 最初の行のみ
          author: commit.commit.author?.name || 'Unknown',
          date: commit.commit.author?.date,
          url: commit.html_url,
        }));

        return {
          content: [
            {
              type: 'text',
              text: JSON.stringify(commits, null, 2),
            },
          ],
        };
      }

      case 'post_slack_message': {
        const input = PostSlackMessageSchema.parse(args);
        
        const result = await slack.chat.postMessage({
          channel: input.channel,
          text: input.message,
        });

        if (!result.ok) {
          throw new Error(`Slackへの投稿に失敗: ${result.error}`);
        }

        return {
          content: [
            {
              type: 'text',
              text: JSON.stringify({
                status: 'success',
                message: 'メッセージを正常に投稿しました',
                timestamp: result.ts,
                channel: result.channel,
              }, null, 2),
            },
          ],
        };
      }

      case 'get_pull_requests': {
        const input = GetPullRequestsSchema.parse(args);
        
        const { data } = await github.pulls.list({
          ...githubRepo,
          state: input.state,
          per_page: input.count,
        });

        const pullRequests = data.map(pr => ({
          number: pr.number,
          title: pr.title,
          author: pr.user?.login || 'Unknown',
          state: pr.state,
          created_at: pr.created_at,
          url: pr.html_url,
        }));

        return {
          content: [
            {
              type: 'text',
              text: JSON.stringify(pullRequests, null, 2),
            },
          ],
        };
      }

      default:
        throw new Error(`未知のツール: ${name}`);
    }
  } catch (error) {
    const errorMessage = error instanceof Error ? error.message : '不明なエラーが発生しました';
    return {
      content: [
        {
          type: 'text',
          text: `エラー: ${errorMessage}`,
        },
      ],
      isError: true,
    };
  }
});

// サーバーの起動
async function main() {
  const transport = new StdioServerTransport();
  await server.connect(transport);
  console.error('MCP multi-service server started');
}

if (require.main === module) {
  main().catch((error) => {
    console.error('サーバー起動エラー:', error);
    process.exit(1);
  });
}

🔧 ステップ3:Claude Desktop設定

Claude Desktopで新しいMCPサーバーを使用するため、設定ファイルを更新します。

macOS: ~/Library/Application Support/Claude/claude_desktop_config.json
Windows: %APPDATA%/Claude/claude_desktop_config.json

{
  "mcpServers": {
    "multi-service": {
      "command": "npx",
      "args": ["ts-node", "/path/to/your/mcp-multi-service/server.ts"],
      "env": {
        "GITHUB_TOKEN": "your_github_token",
        "SLACK_BOT_TOKEN": "your_slack_token",
        "GITHUB_OWNER": "your_github_username",
        "GITHUB_REPO": "your_repo_name"
      }
    }
  }
}

🚀 ステップ4:動作確認とテスト例

Claude Desktopを再起動後、以下のような指示でテストしてみてください。

単一ツールのテスト

例1: GitHubコミット取得

「GitHubリポジトリの最新のコミットを3つ教えて。」

例2: Slackメッセージ投稿

「Slackの#generalチャンネルに「テスト投稿です」というメッセージを送って。」

複数ツールの連携テスト

例3: コミット情報をSlackに通知

「GitHubの最新コミット情報を取得して、その内容を要約してSlackの#devチャンネルに投稿して。」

期待される動作:

  1. get_latest_commitsツールで最新コミット情報を取得
  2. Claudeがコミット情報を分析・要約
  3. post_slack_messageツールで要約をSlackに投稿

例4: プルリクエストステータス報告

「現在オープンなプルリクエストをチェックして、その状況をSlackの#teamチャンネルに報告して。」

🎯 セキュリティとベストプラクティス

セキュリティ考慮事項

  1. 環境変数の管理: APIトークンは必ず環境変数で管理し、コードに直接記載しない
  2. スコープ制限: 必要最小限のAPIスコープのみを付与する
  3. エラーハンドリング: 機密情報がエラーメッセージに含まれないよう注意

パフォーマンス最適化

  1. レート制限対応: GitHub APIとSlack APIのレート制限を考慮した実装
  2. バッチ処理: 複数の操作をまとめて実行できる場合は効率化を図る
  3. キャッシュ機能: 頻繁にアクセスする情報はキャッシュを検討

🔄 応用例とカスタマイズ

追加できる機能例

  1. Issue管理: GitHubのIssueとSlackを連携
  2. デプロイ通知: CI/CDパイプラインの結果をSlackに通知
  3. レビュー依頼: プルリクエストのレビュー依頼をSlackで自動送信

他サービスとの統合

このパターンは以下のサービスにも適用可能です:

  • プロジェクト管理: Jira、Asana、Trello
  • コミュニケーション: Discord、Microsoft Teams
  • クラウドサービス: AWS、Google Cloud、Azure
  • CRM: Salesforce、HubSpot

📚 まとめ

本記事では、MCPのTools機能を活用してGitHubとSlackを統合する実例を紹介しました。

重要なポイント:

  • 複数サービス統合の威力: LLMが異なるサービス間でデータを連携し、複雑なワークフローを自動化
  • 自律的な判断力: ユーザーの指示から適切なツールの組み合わせを選択
  • 安全な設計: 認証情報の適切な管理とエラーハンドリング
  • 拡張性: 新しいサービスやツールを簡単に追加可能

このパターンをマスターすることで、LLMを活用した業務自動化の可能性が大きく広がります。

次回は、開発効率化をテーマに、テスト自動化とCI/CDパイプライン構築について解説します。お楽しみに!


🔗 関連リンク

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?