Claude Media
Claude CodeでMinecraftのPaperプラグインを作る — ビルドとサーバー起動を検証ループに

Claude CodeでMinecraftのPaperプラグインを作る — ビルドとサーバー起動を検証ループに

Paperプラグインを、Gradleビルドとローカルサーバーのログで合否が読める形にして、Claude Codeに書かせて直させる手順。plugin.ymlの落とし穴と権限設定の例つきです。

Claude CodeでMinecraftのPaperプラグインを作る — ビルドとサーバー起動を検証ループに

Paperは、Minecraftサーバーのプラグインを動かすためのサーバーソフトウェアです。プラグインはJavaで書き、Gradleでビルドして、Paperサーバーのpluginsディレクトリに置いて動かします。Claude Codeに任せやすいのは、この流れのうち「ビルドが通るか」と「サーバーがプラグインを読み込めたか」を文字で確かめられる部分です。

この記事では、PaperのProject setupガイドに沿って、ビルドとサーバー起動を検証の手段にする反復の組み方を説明します。ゲーム内で実際に動かして確かめる部分は、手元に残ります。

Claude Codeに渡すPaperプロジェクトの前提

PaperのProject setupガイドは、IntelliJ IDEAを前提にGradleのKotlin DSLで説明しています。Mavenも使えますが、Maven側は非推奨の扱いです。Claude Codeに作らせるときも、Gradle Kotlin DSLで揃えると、公式の例とそのまま突き合わせられます。

build.gradle.kts にはPaperのリポジトリと依存を書きます。ガイドの例は次のとおりです。

repositories {
  maven(url = "https://repo.papermc.io/repository/maven-public/") {
    name = "papermc"
  }
}
 
dependencies {
  compileOnly("io.papermc.paper:paper-api:26.3.build.+")
}
 
java {
  toolchain.languageVersion.set(JavaLanguageVersion.of(25))
}

見るべき点は3つあります。

  • compileOnly: Paper本体はサーバー側にあるので、プラグインのJARには含めません
  • 26.3.build.+: 末尾の + を、{VERSION}.build.25-stable のようなビルド識別子に置き換えると特定ビルドを指せます
  • Javaのツールチェーン: 例ではJava 25です。Minecraft 26.1より前(1.21.11以下)では {VERSION}-R0.1-SNAPSHOT という別の版表記だったと説明しています

ソースの置き場所は src/main/java と src/main/resources です。パッケージは、自分のドメインを逆にした名前(com.example など)にプロジェクト名を続けます。ドメインがなければGitHubのユーザー名を使う、という案内もあります。

メインクラスの最小例

ガイドのメインクラスは、JavaPlugin を継承し、onEnable でイベントリスナーを登録する形です。参加したプレイヤーに挨拶を送ります。

package com.example.paperplugin;
 
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.player.PlayerJoinEvent;
import org.bukkit.plugin.java.JavaPlugin;
 
public final class ExamplePlugin extends JavaPlugin implements Listener {
    @Override
    public void onEnable() {
        this.getServer().getPluginManager().registerEvents(this, this);
    }
 
    @EventHandler
    public void onPlayerJoin(PlayerJoinEvent event) {
        event.getPlayer().sendPlainMessage("Hello, " + event.getPlayer().getName() + "!");
    }
}

メインクラスの名前は Main にしないよう勧められています。プラグイン名に合わせた ExamplePlugin のような名前にします。これはClaude Codeに最初に渡す指示として、CLAUDE.mdに書いておく価値があります。

paperweightが要るのはサーバー内部に触るときだけ

paperweightはPaperのビルド用ツール群の名前で、そのうちpaperweight-userdevは、サーバー内部のコード(NMS)に開発中から触るためのGradleプラグインです。

イベントやコマンドなどのAPIだけで書けるプラグインは、上の paper-api への依存で足ります。userdevを入れるときは、Paper APIへの依存を外します。dev bundleの依存にAPIが含まれるためです。

plugins {
  id("io.papermc.paperweight.userdev") version "2.0.0-beta.24"
}
 
dependencies {
  paperweight.paperDevBundle("26.3.build.+")
}

もう1点、バージョンによる違いがあります。Minecraft 26.1以降、Paperは難読化されたプラグインをサポートしません。そのためdev bundleでの再難読化(reobfJar)は26.1以降で動きません。1.21.11以下の記述を見つけたClaude Codeが reobfJar を足してきたら、対象のバージョンを確かめてください。

パッチ適用の失敗で paperweightUserdevSetup が落ちるときは、paperweight.javaLauncher に別のJavaバージョンを指定する方法がuserdevのページに載っています。

plugin.ymlはサーバーの読み込み時に効く

src/main/resources/plugin.yml は、ビルドが通っても間違いに気づけない場所です。サーバーが起動してはじめて、読み込みの成否がログに出ます。plugin.ymlのページの例は次のとおりです。

name: Example-Plugin
version: 1.0.0
main: com.example.paperplugin.ExamplePlugin
description: An example plugin
author: PaperMC
api-version: '26.3'

必須項目は name、version、main の3つです。検証のときに見る項目を表にまとめます。

項目起きること
main起きることメインクラスをパッケージ名つきで書く。ずれるとクラスを見つけられない
api-version起きること省略するとレガシーなプラグインとして読み込まれ、警告がコンソールに出る
api-version起きること指定より古いサーバーは、プラグインの読み込みを拒否する
depend起きること依存先のプラグインが見つからないと、自分のプラグインは読み込まれない
prefix起きること設定すると、ログ上のプラグイン名の代わりに表示される

api-version の有効な範囲は、1.13から26.3と書かれています。prefix を設定するとログでの見え方が変わるので、ログを読んで合否を判定する運用では、prefix を勝手に足さないよう指示しておくと混乱しません。

ビルドとサーバー起動を回す

ここからが検証ループの本体です。Project setupガイドは、ビルド後の動作確認のために、Run-Task Gradleプラグインを紹介しています。Paperサーバーを自動でダウンロードして起動してくれます。

plugins {
  id("xyz.jpenilla.run-paper") version "VERSION"
}
 
tasks {
  runServer {
    minecraftVersion("1.21.8")
  }
}

VERSION には、Gradleプラグインポータルの最新版を入れます。README上の必須設定は minecraftVersion だけで、プラグインのJAR(shadowJarがあればそちら)は自動で使われます。ここに書かれた 1.21.8 はREADMEの例なので、実際に動かすMinecraftのバージョンは自分のプロジェクトの対象に合わせてください。

これでループは次の2段になります。

手順

Claude Codeに回させる検証の2段

  1. 1

    ビルドを通す

    ./gradlew build を実行し、コンパイルエラーを読んで直します。JARの出力先は build/libs です。

  2. 2

    サーバーで読み込ませる

    ./gradlew runServer を実行し、ログにプラグインの有効化と警告が出ていないかを読みます。

サーバーは止まらないコマンドとして扱う

runServer はサーバーを起動したまま待ち続けるため、前面で実行するとBashツールがそこで止まったままになります。Claude Codeにはこの種のコマンドのための仕組みが2つあります。

  • バックグラウンド実行: Bashツールの run_in_background: true で起動すれば、Claudeは作業を続けられます。起動したタスクは /tasks で一覧と停止ができます
  • Monitorツール: バックグラウンドのコマンドを走らせ、出力の各行をClaudeに返します。ログを見張って、エラーが出たところで反応させる使い方です

Monitorには期限があります。既定は5分で、最大30分です。サーバーの起動確認なら足りる長さですが、長時間のテストプレイには向きません。Monitorが実行するコマンドには、Bashと同じ権限ルールが適用されます。

権限は検証に必要な範囲に絞る

ビルドとサーバー起動を自動で回すなら、コマンドごとに許可を与えます。.claude/settings.json に書く一例を示します。

{
  "permissions": {
    "allow": [
      "Bash(./gradlew build *)",
      "Bash(./gradlew runServer *)",
      "Bash(./gradlew --stop)"
    ],
    "deny": [
      "Bash(./gradlew publish *)"
    ]
  }
}

権限ルールの注意点がいくつかあります。評価は拒否、確認、許可の順で、拒否に当たれば許可で上書きできません。末尾の * の前にスペースを置くと、引数なしの ./gradlew build にも一致します。また、./gradlew build && ./gradlew runServer のように複合したコマンドは、各サブコマンドが別々に判定されます。publish のような公開系のタスクを拒否しておくのは、サーバー検証とは別の安全弁です。

CLAUDE.mdに書いておく運用ルール

検証の手順は、プロジェクトのCLAUDE.mdに条件つきで書くと、毎回の指示が要らなくなります。書き方の一例です。

## Paperプラグイン
- メインクラス名は `Main` にしない。プラグイン名に合わせる
- 変更後は `./gradlew build` を実行し、通らなければ直す
- build が通ったら `./gradlew runServer` をバックグラウンドで起動し、ログを読む
- ログに警告やプラグインの読み込みエラーがあれば、plugin.yml を先に疑う
- plugin.yml の `api-version` と `main` は、変更のたびに build.gradle.kts と突き合わせる
- 確認が終わったら起動したサーバーを止める

Gradleの出力を絞るコツはClaude CodeでGradleビルドを回すで扱っています。ビルドとサーバー起動の2段を決まった手順にしたければ、Claude Code Hooks完全ガイドの仕組みで自動化する方法もあります。

同じ型の別エコシステム

Gradleのタスクで「構成検査 → ビルド → 実機起動」を段階にするのは、JetBrainsプラグインでも同じ発想です。違いは実機の扱いで、IDEの画面を開くか、ログを出すサーバーを起動するかで、後者はClaude Codeが出力から判断できる範囲が広くなります。IDE側の手順はClaude CodeでJetBrainsプラグインを開発する記事にあります。

ログで確かめきれない部分と、デバッグ

サーバーのログで見えるのは、読み込みの成否と、プラグイン自身が出した出力です。イベントの挙動を確かめるには、Debuggingのページが勧めるとおり、System.out.println よりもプラグインのロガー(getSLF4JLogger())を使います。どのプラグインのログか分かりやすいためです。

ログを足すたびに再ビルドとサーバー再起動が必要になるのが、このやり方の弱点です。一時停止して変数を見たいときは、リモートデバッガを使います。runServer を使っている場合は、同ページの案内どおり jvmArgs("-Ddisable.watchdog=true") を足します。ブレークポイントで止めている間、サーバーはtickを進めません。既定のウォッチドッグは、10秒tickがないとスレッドダンプを出し、60秒で止めるためです。開発用のサーバーに限って使い、本番では無効にしません。

また、参加したプレイヤーを写す動作確認(PlayerJoinEvent の挨拶など)は、ゲームクライアントで接続しないと確認できません。Vanillaクライアントは30秒間サーバーから何も届かないと切断するので、デバッガで長く止めると接続も切れます。この部分は人が確認する前提で、Claude Codeには「ログ上でプラグインが有効になった」ところまでを完了の条件として渡すのが現実的です。

起動後にClaude Codeへ読ませるログの切り分け

起動ログを読ませるときは、「何を見て成功とするか」を先に決めておきます。プラグインが有効になったことを示す行が出る、api-version の警告が出ない、依存先のプラグインが見つからない旨のメッセージが出ない、の3点が目安です。どれかが外れたら、まず plugin.yml を疑い、次にメインクラスの完全修飾名を見直します。コードのバグを疑うのはそのあとです。ビルドが通っていてもサーバーが読み込めない場合の原因は、多くがこの設定側にあるためです。

まとめ

Paperプラグインは、./gradlew build の成否と、runServer のログの2つで、Claude Codeが合否を読める状態にできます。APIだけで足りるなら paper-api の compileOnly で十分で、paperweight-userdevはサーバー内部に触る場合にだけ加えます。ゲーム内の動作確認は人の側に残る、と割り切ると、Claude Codeに任せる範囲が決まります。

この記事を共有:XはてブLinkedIn