2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

GitHubでAIを強化する | 第2章:タスクを自動化:AIによるIssueとPR操作

Posted at

はじめに

第1章では、GitHubと**Model Context Protocol(MCP)**の基本を学び、GitHubリポジトリからIssueとコミットデータを取得するMCPサーバーを構築しました。これにより、AIがリポジトリの情報を取得できるようになりました。今回は、この基盤を進化させ、GitHubにIssueを作成したり、Pull Request(PR)にコメントしたりする機能を実装します。これにより、タスクを自動化するエージェントを構築します。

この第2章では、MCPサーバーを通じてAIがIssueにラベルを付けたり、PRにコメントを追加したりできるようにします。たとえば、ユーザーが特定のキーワードを含むIssueを作成すると、AIが自動で適切なラベル(例:「bug」)を付けることができます。コード例とステップごとのガイドで、開発タスク自動化の可能性を体感しましょう。さあ、始めましょう!

タスク自動化エージェントとは?

タスク自動化エージェントは、GitHubのIssueやPRを操作し、開発プロセスを効率化するAIです。MCPサーバーを介して、以下のような機能を実現できます:

  • Issue管理:Issueの作成、ラベル付与、コメント追加。
  • PR操作:PRへのコメント追加、レビューリクエスト。
  • 自動化ルール:特定の条件(例:キーワードやユーザー)に基づいてアクションを実行。

ユースケース

  • バグ管理:AIが「エラー」キーワードを含むIssueに「bug」ラベルを自動付与。
  • コードレビュー:AIがPRに初期コメントを追加し、レビュアーに通知。
  • プロジェクト管理:AIが期限付きIssueを作成し、チームに割り当て。

開発環境の準備

第1章の環境を基に、以下の準備を行います:

  • Python 3.8以降mcpライブラリrequestsライブラリClaude Desktop:第1章と同じ。
  • python-dotenv:環境変数の管理(既にインストール済み)。
  • GitHubリポジトリ:IssueやPRを操作するための設定。

GitHubのセットアップ

  1. GitHub Personal Access Tokenの拡張
    • 第1章のトークンを使用。
    • スコープを確認:repo(Issue、PR、コミット操作)、必要に応じてadmin:repo_hook(Webhook用)。
    • トークンがIssueとPRの書き込み権限を持つことを確認。
  2. リポジトリ準備
    • 第1章のリポジトリ(例:yourusername/mcp-test-repo)を使用。
    • 新しいブランチを作成し、PRを1つ作成(例:「機能追加」ブランチ)。
    • Issueラベルを追加(例:「bug」「enhancement」)。
  3. 環境変数
    第1章の.envファイル(GITHUB_TOKENGITHUB_REPO)を再利用:
    GITHUB_TOKEN=your_token
    GITHUB_REPO=yourusername/mcp-test-repo
    

コード例:タスク自動化用MCPサーバー

以下のMCPサーバーは、GitHubからIssueやコミットを取得し、Issue作成やPRコメント追加の機能を提供します。

from mcp import MCPServer
import os
from dotenv import load_dotenv
import requests

class GitHubTaskServer(MCPServer):
    def __init__(self, host, port, token, repo):
        super().__init__(host, port)
        self.token = token
        self.repo = repo
        self.base_url = "https://api.github.com"
        self.headers = {
            "Authorization": f"Bearer {token}",
            "Accept": "application/vnd.github.v3+json"
        }
        self.register_resource("get_issues", self.get_issues)
        self.register_tool("create_issue", self.create_issue)
        self.register_tool("add_pr_comment", self.add_pr_comment)

    def get_issues(self, params):
        try:
            url = f"{self.base_url}/repos/{self.repo}/issues"
            query = {"state": params.get("state", "open"), "per_page": params.get("limit", 10)}
            response = requests.get(url, headers=self.headers, params=query)
            response.raise_for_status()
            issues = response.json()
            issue_list = [
                {
                    "title": issue["title"],
                    "number": issue["number"],
                    "state": issue["state"],
                    "creator": issue["user"]["login"],
                    "created_at": issue["created_at"]
                }
                for issue in issues
            ]
            return {"status": "success", "issues": issue_list}
        except Exception as e:
            return {"status": "error", "message": str(e)}

    def create_issue(self, params):
        try:
            title = params.get("title", "")
            body = params.get("body", "")
            labels = params.get("labels", [])
            if not title:
                return {"status": "error", "message": "タイトルが必要です"}
            
            url = f"{self.base_url}/repos/{self.repo}/issues"
            payload = {
                "title": title,
                "body": body,
                "labels": labels
            }
            response = requests.post(url, headers=self.headers, json=payload)
            response.raise_for_status()
            issue = response.json()
            return {"status": "success", "issue_number": issue["number"]}
        except Exception as e:
            return {"status": "error", "message": str(e)}

    def add_pr_comment(self, params):
        try:
            pr_number = params.get("pr_number", 0)
            comment = params.get("comment", "")
            if not pr_number or not comment:
                return {"status": "error", "message": "PR番号とコメントが必要です"}
            
            url = f"{self.base_url}/repos/{self.repo}/issues/{pr_number}/comments"
            payload = {"body": comment}
            response = requests.post(url, headers=self.headers, json=payload)
            response.raise_for_status()
            comment = response.json()
            return {"status": "success", "comment_id": comment["id"]}
        except Exception as e:
            return {"status": "error", "message": str(e)}

if __name__ == "__main__":
    load_dotenv()
    server = GitHubTaskServer(
        host="localhost",
        port=8128,
        token=os.getenv("GITHUB_TOKEN"),
        repo=os.getenv("GITHUB_REPO")
    )
    print("GitHubタスクMCPサーバーを起動中: http://localhost:8128")
    server.start()

コードの説明

  • get_issues:第1章から再利用。リポジトリのIssueを取得。
  • create_issue:新しいIssueを作成。タイトル、本文、ラベルを指定。
  • add_pr_comment:指定したPRにコメントを追加。PR番号とコメント内容を指定。
  • register_resource/tool:Issue取得をリソース、Issue作成・PRコメントをツールとして登録。

前提条件

  • GitHubトークンにrepoスコープが付与されている。
  • リポジトリにPRとラベル(例:「bug」「enhancement」)が存在。
  • .envファイルに正しいGITHUB_TOKENGITHUB_REPOが設定済み。

サーバーのテスト

サーバーが正しく動作するか確認します:

  1. サーバー起動

    python github_task_server.py
    

    コンソールに「GitHubタスクMCPサーバーを起動中: http://localhost:8128」と表示。

  2. Issue作成のテスト
    Pythonでリクエストを送信:

    import requests
    import json
    
    url = "http://localhost:8128"
    payload = {
        "jsonrpc": "2.0",
        "method": "create_issue",
        "params": {
            "title": "新しいバグ報告",
            "body": "アプリがクラッシュする問題が発生。",
            "labels": ["bug"]
        },
        "id": 1
    }
    response = requests.post(url, json=payload)
    print(json.dumps(response.json(), indent=2, ensure_ascii=False))
    

    期待されるレスポンス:

    {
      "jsonrpc": "2.0",
      "result": {
        "status": "success",
        "issue_number": 3
      },
      "id": 1
    }
    
  3. PRコメント追加のテスト

    payload = {
        "jsonrpc": "2.0",
        "method": "add_pr_comment",
        "params": {
            "pr_number": 1,
            "comment": "コードレビュー:良好ですが、ドキュメントを追加してください。"
        },
        "id": 2
    }
    response = requests.post(url, json=payload)
    print(json.dumps(response.json(), indent=2, ensure_ascii=False))
    

    期待されるレスポンス:

    {
      "jsonrpc": "2.0",
      "result": {
        "status": "success",
        "comment_id": 123456789
      },
      "id": 2
    }
    

Claude Desktopとの接続

サーバーをClaude Desktopに接続します:

  1. 設定ファイルの編集
    Claude Desktopの設定ファイル(例:claude_desktop_config.json)に以下を追加:

    {
      "mcp_servers": [
        {
          "name": "GitHubTaskServer",
          "url": "http://localhost:8128",
          "auth": "none"
        }
      ]
    }
    
  2. Claudeでテスト
    Claude Desktopを起動し、プロンプトを入力:

    リポジトリに「新しいバグ報告」というIssueを作成し、「bug」ラベルを付けてください。
    

    レスポンス例:

    Issue #3「新しいバグ報告」を作成し、「bug」ラベルを付けました。
    

    別のプロンプト:

    PR #1に「コードレビュー:良好ですが、ドキュメントを追加してください。」とコメントしてください。
    

    レスポンス例:

    PR #1にコメントを追加しました。
    

実装のコツと注意点

  • エラーハンドリング:無効なPR番号やラベルが存在しない場合のエラーを適切に処理。
  • レートリミティング:GitHub APIの制限(例:5000リクエスト/時間)に注意。
  • セキュリティ:本番環境では、auth: noneを避け、トークン認証を導入。
  • テスト:テスト用リポジトリを作成し、本番データに影響を与えない。
  • 拡張性:大量のIssueやPRを処理する場合、キャッシュ(例:Redis)を検討。

試してみよう:挑戦課題

以下の機能を追加して、エージェントを強化してみてください:

  • 特定のキーワード(例:「エラー」)を含むIssueに自動で「bug」ラベルを付ける機能。
  • PRにレビュアーを割り当てるツール。
  • Issueをクローズする機能。

まとめと次のステップ

この第2章では、GitHubにIssueを作成し、PRにコメントを追加するMCPサーバーを構築し、開発タスク自動化エージェントを実現しました。AIがIssueやPRを操作できるようになり、チームの効率が向上しました。

次の第3章では、GitHubのコミットデータを活用してコード分析エージェントを構築します。たとえば、AIがコミット履歴から変更パターンを分析し、コード品質のインサイトを提供します。コード分析AIに興味がある方は、ぜひお楽しみに!


役に立ったと思ったら、「いいね」や「ストック」をしていただけると嬉しいです!次の章でまたお会いしましょう!

2
1
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
2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?