コンテンツにスキップ

MCP サーバ

コンパイラ本体を AI クライアントから呼べるようにするサーバです。パッケージに同梱されていて、クライアントが設定に従って起動します。

Unity のメニューの TsukimiCode から Tsukimi MCP を開きます。節は 5 つで、上から順に進める構成です。

節ボタン役割
Serverなし同梱サーバの起動コマンドと、サーバ DLL の存在確認
Client ConfigurationConfigure使うクライアントの設定ファイルへの書き込み
GroundingInstall Skill・Update AGENTS.mdAI が参照する手順書のインストール(Skills)
Final CheckInstall Check・Remove作業完了の報告時にコンパイルを検査するフックの設置
How to set this upなしここまでの手順の英語版

開いた直後の Tsukimi MCP。右側はまだどれも not installed

Start commandクライアントが実際に実行するコマンドがそのまま出ます
Status が available同梱のサーバが見つかっています
Status が not foundサーバの DLL が見つかっていません。パッケージを導入し直すと戻ります

設定ファイルもキーもクライアントごとに異なるため、8 種類のクライアントそれぞれに合わせた設定を生成します。

クライアント設定ファイルキー書式補足
Claude Code.mcp.jsonmcpServersJSON
GitHub Copilot CLI.mcp.jsonmcpServersJSONClaude Code と同じファイル
Visual Studio.mcp.jsonserversJSON同じファイルの別のキー
VS Code.vscode/mcp.jsonserversJSON
Cursor.cursor/mcp.jsonmcpServersJSON
Roo Code.roo/mcp.jsonmcpServersJSON
Gemini CLI.gemini/settings.jsonmcpServersJSONほかの設定と同じファイル
Codex CLI.codex/config.tomlmcp_serversTOMLプロジェクトの信頼が別に必要

同じ節のドロップダウンでクライアントを選ぶと、貼り付ける内容がそのまま出ます(2 つのパスにはそのプロジェクトでの絶対パスが入ります)。

{
"mcpServers": {
"tsukimi": {
"command": "dotnet",
"args": ["<パッケージの場所>/Server~/TsukimiCode.Mcp.dll", "<プロジェクトの場所>"]
}
}
}

Codex CLI だけ TOML です。

[mcp_servers.tsukimi]
command = "dotnet"
args = ["<パッケージの場所>/Server~/TsukimiCode.Mcp.dll", "<プロジェクトの場所>"]
引数のプロジェクトの場所サーバが、そのプロジェクトに生成された実行環境の API のデータベースを読むために必要です
省略した場合API の存在確認が行われません

Install Check を押すと、.claude/settings.local.json にフックが登録されます。このフックは、AI が作業完了を報告しようとした時点で、そのセッションで変更された T# のソースを 1 回だけコンパイルします。

場合どうなるか
コンパイルが通らなかった診断を提示して作業の続行を求めます
警告だけが出た戻しません
コンパイルできた何も言いません。ウィンドウの Last check に最後の結果が出ます
検査そのものが動かない止めずに通します。壊れた検査が作業を止め続けないためです
同じ診断が 2 回続いた通します。修正できないと判断して打ち切ります
対象のクライアントClaude Code だけです。フック機能を持つクライアントが他に無いためです
書き込み先共有向けでない設定ファイルです。中身に絶対パスが入るためです
Removeこのフックだけを削除します。同じファイルの他の設定は保持されます

インストールを終えたところ。Grounding と Final Check が up to date になっている

6 つあります。

ツール内容
pingサーバが起動しているかの確認
inspectソースのコンパイルと、診断・解析結果の返却
test[TsukimiTest] を付けたメソッドの実行
diagnosticsエラー番号の一覧
specこの言語の説明の生成
decompileコンパイルされたアセンブリを読める形にする

中心になるのは inspect と test で、ping は接続の確認、diagnostics と spec は inspect が返した内容の解釈、decompile はすでにあるプログラムの確認に使います。

サーバが起動しているかどうかだけを確認します。引数はありません。

"tsukimi-mcp ok (static-analysis MCP; liveness check)"

登録した直後に、設定が有効かどうかを切り分けるために使います。

ソースをコンパイルして、結果を構造化した JSON で返します。

引数内容
sourceソース 1 ファイル分の全文
file診断の位置に使うファイル名。省略すると <inline>
sources複数のファイル。{name, text} の配列。source より優先される
cost命令数とヒープ使用量を測定するかどうか。省略すると測定しません。測定時はビルド時と同じ最適化を実行するため、この呼び出しの所要時間の大半はここに費やされます

1 つのソースが宣言できる具象の Behaviour は 1 つだけなので、次のような形を確認したいときは sources にまとめて渡します。

  • インタフェースを Behaviour で実装する
  • 基底の Behaviour と派生の Behaviour に分ける
  • ある Behaviour から別の Behaviour のメソッドを呼ぶ
  • Behaviour の型でコンポーネントを受け取る

1 つの source に 2 つ書いてエラーになるのは渡し方の誤りであり、言語の制限ではありません(sources で渡した場合、結果は Behaviour ごとに分かれ、最上位ではなく programs の各要素に入ります)。

コンパイルできたときの返り値

Section titled “コンパイルできたときの返り値”

次のソースを source に渡し、cost を真にした結果です。cost を指定しないと、heap と cost の 2 つはキー自体が出力されません。

using UnityEngine;
using Tsukimi;
public class Lamp : TsukimiBehaviour
{
[SerializeField] private Light target;
[UdonSynced] private bool on;
public override void Interact()
{
on = !on;
target.enabled = on;
RequestSerialization();
}
}
{
"schemaVersion": 1,
"compiles": true,
"diagnostics": [],
"facts": {
"heap": { "slots": 11, "limit": 1048576, "remaining": 1048565 },
"cost": {
"instructionCount": 12,
"staticExternCount": 3,
"nsPerExtern": 77,
"nsPerNonExtern": 5.4,
"isStraightLine": true
},
"entries": [ "Interact" ],
"recursiveMethods": [],
"loopCount": 0,
"methods": [
{
"name": "Interact", "isEntry": true, "isRecursive": false,
"params": 0, "locals": 1, "loops": 0, "externs": 3,
"entryName": "_interact"
}
],
"runtimeRisk": [],
"contract": {
"synced": [ { "name": "on", "type": "SystemBoolean", "sync": "none" } ],
"exportedFields": [],
"syncMode": "any"
},
"nullDerefRisks": [
{
"kind": "unassignedFieldDeref",
"severity": "warning",
"origin": "runtime",
"message": "The reference field 'target' (type UnityEngineLight) is shown in the Unity Inspector and is never assigned in code, so its value comes only from there. ...",
"method": "Interact",
"field": "target",
"type": "UnityEngineLight",
"deref": "UnityEngineLight.__set_enabled__SystemBoolean__SystemVoid"
}
]
},
"summary": { "total": 0, "bySeverity": {}, "byOrigin": {} },
"externDb": { "resolved": true, "unknownApiChecks": true, "externCount": 32898 }
}
読みどころ
diagnostics が空・compiles が真このソースは変換できています
nullDerefRisks に 1 件target はコードのどこでも代入されておらず、値は Inspector からしか入りません。割り当てを忘れると実行時に null のままになり、target.enabled への代入でイベント全体が中断されます(Udon には例外処理がないので、ログにも何も出ません)
cost の nsPerExtern と nsPerNonExternextern の呼び出し 1 回と、それ以外の命令 1 つのおおよその所要時間です。命令数そのものより、extern を何回呼ぶかのほうが影響することが、この 2 つの差から読み取れます

Udon に存在しない型を使った例です。

using System.Collections.Generic;
using UnityEngine;
using Tsukimi;
public class Bag : TsukimiBehaviour
{
private List<int> items;
public override void Interact()
{
items = new List<int>();
items.Add(1);
Debug.Log(items.Count);
}
}
{
"schemaVersion": 1,
"compiles": false,
"diagnostics": [
{
"code": "TUKI0102",
"severity": "error",
"message": "Variable type does not exist in Udon: field items has the type SystemCollectionsGenericList",
"explain": "This type does not exist in Udon (outside the extern database).",
"location": { "file": "Bag.cs", "startLine": 7, "startCol": 23,
"endLine": 7, "endCol": 23 },
"fix": "This type cannot be used here (it is outside the usable Udon extern set). ... For lists and dictionaries, use the VRChat data containers DataList / DataDictionary (VRC.SDK3.Data) - generic List<T> / Dictionary<K,V> are not usable.",
"witness": "SystemCollectionsGenericList",
"origin": "subset",
"path": null
},
{
"code": "TUKI0101",
"severity": "error",
"message": "Not exposed to Udon: SystemCollectionsGenericList.__ctor____SystemCollectionsGenericList",
"explain": "This API is not exposed to Udon.",
"location": { "file": "Bag.cs", "startLine": 11, "startCol": 17,
"endLine": 11, "endCol": 17 },
"fix": "This API is not exposed to Udon. Use an API that exists in the Udon extern set (see the `spec` tool, topic \"api\").",
"witness": "SystemCollectionsGenericList.__ctor____SystemCollectionsGenericList",
"origin": "subset",
"path": null
}
],
"facts": null,
"summary": {
"total": 4,
"bySeverity": { "error": 4 },
"byOrigin": { "subset": 4 }
},
"externDb": { "resolved": true, "unknownApiChecks": true, "externCount": 32898 }
}
読みどころ
facts が nullcompiles が偽のときは常に null です。コンパイルできていない以上、ヒープ消費もコストも測定できないためです。facts は存在するのに heap と cost だけが無い場合は別で、cost を指定していないことを意味します
diagnostics が 2 件4 件のうち先頭の 2 件だけを載せています(残りは List のメソッド呼び出しに対する同種の指摘)。全体の件数は summary.total にあります
witnessエラーと判定した根拠となった識別子が入ります。この例では型名 SystemCollectionsGenericList と、見つからなかった extern の署名で、メッセージを読み解かなくても何が無かったのか分かります
キー内容
schemaVersionこの出力形式のバージョン。形式が変わると上がる
compilesUdon のバイトコードまで変換できたか。実行環境で動作することの保証ではない
diagnosticsエラーと警告の配列。番号・重大度・位置・直し方・原因の区分が入る
summarydiagnostics を重大度と原因の区分で数えたもの
facts変換できたときだけ入る。エラーのときは null
facts.heap使うヒープの数と、残り。limit は実行環境がヒープに許す最大の数です。cost を真で渡したときだけ入ります
facts.cost命令数・extern の呼び出し数と、1 回あたりのおおよその所要時間。cost を真で渡したときだけ入ります。入っていないのは測っていないという意味で、費用が無いという意味ではありません
facts.entries実行環境から呼ばれるエントリポイントの名前
facts.methodsメソッドごとの内訳。ローカル変数・ループ・extern の数
facts.contract同期するフィールドと、外へ公開しているフィールドと、その Behaviour 全体の同期方式。方式はフィールドごとの sync とは別の軸です
facts.runtimeRiskコンパイルは通るが実行時に停止しうる箇所
facts.nullDerefRisksnull になりうる参照をそのまま辿っている箇所
externDb実行環境の API のデータベースを参照できたか。参照できた API の総数

diagnostics の各要素には origin が付き、4 つの区分によって修正すべき場所が変わります(runtime はこの表には含まれず、facts.runtimeRisk と facts.nullDerefRisks にのみ付きます)。

区分意味
subsetこの言語が対応していない構文。書き方を変えればコンパイルできる
environment実行環境に存在しない。書き方を変えてもコンパイルできない
costコンパイルはできるが、コストが上限を超える
toolコンパイラか静的解析の内部エラー。修正すべき場所は書いたコードではない

tool だけは性質が異なり、コンパイラ側の失敗を示すため、コードを書き直しても結果は変わりません。

ソースをコンパイルして、[TsukimiTest] を付けたメソッドを Unity なしで実行します。

受け取るのは 3 つです。

引数内容
sourcesまとめてコンパイルするファイル。inspect と同じ形。テストは 名前.Tests.cs に書いて、確認したい Behaviour と一緒に渡します(書き方は テスト)
projectPathUnity プロジェクトの場所。省略可。渡すと同じ内容がそのプロジェクトの中にも書かれ、Unity へ戻ってウィンドウを開き直すとそこに出ます
fast素の C# として実行するかどうか。省略すると既定の道で実行します。速いので書いている途中の確認に向きますが、答える問いが違います——書いた規則が正しいかを確認し、コンパイルした形が正しいかは確認しません(テスト)
{
"schemaVersion": 1,
"origin": "mcp",
"ranAt": "2026-08-13T05:57:52.7817272Z",
"passed": 2,
"failed": 1,
"notRunnable": 0,
"allPassed": false,
"cases": [
{
"name": "OneBumpAddsOne",
"source": "Counter.Tests.cs",
"outcome": "passed"
},
{
"name": "TwoBumpsMakeTwo",
"source": "Counter.Tests.cs",
"outcome": "passed"
},
{
"name": "DeliberatelyWrong",
"source": "Counter.Tests.cs",
"expected": "99",
"actual": "1",
"location": "32:9",
"outcome": "assertFailed"
}
]
}
キー読み方
outcome4 種類あります。passed は成功、assertFailed は Assert が成立しなかったもの、halted は実行環境でも同じ箇所で停止するもの、notRunnable は実行そのものができなかったものです。notRunnable はテストの失敗ではなく、コードを書き直しても結果は変わらないため、まず detail を読んでください
allPassedテストが 1 本も見つからなかった場合と、1 本でも実行不可があった場合も偽になります。どちらも成功と誤認されないようにするためです
originどちらの道で実行したかが値で入ります。mcp は既定の道、mcp-fast は素の C# の道、editor は Unity のウィンドウからの実行です。失敗を読む前にここを見ます
trace失敗したときだけ入ります。実行中に記録された事象が時系列で並びます

エラー番号の一覧を返します(引数はありません)。inspect が返した番号の意味を参照するために使います。

{
"code": "TUKI0101",
"defaultSeverity": "error",
"en": "This API is not exposed to Udon.",
"fixTemplate": "This API is not exposed to Udon. Use an API that exists in the Udon extern set ...",
"origin": "subset"
}

上は配列の 1 要素で、inspect が返す個々の診断と違って位置が入りません(番号そのものの定義を参照する表だからです)。

この言語の説明を Markdown で返します(topic で選べる話題は 4 つ)。

話題内容根拠
subset対応している形の規則。エラー番号ごとに、エラーになる条件と直し方が並ぶ診断の一覧から自動生成。コンパイラの版が変われば内容も変わる
api実行環境が持っている型の名前の一覧。名前を参照できることと、その API が実際に使えることは別で、使えるかどうかは inspect に通して確認するextern データベースから自動生成。同上
pitfalls通常の C# と挙動が違うところ。null の発生源、イベントが黙って終わるケースなど手書きの解説。コンパイラの定義からは自動生成できない内容
builtinsこの言語固有の組み込み要素。属性・基底クラス・Assert・カーネル API。実行環境の API ではないので api には出ないコンパイラが持つ型定義から自動生成
(省略)全体の見取り図と話題の索引extern データベースを参照できているかどうかも書かれる。参照できていないと未知の API と未知の型の検査(TUKI0101・TUKI0102)が実行されないので、その状態でエラーになった形は、書き方ではなくデータベースが無いことが理由かもしれない
(知らない話題)使える話題を並べたメッセージ
  • compiles が真でも、実行環境で動作するとは限りません。バイトコードまで変換できたことだけを表します
  • runtimeRisk と nullDerefRisks が空でも安全ではありません。この検出は意図的に狭く、null を返しうる呼び出しの直後に連ねた形しか追っていません。いったんローカル変数へ代入すると、そこで追跡が切れます。コンポーネントの取得や実行環境から取る値は、結果の内容にかかわらず自分で確認してください
  • externDb.resolved が偽のときは、未知の API と未知の型の検査が実行されていません。この状態では、データベースを参照できないせいでエラーになっている形が混ざります
  • 重大度が info の診断は止める理由になりません。compiles が真でも出ます

コンパイルされた Udon のアセンブリを、C# に似た読める形にします。すでにワールドに置かれているプログラムの確認に使い、引数は 2 つです。

引数内容
uasmアセンブリのテキスト
fileアセンブリファイルのパス。uasm を省略したときだけ読まれます
{
"schemaVersion": 1,
"file": "Lamp.uasm",
"source": "public class Lamp\n{\n ...\n}",
"fidelity": {
"isOriginalSource": false,
"isCompileChecked": false,
"receiverIsInferred": true,
"note": "This is a readable rendering of the assembly you passed in. ..."
},
"externDb": { "resolved": true, "externCount": 32898 },
"warnings": [],
"facts": {
"heap": { "slots": 11, "limit": 1048576, "remaining": 1048565 },
"cost": { "instructionCount": 42, "staticExternCount": 6,
"nsPerExtern": 77, "nsPerNonExtern": 5.4 },
"entries": ["_interact"],
"externs": ["UnityEngineGameObject.__SetActive__SystemBoolean__SystemVoid"],
"loopCount": 0
}
}
キー読み方
source可読化した結果です。元のソースではありません。ローカル変数名・クラス名・ブロック構造は、アセンブリだけから再構成したものです(アセンブリはそれらを持っていません)
fidelityこのツールの出力の性質を示す固定値です。入力ごとに判定するものではなく、常に同じ値です。source より先に読みます
externDb呼び出しの受け手を、データベースを参照して決めたか、周囲の命令列から推定したかが分かります。推定は間違うことがあり、間違った結果は普通のコードに見えます
warnings結果が信用できない箇所です。receiverNotResolved は、ドットの左側に置いた式がその呼び出しのレシーバとして成立しないもの、notRaisedToStatements は、スタックの操作のまま残した行です。空でも、すべてが正しく出たという意味にはなりません
facts渡したアセンブリから測った値です。heap は inspect と同じ数え方で、cost は命令数と静的な外部呼び出しの数です
facts.entriesアセンブリに定義されたエントリポイントの名前です。inspect の同じキーが C# の名前(Interact)を返すのに対し、こちらは実行環境が呼ぶ名前(_interact)を返します
facts.cost.isStraightLineありません。アセンブリからはループが見えても再帰は見えないため、loopCount が数えるのは後ろ向きのジャンプだけです
自分で書いたソースinspect を使います。このツールの出力を inspect にかけ直す使い方は想定していません

ping を呼ぶと、次の 1 行が返ります。

"tsukimi-mcp ok (static-analysis MCP; liveness check)"

クライアント側にも、接続しているサーバの一覧があります。

クライアントから見たサーバの一覧

見えるツール6 つ(ping・inspect・test・diagnostics・spec・decompile)
1 つも見えないとき設定を書き込む前にクライアントを起動したか、プロジェクトの外で起動しています

短いソースを 1 つ inspect にかけると、externDb が返ります。

"externDb": { "resolved": true, "unknownApiChecks": true, "externCount": 32898 }
resolved真なら参照できています。偽の間は、実在する API も未知として報告されます
偽になる条件Unity でこのプロジェクトをまだ一度も開いていないときです
externCount参照できた API の数。導入した SDK の版で変わるので、手元の値が上と違っていても構いません

シーンへの配置・Inspector での参照の割り当て・Play モードでの確認はこのサーバの対象外で、これらも AI に操作させたい場合は、Unity エディタを外部から操作する MCP サーバを併用します。

この種のサーバは第三者が公開しているもので、このパッケージには含まれません(MCP for Unity の場合は、Unity の Package Manager の Add package from git URL に次のアドレスを入力します)。

https://github.com/CoplayDev/unity-mcp.git?path=/MCPForUnity#main
症状原因対処
メニューに TsukimiCode が出ないパッケージが入っていないか、プロジェクトのコンパイルが通っていないUnity のコンソールを読む。コンパイルエラーが出ていれば、そちらが先
クライアントに tsukimi のツールが出ない設定の前に起動していたか、プロジェクトの外で起動しているプロジェクトの直下で再起動する。ウィンドウで該当クライアントが configured になっているかを見る
Configure を押したのに変わらない起動中のクライアントは、設定を起動時にしか読まないクライアントを再起動する
Install Skill を押したのに AI が手順書を知らないインストールした Skill も、クライアントは起動時にしか読まないクライアントを再起動する
AI が Skill を参照せずにコードを書いているSkill がクライアントに読み込まれていないウィンドウの Grounding が up to date かを確認し、押し直してから再起動する
Codex CLI だけ接続できないプロジェクトが信頼されていないそのクライアントの手順で信頼させてから、再起動する
パッケージの更新後に接続できない設定に書かれたサーバの場所が、更新で変わったConfigure と Install Skill を押し直して、クライアントを再起動する
更新したのにサーバの挙動が古いクライアントが古いサーバを動かしたままクライアントを再起動する(サーバも一緒に再起動される)
Server の Status が not found同梱のサーバ DLL が見つからないパッケージを導入し直す
実在するはずの API が無いと言われ続ける実行環境の API のデータベースが生成されていないUnity でこのプロジェクトを一度開く。externDb.resolved が偽の間はこの状態
完了時の検査が動作しているか分からないコンパイルできたときは何も出ないウィンドウの Last check を見る。欄が out of date なら Install Check を押し直す
AI がファイルを置いたのに Unity で何も起きないUnity は、フォーカスが戻るまで外で置かれたファイルをインポートしないUnity のウィンドウを 1 度クリックする
シーンで触っても何も起きないInspector の参照が空UdonBehaviour の欄に割り当てる。Udon には例外処理がないので、ログには何も出ない

どの行の対処でも解決しないときは、ウィンドウを開き直して各節の表示を上から順に確認し、最初に異常が出ている節から対処してください。