Diagnostics & Connection Tools¶
Troubleshoot connection issues, inspect errors, and monitor compilation. Essential for debugging when commands hang or fail.
doctor¶
Comprehensive health check with optional auto-fix.
Parameters: - fix (bool, default=false) — Auto-clean stale port files and retry connection
Checks (5 total):
| Check | Tests | Auto-fix? |
|---|---|---|
python_version | Python >= 3.10 | No (install Python 3.10+) |
port_file | ~/.unity-biome-mcp/ports/*.port exist + PIDs alive | Yes (remove stale files) |
lockfile | ~/.unity-biome-mcp/server-*.lock contains no dead-PID entries | Yes (clean stale files) |
tcp_connection | 127.0.0.1:port reachable + responds | Reconnect attempt only |
unity_state | Unity responds to the TCP diagnose command without a compile/reload wedge | No |
Output:
All checks passed
Python: 3.12.1
Port file: ~/.unity-biome-mcp/ports/1234.port (PID 1234 alive)
Lockfile: no stale server lockfiles
TCP connection: port 9500
Unity state: compile clean
Example:
# Diagnosis only
result = await doctor()
# Auto-fix stale files + retry
result = await doctor(fix=True)
When to use: Before every session, or if commands hang/timeout.
get_compile_errors¶
Check if C# compilation has errors. Gates test execution. Uses corroboration between TCP response and Editor.log for reliability.
Parameters: None
Output: List of compiler errors with file:line:col, or "compile clean".
Format:
error CS0103 at Assets/Scripts/Player.cs:15:10: The name 'Health' does not exist
error CS0246 at Assets/Scripts/Player.cs:8:5: Type 'Enemy' not found
Example:
# Check compile status
errors = await get_compile_errors()
if "error CS" in errors:
print(f"Compile errors: {errors}")
else:
print("Compile clean — ready for tests")
get_console¶
Read Unity Console output (errors, warnings, logs).
Parameters: - count (int, default=10) — Number of lines to return - level (string, optional) — "error" | "warning" | "log" (default: all). For comprehensive problem detection (including Exception and Assert), use level="Error,Exception,Assert" - first (int, default=0) — If > 0, return first N from init buffer + last (count-first) from ring - keyword (string, optional) — Case-insensitive substring filter - count_only (bool, default=false) — Return number of matches as string instead of log lines - since (float, optional) — Only logs from last N seconds
Output: Console lines with timestamps.
Example:
# All console output
console = await get_console()
# Error logs only (excludes Exception/Assert)
errors = await get_console(level="error")
# All problem types (Error + Exception + Assert)
problems = await get_console(level="Error,Exception,Assert")
# Search for specific keyword
hits = await get_console(keyword="NullReference", count=50)
# Count errors without returning them
error_count = await get_console(level="error", count_only=True)
# Recent logs only (last 30 seconds)
recent = await get_console(since=30.0)
console_mark¶
Create a console watermark. Pure Python, no TCP call.
Parameters: - label (string, default="") — Optional label for the mark
Returns: mark_id string encoding current timestamp. Pass to get_console_since() to retrieve only logs after this point.
Example:
# Mark before an operation
mark = await console_mark(label="before_test")
# ... perform operations ...
# Get only new logs since the mark
new_logs = await get_console_since(mark_id=mark)
get_console_since¶
Console entries after a watermark created by console_mark().
Parameters: - mark_id (string) — String from console_mark() or bare float timestamp - level (string, optional) — Filter (e.g. "error,exception,assert") - count (int, default=500) — Max entries to return - keyword (string, optional) — Case-insensitive substring filter - count_only (bool, default=false) — Return match count as string
Example:
mark = await console_mark()
# ... do something ...
errors = await get_console_since(mark_id=mark, level="error")
recompile¶
Trigger Unity to reimport C# scripts. Returns immediately.
Parameters: None
Returns: Acknowledgment. Use await_compile to block until compilation finishes.
Example:
await_compile¶
Block until C# compilation and domain reload finish.
Parameters: - timeout (float, default=60.0) — Max seconds to wait. timeout=0 for immediate check without polling.
Returns: - "compile clean (X.Xs)" — Success after N seconds - "compile clean (sync)" — Via epoch tracking - "compile clean (no IL change)" — Compiled but no IL delta - "error CS0103: ..." — Compilation failed with errors - "timeout after 60s — compile still in progress" — Timeout
Example:
# Wait up to 30s for compile
result = await await_compile(timeout=30.0)
if "clean" in result:
print("Ready for tests")
else:
print(f"Compile status: {result}")
Workflow:
# After writing .cs files
await write_file(...)
result = await await_compile(timeout=30)
if "clean" not in result:
return # Abort, don't run tests
result = await run_tests_wait(mode="EditMode")
# Accept only its reconciled terminal snapshot; TIMEOUT is nonterminal.
This wrapper is for focused consumer-project verification. When developing Unity Biome MCP itself, run repository and disposable-worker C# tests with python3 run_unity_tests.py; release evidence never comes from an ad hoc MCP poll loop.
compile_preflight¶
Validate C# code without recompiling (fast Roslyn syntax check).
Parameters: - file_path (string) — Assets-relative path (e.g., "Assets/Scripts/Player.cs") - new_content (string) — Full file content to validate
Output: - "OK preflight (143ms)" — No errors - "ERR preflight" + error list — Diagnostics found
Example:
new_code = """public class Player : MonoBehaviour {
public void Move(float speed) {
transform.position += Vector3.forward * speed;
}
}"""
result = await compile_preflight("Assets/Scripts/Player.cs", new_code)
# -> "OK preflight (156ms)" (can now safely write)
# -> "ERR preflight\nerror CS0103 at line 5: ..." (fix first)
Preflight can reject syntax and reference errors before writing a file and triggering a Unity compile cycle.
execute_code¶
Execute C# code in the Unity Editor via Roslyn without creating a persistent script asset. Bare statements are auto-wrapped in a static class.
Security: Scanning depends on the selected security level. The default AllowAll level does not block these APIs; Standard and Strict apply progressively stronger checks. See the Code Execution Guide.
Parameters: - code (string) — C# code to execute (bare statements, no class wrapper needed) - undo_label (string, default="execute_code") — Label for Unity Undo group
Output: Return value from the executed code, or error message.
Example:
# Create a GameObject
result = await execute_code('var go = new GameObject("Test"); return go.name;')
# Query scene state
result = await execute_code('return FindObjectOfType<Camera>().orthographic.ToString();')
# Modify component values
result = await execute_code("""
var rb = GameObject.Find("Player").GetComponent<Rigidbody>();
rb.mass = 5f;
return $"mass={rb.mass}";
""")
doctor vs diagnose¶
doctor— Auto-repair health check. Fixes stale port files, retries connection. Use when setup is broken or connection needs cleanup.diagnose— Read-only compile/reload verdict. No mutations. Use to understand current state after a change.
diagnose¶
Lightweight non-blocking diagnostics. Reads Unity compile/reload fact-signals atomically and returns a single typed verdict.
Parameters: - prev_mvid (string, default="") — MVID from before a sync operation. Enables STALE-DOMAIN detection when provided. - expected_compile (bool, default=true) — Set to false for cache-hit/will_compile=false probes to prevent false STALE-DOMAIN on legitimately-frozen MVID.
Verdicts:
| Verdict | Meaning |
|---|---|
CLEAN-LIVE | All signals green, MVID determined, no errors |
FAIL:<CS> | Compile errors found (CS code or 'unknown') |
STALE-DOMAIN | MVID unchanged after intended recompile |
WEDGE-ENGINE | iscompiling=true + cn_active=false + stamp_frozen |
WEDGE-STATE | sync_state=compiling but compile=idle |
BUILD-FAILED-WEDGE | Log shows failed reload + guard keeps rejecting |
STALE-CACHE | Disk-fixed CS error not yet reimported |
TESTS-INVISIBLE | Tests dll unknown(missing) |
REBUILDING | All dlls missing, mid-rebuild |
NO-OP | idle-never, idle-stale, or MVID frozen (no compile expected) |
UNKNOWN | Connection error or undetermined stamp |
Example:
# Standalone probe
verdict = await diagnose()
# After sync with MVID tracking
verdict = await diagnose(prev_mvid="abc123", expected_compile=True)
verify_after_change¶
Single atomic 5-gate verification after code/scene changes. Runs only enabled gates; returns PASS when all pass, FAIL at first failure.
Parameters: - changed_files (string, default="") — Optional context; not used in gate logic - test_filter (string, default="") — NUnit filter applied to run_tests_mode (e.g., "ClassName" or "ClassName.MethodName") - run_tests_mode (string, default="") — Enable tests gate: "EditMode" or "PlayMode" (empty = skip tests) - playtests (string, default="") — Enable playtests gate: glob pattern or paths (e.g., "Playtests/*.playtest"; empty = skip) - mark_id (string, default="") — Enable console gate: mark ID from console_mark() (empty = skip) - timeout (float, default=300.0) — Max seconds for entire verification
Gates (runs in order, stops at first failure):
| Gate | When Enabled | Fails If |
|---|---|---|
1. await_compile | Always | Compile takes longer than timeout |
2. get_compile_errors | Always | CS errors found |
3. get_console_since | If mark_id provided | Error/Exception/Assert logs since mark |
4. run_tests_wait | If run_tests_mode provided | Tests fail or timeout |
5. run_playtest_suite | If playtests provided | Any playtest fails |
Returns: - "PASS: compile + errors_clean + console_clean + tests(12/12) + playtests(3/3)" — All enabled gates pass - "FAIL: <gate> gate failed\n <detail>\nnext gates skipped: console, tests, playtests" — Stops at first failure
Example:
# Just compile check
result = await verify_after_change()
# Compile + tests
result = await verify_after_change(run_tests_mode="EditMode")
# Compile + console + playtests
mark = await console_mark(label="before_change")
# ... make changes ...
result = await verify_after_change(mark_id=mark, playtests="Playtests/*.playtest")
# Full verification with all gates
mark = await console_mark()
result = await verify_after_change(
mark_id=mark,
run_tests_mode="EditMode",
playtests="Playtests/core/*.playtest",
timeout=600
)
When to use: After any significant change to verify compilation, references, console health, tests, and gameplay all at once.
scan_scene¶
See Spatial: scan_scene.
scene_health¶
Scene hierarchy and health audit with focus options. Returns severity-tagged findings.
Parameters: - focus (string, default="all") — Audit focus: "all" | "hierarchy" | "naming" | "duplicates" | "origins" | "missing" | "empty" | "disabled"
Severity Tags: - CRITICAL — Blocking issues (e.g., missing root, circular refs) - WARNING — Likely problems (e.g., orphaned objects, duplicate names) - INFO — Informational findings - OK — All checks passed
Example:
# Full audit
result = await scene_health()
# Check naming only
result = await scene_health(focus="naming")
# Check for disabled objects
result = await scene_health(focus="disabled")
validate_references¶
Validate all ObjectReference fields under a path recursively. Reports broken and missing refs; summary shows error/ok counts.
Parameters: - path (string) — Scene path to root of validation (e.g., "/Player") - depth (int, default=3) — Recursion depth: 1 = immediate children only, 3-5 = full subtree - verbose (bool, default=false) — Include [OK] lines; off by default to save tokens - ignore_optional (bool, default=false) — Skip fields marked [Optional] to reduce noise
Output: Per-field status ([ERROR], [MISSING], or [OK]) + summary line.
Example:
# Quick top-level check
result = await validate_references(path="/Player", depth=1)
# Full subtree, verbose
result = await validate_references(path="/Player", depth=5, verbose=True)
# Skip optional fields
result = await validate_references(path="/Enemies", ignore_optional=True)
resolve_scene_refs¶
Read-only scene reference resolver. Resolves $alias, /path, or t:Type tokens against the live scene.
Parameters: - refs (string) — Comma-separated list of $alias, /path, or t:Type tokens - fields (string, optional) — Comma-separated field names to check existence on matched component
Returns: One tab-aligned line per ref: OK|MISS|AMB (ambiguous) + path + details.
Example:
result = await resolve_scene_refs(refs="$player,/Enemy,t:Camera")
result = await resolve_scene_refs(refs="$player", fields="hp,maxHp")
lint_scene_refs¶
Read-only linter for scene references in DSL scripts or batch commands. Checks for unresolved aliases, missing objects, and ambiguous names without executing anything.
Parameters: - path (string, optional) — Project-relative path to .playtest file (mutually exclusive with snippet) - snippet (string, optional) — Inline DSL or batch commands to lint (mutually exclusive with path)
Checks: Unresolved aliases, embedded aliases, missing objects, ambiguous names.
Returns: OK: no issues or severity-tagged issues (ERROR/WARN) with file:line:token.
Example:
result = await lint_scene_refs(path="Playtests/combat.playtest")
result = await lint_scene_refs(snippet="ASSERT /Player|Health|hp == 100")
serialized_field_rename_audit¶
Audit [SerializeField] rename safety. Detects stale serialized values and suggests migration actions.
Parameters: - type (string) — Fully-qualified or simple component type name (e.g., "MyNamespace.PlayerStats" or "PlayerStats") - old_field (string) — Field name as it exists in serialized assets (before rename) - new_field (string) — Renamed field name in current C# source - include (string, default="prefabs,scenes,scriptable_objects") — Comma-separated scan targets
Returns: Four-field summary: has_formerly_serialized_as, stale_assets, safe_to_remove_attribute, recommended_actions.
Example:
# Audit a renamed field
result = await serialized_field_rename_audit(
type="PlayerStats",
old_field="health",
new_field="hp"
)
# Scan only prefabs
result = await serialized_field_rename_audit(
type="EnemyController",
old_field="speed",
new_field="moveSpeed",
include="prefabs"
)
sync_unity¶
Unified Unity reload: trigger Refresh (+ optional Resolve), wait for new code to be live.
Parameters: - resolve (bool, default=false) — Call Client.Resolve() first (use after package.json change) - bump (bool, default=false) — Atomically increment plugin patch version before sync; implies resolve=True. Circuit-breaker: one bump per session. - timeout (float, default=session timeout) — Max seconds to wait for convergence
Returns: "sync clean" / compile errors / timeout message / "REIMPORT-NEEDED".
Example:
# Basic sync after code changes
result = await sync_unity()
# After package.json change
result = await sync_unity(resolve=True)
# Force version bump + resolve + sync
result = await sync_unity(bump=True)
alias_status¶
Check alias table health: loaded/empty/stale, sources, and total alias count.
Parameters: None
Returns: Status of the alias expander cache.
Example:
mcp_status¶
Compact MCP status: scene, dirty, play/compile state, port, alias count.
Parameters: None
Returns: One-line status summary.
Example:
release_smoke¶
Run release readiness checks: status, aliases, compile. Returns PASS/FAIL summary.
Parameters: None
Returns: PASS or FAIL header + per-check lines.
Example:
list_connections¶
Show current TCP connection status.
Parameters: None
Output: Single line with port and state.
Example:
reconnect_unity¶
Explicitly reconnect to Unity via TCP.
Parameters: - port (int, default=0) — Port to connect to (0 = auto-discover)
Port discovery waterfall: 1. Explicit port param (if > 0) 2. UNITY_MCP_PORT env var 3. Live .port file whose recorded project path best matches UNITY_MCP_PROJECT_DIR, CLAUDE_PROJECT_DIR, or the current working directory 4. Newest live .port file 5. Default: 9500
Example:
# Auto-discover port and reconnect
await reconnect_unity()
# Manual port
await reconnect_unity(port=9501)
Troubleshooting Decision Tree¶
Commands hanging or timing out?
+-- Run: doctor()
+-- If errors: doctor(fix=True)
+-- Check console: get_console(level="Error,Exception,Assert")
+-- Check compile: get_compile_errors()
+-- If compiling: await_compile(timeout=30)
+-- Diagnose: diagnose() # typed verdict
+-- If disconnected: reconnect_unity()
+-- Still broken? doctor(fix=True), then reconnect_unity()
Common Issues & Fixes¶
| Issue | Check | Fix |
|---|---|---|
| "Commands hang after 30s" | get_compile_errors() | Wait for compile: await_compile() |
| "Connection refused" | list_connections() | Restart Unity or reconnect_unity() |
| "Tests fail but compile clean" | diagnose() | Check verdict; if STALE-DOMAIN: sync_unity(bump=True) |
| "Reconnect spam (9+ attempts)" | doctor(fix=True) | Clean stale port files |
| "Wrong port when multi-instance" | ls ~/.unity-biome-mcp/ports/ | Set explicitly: export UNITY_MCP_PORT=9501 |
Connection Diagnostics Workflow¶
# 1. Start session
result = await doctor()
if "failed" in result:
await doctor(fix=True)
# 2. Before tests
errors = await get_compile_errors()
if "error CS" in errors:
print(f"Cannot test: {errors}")
exit(1)
# 3. Gate on compile
compile_result = await await_compile(timeout=30)
if "clean" not in compile_result:
print(f"Compile failed: {compile_result}")
exit(1)
# 4. Run tests with correlated request/run identity
result = await run_tests_wait(mode="EditMode")
print(f"Tests: {result}")
For Unity Biome MCP repository/full-suite verification, replace the final MCP call with the standalone durable run_unity_tests.py command documented in Testing Reliability.
See also: Getting Started Troubleshooting for common connection issues.