JiraでAIを強化する | 第2章：タスクを自動化：AIによるTicketとコメント操作

## はじめに

第1章では、**Jira**と**Model Context Protocol（MCP）**の基本を学び、JiraプロジェクトからTicketやSprintデータを取得するMCPサーバーを構築しました。これにより、AIがプロジェクトの情報を取得できるようになりました。今回は、この基盤を進化させ、JiraにTicketを作成したり、コメントを追加したりする機能を実装します。これにより、**タスクを自動化**するエージェントを構築します。

この第2章では、MCPサーバーを通じてAIがTicketに優先度を付けたり、コメントを追加したりできるようにします。たとえば、ユーザーが特定のキーワードを含むTicketを作成すると、AIが自動で「High」優先度を付けることができます。コード例とステップごとのガイドで、プロジェクトタスク自動化の可能性を体感しましょう。さあ、始めましょう！

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

タスク自動化エージェントは、JiraのTicketやコメントを操作し、プロジェクト管理を効率化するAIです。MCPサーバーを介して、以下のような機能を実現できます：
- **Ticket管理**：Ticketの作成、優先度設定、コメント追加。
- **自動化ルール**：特定の条件（例：キーワードやステータス）に基づいてアクションを実行。
- **チーム支援**：Ticketの割り当てや通知を最適化。

### ユースケース
- バグ管理：AIが「エラー」キーワードを含むTicketに「High」優先度を自動付与。
- コミュニケーション：AIがTicketに初期コメントを追加し、担当者に指示。
- ワークフロー最適化：AIが期限付きTicketを作成し、チームに割り当て。

## 開発環境の準備

第1章の環境を基に、以下の準備を行います：
- **Python 3.8以降**、**mcpライブラリ**、**requestsライブラリ**、**Claude Desktop**：第1章と同じ。
- **python-dotenv**：環境変数の管理（既にインストール済み）。
- **Jiraプロジェクト**：Ticket操作のための設定。

### Jiraのセットアップ
1. **Jira APIトークンの確認**：
   - 第1章のトークンを使用。
   - 権限を確認：プロジェクトでのTicket作成、編集、コメント追加が可能。
2. **プロジェクト準備**：
   - 第1章のプロジェクト（例：`MCP-TEST`、キー：`MCP`）を使用。
   - Ticketタイプ（例：Bug、Task）と優先度（例：High、Medium、Low）を設定。
   - テスト用ユーザーをプロジェクトに追加（コメントや割り当て用）。
3. **環境変数**：
   第1章の`.env`ファイル（`JIRA_URL`、`JIRA_EMAIL`、`JIRA_API_TOKEN`、`JIRA_PROJECT_KEY`）を再利用：
   ```bash
   JIRA_URL=https://your-domain.atlassian.net
   JIRA_EMAIL=your_email@example.com
   JIRA_API_TOKEN=your_jira_api_token
   JIRA_PROJECT_KEY=MCP
   ```

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

以下のMCPサーバーは、JiraからTicketデータを取得し、Ticket作成やコメント追加の機能を提供します。

```python
from mcp import MCPServer
import os
from dotenv import load_dotenv
import requests
from requests.auth import HTTPBasicAuth

class JiraTaskServer(MCPServer):
    def __init__(self, host, port, url, email, api_token, project_key):
        super().__init__(host, port)
        self.url = url
        self.email = email
        self.api_token = api_token
        self.project_key = project_key
        self.base_url = f"{url}/rest/api/3"
        self.auth = HTTPBasicAuth(email, api_token)
        self.headers = {
            "Accept": "application/json",
            "Content-Type": "application/json"
        }
        self.register_resource("get_tickets", self.get_tickets)
        self.register_tool("create_ticket", self.create_ticket)
        self.register_tool("add_comment", self.add_comment)

    def get_tickets(self, params):
        try:
            url = f"{self.base_url}/search"
            query = {
                "jql": f"project={self.project_key} AND status={params.get('status', 'Open')}",
                "maxResults": params.get("limit", 10)
            }
            response = requests.get(url, headers=self.headers, auth=self.auth, params=query)
            response.raise_for_status()
            issues = response.json()["issues"]
            ticket_list = [
                {
                    "key": issue["key"],
                    "summary": issue["fields"]["summary"],
                    "status": issue["fields"]["status"]["name"],
                    "creator": issue["fields"]["creator"]["displayName"],
                    "created": issue["fields"]["created"]
                }
                for issue in issues
            ]
            return {"status": "success", "tickets": ticket_list}
        except Exception as e:
            return {"status": "error", "message": str(e)}

    def create_ticket(self, params):
        try:
            summary = params.get("summary", "")
            description = params.get("description", "")
            issue_type = params.get("issue_type", "Task")
            priority = params.get("priority", "Medium")
            if not summary:
                return {"status": "error", "message": "サマリーが必要です"}
            
            url = f"{self.base_url}/issue"
            payload = {
                "fields": {
                    "project": {"key": self.project_key},
                    "summary": summary,
                    "description": description,
                    "issuetype": {"name": issue_type},
                    "priority": {"name": priority}
                }
            }
            response = requests.post(url, headers=self.headers, auth=self.auth, json=payload)
            response.raise_for_status()
            issue = response.json()
            return {"status": "success", "ticket_key": issue["key"]}
        except Exception as e:
            return {"status": "error", "message": str(e)}

    def add_comment(self, params):
        try:
            ticket_key = params.get("ticket_key", "")
            comment = params.get("comment", "")
            if not ticket_key or not comment:
                return {"status": "error", "message": "Ticketキーとコメントが必要です"}
            
            url = f"{self.base_url}/issue/{ticket_key}/comment"
            payload = {"body": comment}
            response = requests.post(url, headers=self.headers, auth=self.auth, 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 = JiraTaskServer(
        host="localhost",
        port=8133,
        url=os.getenv("JIRA_URL"),
        email=os.getenv("JIRA_EMAIL"),
        api_token=os.getenv("JIRA_API_TOKEN"),
        project_key=os.getenv("JIRA_PROJECT_KEY")
    )
    print("JiraタスクMCPサーバーを起動中: http://localhost:8133")
    server.start()
```

### コードの説明
- **get_tickets**：第1章から再利用。プロジェクトのTicketを取得。
- **create_ticket**：新しいTicketを作成。サマリー、説明、課題タイプ、優先度を指定。
- **add_comment**：指定したTicketにコメントを追加。Ticketキーとコメント内容を指定。
- **register_resource/tool**：Ticket取得をリソース、Ticket作成・コメント追加をツールとして登録。

### 前提条件
- Jira APIトークンにプロジェクトへの書き込み権限がある。
- プロジェクトに課題タイプ（例：Bug、Task）と優先度（例：High、Medium、Low）が設定済み。
- `.env`ファイルに正しい`JIRA_URL`、`JIRA_EMAIL`、`JIRA_API_TOKEN`、`JIRA_PROJECT_KEY`が設定済み。

## サーバーのテスト

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

1. **サーバー起動**：
   ```bash
   python jira_task_server.py
   ```
   コンソールに「JiraタスクMCPサーバーを起動中: http://localhost:8133」と表示。

2. **Ticket作成のテスト**：
   Pythonでリクエストを送信：
   ```python
   import requests
   import json

   url = "http://localhost:8133"
   payload = {
       "jsonrpc": "2.0",
       "method": "create_ticket",
       "params": {
           "summary": "新しいバグ報告",
           "description": "アプリがクラッシュする問題が発生。",
           "issue_type": "Bug",
           "priority": "High"
       },
       "id": 1
   }
   response = requests.post(url, json=payload)
   print(json.dumps(response.json(), indent=2, ensure_ascii=False))
   ```
   期待されるレスポンス：
   ```json
   {
     "jsonrpc": "2.0",
     "result": {
       "status": "success",
       "ticket_key": "MCP-3"
     },
     "id": 1
   }
   ```

3. **コメント追加のテスト**：
   ```python
   payload = {
       "jsonrpc": "2.0",
       "method": "add_comment",
       "params": {
           "ticket_key": "MCP-3",
           "comment": "問題を確認しました。詳細を教えてください。"
       },
       "id": 2
   }
   response = requests.post(url, json=payload)
   print(json.dumps(response.json(), indent=2, ensure_ascii=False))
   ```
   期待されるレスポンス：
   ```json
   {
     "jsonrpc": "2.0",
     "result": {
       "status": "success",
       "comment_id": "10001"
     },
     "id": 2
   }
   ```

## Claude Desktopとの接続

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

1. **設定ファイルの編集**：
   Claude Desktopの設定ファイル（例：`claude_desktop_config.json`）に以下を追加：
   ```json
   {
     "mcp_servers": [
       {
         "name": "JiraTaskServer",
         "url": "http://localhost:8133",
         "auth": "none"
       }
     ]
   }
   ```

2. **Claudeでテスト**：
   Claude Desktopを起動し、プロンプトを入力：
   ```
   プロジェクトに「新しいバグ報告」というBug Ticketを作成し、優先度を「High」にしてください。
   ```
   レスポンス例：
   ```
   Ticket MCP-3「新しいバグ報告」を作成し、優先度を「High」に設定しました。
   ```

   別のプロンプト：
   ```
   Ticket MCP-3に「問題を確認しました。詳細を教えてください。」とコメントしてください。
   ```
   レスポンス例：
   ```
   Ticket MCP-3にコメントを追加しました。
   ```

## 実装のコツと注意点

- **エラーハンドリング**：無効なTicketキーや優先度が存在しない場合のエラーを適切に処理。
- **レートリミティング**：Jira APIの制限（例：600リクエスト/分、クラウドインスタンスによる）に注意。
- **セキュリティ**：本番環境では、`auth: none`を避け、トークン認証を導入。
- **テスト**：テスト用プロジェクトを作成し、本番データに影響を与えない。
- **拡張性**：大量のTicketを処理する場合、キャッシュ（例：Redis）を検討。

## 試してみよう：挑戦課題
以下の機能を追加して、エージェントを強化してみてください：
- 特定のキーワード（例：「エラー」）を含むTicketに自動で「High」優先度を付ける機能。
- Ticketに担当者を割り当てるツール。
- Ticketのステータスを更新（例：Open → In Progress）する機能。

## まとめと次のステップ

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

次の第3章では、JiraのTicketデータを活用して**プロジェクト分析エージェント**を構築します。たとえば、AIがSprintの完了率やチームのパフォーマンスを分析し、プロジェクトのインサイトを提供します。プロジェクト分析AIに興味がある方は、ぜひお楽しみに！

---

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