0
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?

ポケモン対戦の育成・構築をAIに任せたい!MCPサーバーを自作してGitHub Copilotと連携してみた

0
Posted at

はじめに

ポケモンチャンピオンズの選出画面、制限時間ありますよね。

相手のパーティを見て、タイプ相性を頭の中で計算して、「こおり一貫してるな…」「でもこっちを出すとじめんが重い…」と考えているうちに時間が過ぎていく。結局なんとなくで3体を選んで、試合後に「あの選出は間違いだった」と気づく。これを何度も繰り返していました。

育成にしても同じです。種族値を調べて、タイプ相性を確認して、努力値配分を考えて、性格を決めて…。ポケモン1体の育成方針を決めるだけで、複数のサイトを行き来する作業が発生します。

「この作業、全部AIに聞けたら楽なのに」

そう思って作ったのが、ポケモンのデータ検索・育成提案・パーティ分析ができる MCP サーバーです。GitHub Copilot に「このパーティの弱点を分析して」と聞くだけで、18タイプの相性を全部計算して改善提案まで返してくれます。

この記事では、MCP サーバーの設計・実装から、実際に GitHub Copilot と連携して使うところまでを解説します。

MCP とは

MCP(Model Context Protocol)は、AI アシスタントに外部ツールを追加するためのプロトコルです。Anthropic が策定したオープン標準で、VS Code の GitHub Copilot や Claude Desktop などが対応しています。

簡単に言うと「AIに新しいスキルを教える仕組み」です。今回はポケモンの知識を教えます。

作ったもの

PokeAPI と連携して、以下の 5 つのツールを提供する MCP サーバーです。

ツール できること
search_pokemon ポケモンの種族値・タイプ・特性を検索
recommend_build 役割に応じた性格・努力値配分を提案
analyze_team パーティのタイプバランスを分析・改善提案
check_type_matchup タイプ相性の倍率を計算
suggest_selection 相手パーティに対する選出を提案

技術スタック

  • Python 3.11+
  • FastMCP(MCP の Python SDK)
  • httpx(非同期 HTTP クライアント、HTTP/2 対応)
  • PokeAPI(ポケモンデータの公開 API)
  • uv(パッケージマネージャー)

プロジェクト構成

mcp/
├── server.py         # MCPサーバー本体(ツール定義)
├── pokeapi.py        # PokeAPI クライアント(キャッシュ付き)
├── pokemon_data.py   # タイプ相性・性格などの静的データ
└── pyproject.toml    # プロジェクト設定

各ツールの実装解説

各ツールを「何ができるか → コード → 実行例」の3点セットで紹介します。

search_pokemon — ポケモン検索

何ができるか: ポケモンの名前(英語名・日本語名・図鑑番号)から種族値・タイプ・特性・弱点を一発表示します。

コード(server.py より抜粋):

@mcp.tool()
async def search_pokemon(name: str) -> str:
    """ポケモンの種族値・タイプ・特性を検索します。

    Args:
        name: ポケモンの名前(例: pikachu, ピカチュウ, 25)
    """
    pokemon, species, ja_name = await _fetch_pokemon_full(name)

    # 種族値を取得
    stats = {s["stat"]["name"]: s["base_stat"] for s in pokemon["stats"]}

    # タイプから弱点・耐性を自動計算
    types = [t["type"]["name"] for t in pokemon["types"]]
    weakness_map = get_type_weaknesses(types)
    # ...(整形して返す)

ポイントは docstring がそのまま AI への説明になること。Args の説明が具体的だと、AI が適切に引数を渡してくれます。

実行例:

👤 ピカチュウの種族値を教えて

🤖 ## ピカチュウ (#25)
   タイプ: でんき
   特性: せいでんき, ひらいしん(夢特性)

   ### 種族値
     HP       35 ██░░░░░░░░░░░░░░░░░░
     こうげき  55 ████░░░░░░░░░░░░░░░░
     ぼうぎょ  40 ███░░░░░░░░░░░░░░░░░
     とくこう  50 ███░░░░░░░░░░░░░░░░░
     とくぼう  50 ███░░░░░░░░░░░░░░░░░
     すばやさ  90 ███████░░░░░░░░░░░░░
     合計   320

   弱点: じめん(x2.0)
   耐性: でんき(x0.5), ひこう(x0.5), はがね(x0.5)

recommend_build — 育成提案

何ができるか: 種族値から役割(物理アタッカー・特殊受けなど)を自動判定し、性格・努力値配分を提案します。

コード(役割自動判定ロジック):

def _detect_role(stats: dict[str, int]) -> str:
    """種族値から最適な役割を自動判定"""
    atk = stats.get("attack", 0)
    spa = stats.get("special-attack", 0)
    spd_stat = stats.get("speed", 0)
    bulk = stats.get("hp", 0) + stats.get("defense", 0) + stats.get("special-defense", 0)
    offense = max(atk, spa)

    if offense >= 100 and spd_stat >= 90:
        return "physical_attacker" if atk >= spa else "special_attacker"
    if bulk >= 280 and offense < 90:
        return "physical_tank" if df >= spd else "special_tank"
    # ...

攻撃種族値 100 以上 × すばやさ 90 以上 → アタッカー、耐久合計 280 以上 × 火力 90 未満 → 受け、のようにルールベースで判定しています。role パラメータで手動指定も可能です。

実行例:

👤 ガブリアスの育成提案をして

🤖 ## ガブリアス の育成提案
   判定された役割: 物理アタッカー

   ### 性格
     おすすめ: ようき(jolly)
       ↑ すばやさ / ↓ とくこう

   ### 努力値配分
     こうげき: 252
     すばやさ: 252
     HP:       4

   ### 育成のポイント
   - すばやさが高いため、上から殴れるアタッカーとして運用できます
   - ⚠️ 4倍弱点: こおり(要注意)

analyze_team — パーティ分析

何ができるか: パーティ(最大6体)の攻撃面タイプカバー、防御面の弱点集中、ステータスバランスを分析し、改善提案を出します。18タイプ全ての相性を自動計算します。

コード(弱点集中の検出ロジック):

# チーム全体で各タイプへの弱点数を集計
type_weakness_count: dict[str, int] = {t: 0 for t in ALL_TYPES}

for p in team_data:
    wmap = get_type_weaknesses(p["types"])  # 18タイプ全ての倍率を計算
    for t in ALL_TYPES:
        mult = wmap.get(t, 1.0)
        if mult > 1.0:
            type_weakness_count[t] += 1

# 2体以上が弱点を持つタイプを警告
danger_types = [(t, c) for t, c in type_weakness_count.items() if c >= 2]

get_type_weaknesses() が 18×18 の相性マトリクスから複合タイプの倍率を掛け算で計算します(足し算ではなく掛け算がポイント。例: リザードンにいわ技 = 2.0 × 2.0 = 4倍)。

実行例:

👤 ガブリアス、カイリュー、ナットレイのパーティを分析して

🤖 ## パーティ分析 (3体)

   ### メンバー
   - ガブリアス (ドラゴン / じめん) 合計種族値: 600
   - カイリュー (ドラゴン / ひこう) 合計種族値: 600
   - ナットレイ (くさ / はがね) 合計種族値: 489

   ### 攻撃面タイプカバー
   抜群を取れるタイプ (13/18): ...
   ⚠️ カバーできていないタイプ: ノーマル, ひこう, エスパー, ゴースト, あく

   ### チーム弱点分析
   以下のタイプに複数メンバーが弱点を持っています:
     ⚠️ こおり: 2体が弱点
     ⚠️ ドラゴン: 2体が弱点
     ⚠️ フェアリー: 2体が弱点

   ### 改善提案
   - 全員が物理寄りです。特殊アタッカーの追加を検討してください。

check_type_matchup — タイプ相性チェック

何ができるか: 攻撃タイプと防御タイプ(複合対応)の倍率を即座に計算します。

コード:

@mcp.tool()
async def check_type_matchup(
    attacking_type: str,
    defending_types: list[str],
) -> str:
    """タイプ相性の倍率を計算します。"""
    # 複合タイプは各タイプの倍率を掛け算
    multiplier = 1.0
    for d in resolved_defs:
        multiplier *= TYPE_CHART.get(atk, {}).get(d, 1.0)

日本語タイプ名(「ほのお」「みず」)にも対応しています。

実行例:

👤 いわタイプの技でリザードン(ほのお/ひこう)に攻撃したら?

🤖 いわ → ほのお / ひこう: x4.0 (4倍弱点!)

suggest_selection — 選出提案

何ができるか: 自分と相手のパーティを入力すると、タイプ相性ベースで選出3体(ダブルなら4体)と注意すべき相手ポケモンを提案します。選出画面の制限時間内に使う想定の高速ツールです。

コード(スコアリングロジック):

for i, me in enumerate(my_data):
    # 攻撃面: 相手全員に対するタイプ相性スコア
    atk_score = sum(_matchup_score(me["types"], opp["types"]) for opp in opp_data)
    # 防御面: 相手から受けるダメージリスク
    def_penalty = sum(_defensive_score(me["types"], opp["types"]) for opp in opp_data)
    # 総合スコア = 攻撃貢献 - 被ダメリスク(防御の重みは0.5)
    total = atk_score - def_penalty * 0.5

上位からタイプ被りを考慮しつつ3体を選び、相手の脅威度ランキングと対処法も合わせて出力します。

実行例:

👤 自分: ガブリアス, ロトム, ナットレイ, ウルガモス, カイリュー, ドヒドイデ
   相手: ミミッキュ, ドラパルト, ドリュウズ, ガブリアス, モロバレル, ハッサム
   を選出提案して

🤖 ## 選出提案 (シングル)

   ### 選出 (3体)
     1. ウルガモス (むし/ほのお) → 有利: ドリュウズ, モロバレル, ハッサム
     2. カイリュー (ドラゴン/ひこう) → 有利: ドラパルト, ガブリアス, モロバレル
     3. ガブリアス (ドラゴン/じめん) → 有利: ドラパルト, ドリュウズ, ガブリアス

   ### 相手の注意ポケモン
     ⚠️ ガブリアス (ドラゴン/じめん) ← 対処: カイリュー, ガブリアス
     ⚠️ ドラパルト (ドラゴン/ゴースト) ← 対処: カイリュー, ガブリアス

   ### 初手: ウルガモス

共通の技術ポイント

ツール横断で使っている工夫を紹介します。

PokeAPI への非同期アクセスとキャッシュ

_cache: dict[str, dict] = {}
_client: httpx.AsyncClient | None = None

async def _get(path: str) -> dict:
    """PokeAPI への GET リクエスト(インメモリキャッシュ付き)"""
    if path in _cache:
        return _cache[path]
    client = _get_client()
    resp = await client.get(f"{POKEAPI_BASE}/{path}")
    resp.raise_for_status()
    data = resp.json()
    _cache[path] = data
    return data
  • インメモリキャッシュ: 同じリクエストは 2 回目以降即座に返る
  • コネクションプール: httpx の AsyncClient を共有して接続オーバーヘッドを削減
  • HTTP/2: PokeAPI が対応しているので有効化

並列データ取得

パーティ分析(6体)や選出提案(最大12体)では asyncio.gather で並列化。6体直列だと数秒かかるところが体感 1 秒以下になります。

async def _fetch_pokemon_full_batch(queries: list[str]):
    ids = await asyncio.gather(*(pokeapi.resolve_pokemon_id(q) for q in queries))
    pairs = await asyncio.gather(*(pokeapi.get_pokemon_and_species(str(pid)) for pid in ids))

日本語名検索

PokeAPI は英語名ベースですが、日本語名→図鑑番号のマッピングを初回に構築してキャッシュしています。

セットアップ方法

インストール

cd mcp
uv sync

VS Code(GitHub Copilot)で使う

.vscode/mcp.json に以下を追加します。

{
  "servers": {
    "pokemon-champions": {
      "type": "stdio",
      "command": "uv",
      "args": ["run", "--directory", "/path/to/mcp", "python", "server.py"]
    }
  }
}

あとは Copilot Chat で「ガブリアスの種族値を教えて」「このパーティのバランスを分析して」と聞くだけです。

使ってみた例

種族値検索:
「ピカチュウの種族値を教えて」→ 種族値バーつきで表示

育成提案:
「ガブリアスの育成提案をして」→ 物理アタッカーと自動判定、ようき AS252 を提案

パーティ分析:
「ガブリアス、カイリュー、ナットレイのパーティを分析して」→ タイプカバー・弱点集中・改善提案を表示

選出提案:
「相手のパーティは〜なので選出を提案して」→ 3体の選出と注意ポケモンを提示

ハマったポイント

PokeAPI の日本語名検索が遅い

初回は全ポケモンの種族データを取得して日本語名マッピングを構築するため、初回起動時にかなり時間がかかります。将来的にはローカルにキャッシュファイルを持たせる改善を検討中です。

タイプ相性の複合計算

2 タイプのポケモンへの相性計算は、各タイプへの倍率を掛け算する必要があります。例えばリザードン(ほのお/ひこう)にいわタイプの技は 2.0 × 2.0 = 4.0 倍です。最初うっかり足し算にしていました。

まとめ

  • MCP サーバーは意外と簡単に作れる(FastMCP + デコレータだけ)
  • PokeAPI + 非同期処理 + キャッシュで実用的な速度を実現
  • GitHub Copilot と連携すると、自然言語でポケモンのデータ検索・育成相談ができる

MCPはAIに「自分専用のスキル」を追加できる仕組みなので、ポケモンに限らず色々なドメインで活用できると思います。

0
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
0
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?