Skip to main content
Glama
README.md
# ue5-gameplay-mcp

実行中のUnreal Engine 5のゲームを**プレイ**するMCPサーバーです。
仮想ゲームパッド、キーボード、マウスの入力を受け付け、スクリーンキャプチャ、ログ行、UMGステータスを出力します。

これはエンジンプラグインではなく、クライアントです。2つのプラグインがすでにエンジン内の処理を担い、それぞれ独自のポートを保持しています。
このサーバーはその両方にダイヤルインし、1つのツールインターフェースとして提供します。

| プラグイン | ポート | 提供機能 |
|---|---|---|
| RemoteConsole2 | 10101 | `IInputDevice`によるゲームパッド/キーボード/マウスのインジェクション、コンソールコマンド、UMGのダンプ/クリック/フォーカス、ライブログストリーム、構造化されたゲームステート |
| RemoteCapturePlugin | 10102 | JPEG/PNGスクリーンキャプチャ(PIEおよびパッケージ化されたビルドで動作)`ue5_gamecapture_mcp` 付属 |

両プラグインには、このサーバーのために少量のC++コードが追加されました。
これらは独立性を保ち、独自のプロトコルを維持しています。追加部分は後方互換性があるため、古いクライアントでも再ビルドされたゲームと通信可能です:

* `FImageMeta.SourceSize` — ゲームのバックバッファサイズ。構造体のサイズを24バイトに保つため、以前は予約語だった領域にパックされています。これにより、クライアントはダウンスケールされたキャプチャ上の特定の点をウィンドウのピクセルにマッピングし直すことができます。
* `CMD_GET_GAME_STATE` (520) および `IRemoteGameStateProvider` — 詳細は後述します。

## セットアップ

```bash
cd ue5_gameplay_mcp
uv sync
```

**MCP Python SDK v2** (`mcp.server.MCPServer`) が必要です。

## 実行方法

まずゲームを起動します。
このプロジェクトでは、Gameターゲットがクックされていないプロジェクトでは即座に終了してしまうため、スタンドアロンゲームは*エディタ*のバイナリから実行されます:

```bash
"C:/Program Files/Epic Games/UE_5.8/Engine/Binaries/Win64/UnrealEditor.exe" "<PATH>/MyProject.uproject" -game -windowed -resx=1280 -resy=720 -log -nosplash
```

次にサーバーを登録します。
プロジェクトルートにある`.mcp.json`がすでにこれを行っているため、Claude Codeは自動的にこれを認識します。
手動で行う場合と同等のコマンドは以下の通りです:

```bash
claude mcp add ue5-gameplay -- uv run --directory <PATH>/ue5_gameplay_mcp -m ue5_gameplay_mcp
```

サーバーは遅延接続(レイジーコネクト)を行うため、実際の起動順序は重要ではありません。
ゲームより先に起動した場合でも、最初のツール呼び出し時に接続が行われます。

オプション: `--host`, `--console-port`, `--capture-port`, `--format`, `--quality`, `--max-size`, `--grid-step`, `--transport streamable-http --mcp-port 14102`。

## ツール

**セッション** — `game_connect`, `game_status`, `game_reset_input`

**オブザベーション(観察)** — `game_observe`, `game_state`, `game_log`, `game_wait_for_log`

**アクション** — `game_pad`, `game_pad_sequence`, `game_key`, `game_mouse`, `game_console`, `game_time_scale`

**UMG** — `game_ui_dump`, `game_ui_click`, `game_ui_focus`

### APIの設計とその理由

エージェントのラウンドトリップには数秒かかりますが、ゲームは60Hzで動作します。フレーム単位でのアクションは非現実的であるため、以下のようになっています:

* **すべてのアクションツールは`duration`(継続時間)を受け取り**、押す/押し続ける/離すという一連の動作をローカル環境で通信速度に合わせて実行します。1回のラウンドトリップは1フレームではなく、1つの意図(インテント)を伝達します。
* **アクションツールはデフォルトで観察(Observe)を行います。** `game_pad(ly=1.0, duration=0.5)`は前進し、その結果のフレームを返します。これにより、行動と確認を別々に行う場合の半分のラウンドトリップで済みます。
* **`game_pad_sequence`** は、入力間の確認よりも入力のタイミングが重要な場合に、コンボ全体を1回の呼び出しにまとめます。
* **`game_time_scale(0.2)`** は、瞬間の精度が必要な場合にゲーム内時間を稼ぎます。
* **`hold=True`** は、エージェントが思考している間もキャラクターが動き続けるように、ターンの間も入力を適用したままにします。`game_reset_input`でこれをクリアします。

### ピクセルではなく数値

`game_state`は、レベル、ワールドタイム、ポーズ/タイムダイレーション、プレイヤーポーンのトランスフォーム、速度、移動モード、カメラ、そして最も近いアクタの距離と**正規化された画面位置**を返します。
これは`game_mouse`が受け取るのと同じ0~1の座標であるため、ステータスレポート内で見つけた対象に即座に狙いを定めることができます。
この処理コストは画像の処理コストのほんの一部であり、HUDの数値を読み違えることもありません。

`game_observe(state=True)`はこれを観察に組み込み、`game_pad(..., state=True)`はアクションに組み込むため、移動と確認は依然として1回のラウンドトリップで済みます。

実際のマップでは、最も近いアクタのほとんどが背景オブジェクトであるため、レポートには`class_counts`(半径内のすべてのオブジェクトの調査結果)も含まれます。これを一度読み取り、`class_filter="Enemy"`で絞り込みます。

#### ゲーム独自の数値の追加

組み込みのレポートにはゲーム側のコードは必要ありません。ヘルス、スコア、クエストフラグなど、そのプロジェクトのみが知る情報については、任意のアクタに`IRemoteGameStateProvider`(`Plugins/RemoteConsole2/Source/RemoteConsole2/RemoteGameState.h`)を実装し、JSONオブジェクトの文字列を返すようにします:

```cpp
FString AMyGameMode::GetRemoteGameState_Implementation()
{
    return FString::Printf( TEXT("{\"score\":%d,\"wave\":%d}"), Score, Wave );
}
```

これは`BlueprintNativeEvent`であるため、Blueprintのみのプロジェクトでもオーバーライド可能です。
返された値はすべて、アクタ名をキーとして`custom`の下に格納されます。
プロバイダは距離フィルタに関係なく収集されるため、原点に配置されたスコアキーパーもレポートを送信します。
有効なJSONではないテキストは破棄されず、文字列としてそのままパススルーされるため、立ち上げ時の簡単な`Printf`によるデバッグでも有用です。

### メニューを操作する3つの方法(推奨順)

1. `game_ui_dump` + `game_ui_click` — 正確かつ高速ですが、UMG経由で登録されたウィジェットしか認識しません。カスタムのSlate UIを持つゲームでは何も返されませんが、ハングアップする代わりにツールがその旨を伝えます。
2. **パッドナビゲーション** — `game_pad(buttons=["DOWN"])`, `game_pad(buttons=["A"])`。ほぼすべてのゲームで機能します。
3. **ルック&クリック** — `game_observe(grid=True)`は、ラベル付きの0~1の座標グリッドをオーバーレイ表示します。画像からターゲットを読み取り、同じ数値を`game_mouse(x=..., y=...)`に渡します。解像度に依存せず、UIの構築方法に関係なく機能します。

### 規則(コンベンション)

* スティックはUEの仕様に従います: `ly=+1`が前進です。(通信プロトコルではY軸が反転しますが、ここではそれを元に戻しているため、ツールAPIはゲーム独自の軸マッピングの意味と一致します)。
* マウス座標は左上を原点として0~1に正規化されており、ゲームの実際のバックバッファサイズを使用してピクセルに変換されます。キャプチャはリサイズ後の寸法を報告するため、このサイズは別途プローブされます。
* `game_observe`は前回の観察以降の新しいログ行のみを返すため、長時間のセッションでも同じ出力が再送されることはありません。

## 既知の問題点・未完成な部分

* **エディタバイナリでゲームをホストしている場合、コンソールコマンドはPythonを経由してルーティングされます。** `FGameAccessAPI::ExecConsoleCommand`は`IConsoleCommandExecutor`の実装`[0]`にディスパッチしますが、エディタのPythonプラグインがロードされている場合、そのスロットはCmdではなくPythonになります。そのため、単なる`stat fps`が`SyntaxError`として返されます。サーバーは初回使用時に一度プローブを行い、これを検出した場合はコマンドを`unreal.SystemLibrary.execute_console_command`でラップします。パッケージ化されたビルドにはPythonエグゼキュータがないため、この回避策は不要です。`game_console(via="cmd")`でオーバーライド可能です。
* **画面のクリックは、キャプチャがゲームウィンドウ全体を満たしていることを前提としています。** `-game -windowed`の場合はこれが当てはまります。レターボックス化されたフルスクリーンモードの場合、報告されるソースサイズ内に黒帯が含まれることになり、マッピングがずれます。
* **`game_state`は、呼び出されるたびにレベル内のすべてのアクタを走査します。** 通常のマップの規模であれば問題ありませんが、ストリーミングされるオープンワールドでは`TActorIterator`の代わりに空間クエリが必要になるでしょう。

## テスト

```bash
uv run test/smoke_test.py
```
ゲームと直接通信し、`smoke_*.jpg`を書き出します。これにより、キャプチャやグリッドのオーバーレイを目視で確認できます。

```bash
uv run test/mcp_client_test.py
```
標準入出力(stdio)経由でサーバーを実際のMCPクライアントとして起動し、エラーパスを含めてすべてのツールを実行・テストします。

TDQS

A3.8/5.0

Scored across 16 tools

Disambiguation5/5

Each tool targets a distinct interaction mode: connection, observation, state, input (pad/key/mouse/UI), logging, console, time scaling, and focus. Even overlapping actions like game_mouse vs game_ui_click are explicitly differentiated by target (screen coordinates vs widget tree) and fallback behavior.

Naming Consistency5/5

All tools follow the predictable 'game_' prefix with an action verb (connect, log, observe, state, pad, key, mouse, console). The sub-group 'game_ui_*' maintains consistency with a clear sub-domain. No mixed conventions or vague verbs.

Tool Count4/5

16 tools is slightly above the ideal 3-15 range but fully justified for a comprehensive UE5 gameplay API covering connection, observation, multiple input methods, UI interaction, and convenience utilities. Each tool serves a clear practical purpose.

Completeness5/5

The surface covers the full cycle: connect, observe (screenshot/state), act (pad/key/mouse/UI), read logs, wait for conditions, adjust time scale, and reset input. No obvious dead ends or missing operations for controlling a game; even edge cases like UI failure have explicit fallback guidance.

Maintenance

ActivityMaintained
ResponsivenessNo issues