216 Commits

Author SHA1 Message Date
Shutong Wu 5595c7be99 Merge pull request #1300 from kpkhxlgy0/codex/codedom-assembly-dedup
fix: deduplicate CodeDom assembly references
2026-08-03 11:23:34 -04:00
XiaoLongHan d2247e942e perf: cache CodeDom assembly paths 2026-08-03 14:42:32 +08:00
Shutong Wu 8bf889cf50 Merge pull request #1250 from beast-ofcourse/fix/execute-code-bom-phantom-error
fix(execute_code): skip phantom CodeDom BOM error from mcs compiler
2026-08-02 17:23:27 -04:00
Shutong Wu 07e8680df9 Merge pull request #1298 from asavs/fix/gameobject-create-component-properties
fix: make component properties reachable on manage_gameobject create
2026-08-02 16:25:25 -04:00
KamilDev 69267c536c fix(server): address resources by URI in agent-facing prose
A resource's name and its URI are deliberately different (`editor_state` vs
`mcpforunity://editor/state`), and the URI scheme is not derivable from the
name -- most resources are `category/thing` but several are flat
(`mcpforunity://instances`, `mcpforunity://menu-items`, `mcpforunity://tests`).

Several agent-facing strings still named resources without their URI, so an
agent following them built `mcpforunity://editor_state` and got a 404:

- server instructions listed resources by bare name and told the reader to
  "poll the `editor_state` resource's `isCompiling` field" (that field path is
  also wrong -- payloads are wrapped, so it is `data.compilation.is_compiling`)
- `refresh_unity`'s `wait_for_ready` parameter description referred to
  `editor_state.advice.ready_for_tools`
- the hint Unity returns in the `refresh_unity` result said "poll editor_state
  until ready_for_tools is true"

#1244 added a warning that names and URIs are not interchangeable, but left the
strings that trigger the mistake unchanged. Spell every resource reference as a
full URI instead, and correct the field paths while here.

Adds a regression test asserting that no agent-facing prose -- server
instructions, resource descriptions, tool and parameter descriptions, and
multi-word string literals under MCPForUnity/Editor -- mentions a resource by
its snake_case name without also giving that resource's URI.
2026-08-01 13:23:23 +10:00
XiaoLongHan 6f4c81224a fix: deduplicate CodeDom assembly references 2026-07-30 03:37:54 +08:00
asavschaeffer 503d938b4a fix: make manage_gameobject component properties reachable on create
Fixes #1297. At action:"create", component_properties was accepted and
coerced by the C# dispatcher (ManageGameObject.cs) but only ever consumed
by the "modify" handler, so it silently did nothing. Meanwhile the shape
"create" already reads directly out of each componentsToAdd entry
({typeName, properties}) was rejected before it reached Unity, because the
Python schema typed components_to_add as list[str].

- GameObjectComponentHelpers.cs: factor the componentProperties loop +
  error aggregation out of GameObjectModify.cs into a shared
  ApplyComponentProperties helper, so both actions apply it identically.
- GameObjectCreate.cs: call the new helper after components are added,
  destroying the partially-created object and returning the error if any
  property fails to set (matching how component-add failures are handled).
- GameObjectModify.cs: switch to the shared helper (behavior-preserving
  refactor, no functional change on the modify path).
- manage_gameobject.py: widen components_to_add to accept
  {"typeName": ..., "properties": {...}} objects alongside plain strings,
  matching what GameObjectCreate.cs already reads.
- Regenerated website/docs/reference/tools/core/manage_gameobject.md via
  tools/generate_docs_reference.py for the updated parameter docs.

Tested: Server/tests/test_manage_gameobject.py exercises the Python
contract end-to-end, including a real fastmcp/pydantic schema validation
run of the issue's exact repro payloads (confirmed the pre-fix
ValidationError reproduces on the unmodified file, and is gone after).
Added TestProjects/UnityMCPTests/Assets/Tests/EditMode/Tools/
ManageGameObjectCreateTests.cs coverage for the C# side, but this was not
run against a live Editor.
2026-07-28 15:59:38 -07:00
Shutong Wu 777e8a9c7a fix: trust the pipeline flag when a domain reload is deferred (#1276)
EditorApplication.isCompiling conflates three states: actually compiling,
compilation queued, and finished-but-reload-deferred. A project holding
EditorApplication.LockReloadAssemblies sits in the third state for as long
as the lock is held, with no compilation running, and isCompiling stays
true the whole time.

Eight call sites gated on that raw flag, so they refused work indefinitely:
the stdio bridge would not start, unity_reflect and manage_scriptable_object
returned "Unity is compiling", refresh_unity reported the wrong resulting
state and never completed its wait, the stdio reload handler deferred its
resume, and TestJobManager both mis-attributed its init timeout and reported
a bogus "compiling" block reason.

Route all eight through EditorStateCache.GetActualIsCompiling(), which falls
back to the event-tracked CompilationPipeline flag, and drop the isPlaying
gate that previously limited the workaround to play mode.

Verified live: with LockReloadAssemblies held after RequestScriptCompilation,
EditorApplication.isCompiling is true while the pipeline flag is false, so
CompilationPipeline.compilationFinished does fire while the reload is held.
2026-07-28 12:05:30 -04:00
Shutong Wu 36cbe62fe1 refactor(asset-gen): dedupe generate_* tool shell (CodeRabbit review)
status / cancel / list_providers were near-identical across GenerateAudio,
GenerateImage and GenerateModel (differing only by a kind label + poll
interval), and NormalizeOutputFolder was a verbatim triple copy.

- New AssetGenToolHelpers.{Status,Cancel,ListProviders} — the three tools now
  delegate, passing their kind label / poll interval.
- NormalizeOutputFolder moved to AssetGenPaths (it already lived on
  TryGetAssetsFolder).
- Behaviour preserved (image `remove_background` arm kept; provider-list
  filtering unchanged). Net -120 lines of duplication.

Verified: EditMode 1167 tests / 0 failures + in-editor smoke (audio/image/model
list_providers, status/cancel error paths, image remove_background all intact).

Claude-Session: https://claude.ai/code/session_015KYy51gwBuhDuLZXXoqc98
2026-07-13 10:26:31 -07:00
Shutong Wu 4faf7a527b fix(asset-gen): security hardening + correctness fixes (+19 tests)
Security (from a dynamic security audit of the branch):
- H1: UnityWebRequestTransport disables auto-redirect on auth-bearing
  requests (redirectLimit=0) so a provider 3xx can't re-send the API key
  to a redirect host.
- H2/P8: per-kind result-extension allowlist in AssetGenJobManager.WriteFile
  (+ defense-in-depth in the audio/image import pipelines) — a provider
  can no longer land a .cs/.asmdef/.meta/.asset under Assets/ (Editor RCE).
- H3: ProviderHttp.RequireHost pins the submit URL and the provider-supplied
  response_url to https://queue.fal.run before the fal key is attached
  (both fal image + audio adapters).

Correctness (from code review):
- C1: Tripo image->3D now sends model_version.
- C2/C3/C6/C10: FalAudioAdapter.BuildBody is catalog-driven — duration-
  required models (CassetteAI SFX/Music, Stable Audio) send a default
  duration when the caller passes 0 (fixes the default-input 422),
  fractional durations floor to >=1, Lyria stays prompt-only and its GUI
  no longer advertises a duration it ignores, and the clamp ceilings come
  from the catalog (no more duplicated 190/30/180).
- C4: an unmapped fal poll status now fails fast instead of polling to the
  600s timeout (both fal adapters).
- C5: a stale/invalid selected-model pref is cleared on dropdown fallback.
- C7: the audio fal-key status refreshes when the shared 2D fal key changes.

Cleanup: extract AssetGenModelCatalog.ResolveModel (dedupes the model-
resolution chain across the three generate tools) + DefaultModelId no-alloc.

Verified: full EditMode suite 1166 tests, 0 failures (+19 new regression
tests); 34 Python asset-gen tests pass.

Claude-Session: https://claude.ai/code/session_015KYy51gwBuhDuLZXXoqc98
2026-07-13 00:29:48 -07:00
Shutong Wu 8c0a5dfe0d feat(asset-gen): model catalog + generate_audio tool + default-model wiring
Phases 2-3. Adds a curated AssetGenModelCatalog (image/3D/audio models with
price/duration/use-case metadata; defaults reference the adapter constants so the
panel default equals what an omitted model resolves to). Adds the generate_audio
MCP tool + CLI, threads a per-(kind,provider) selected-model pref so a GUI choice
becomes the default generate_* uses when no model is passed, and makes
Tripo/Meshy consume req.Model. Per-provider prefs (not per-type) so disjoint model
lists never clobber. Compiles on 2021.3 floor; Python + adapter/catalog tests green.

Claude-Session: https://claude.ai/code/session_01GCxmdd4qo7MG6J4M6WcT9Y
2026-07-12 11:20:16 -07:00
Shutong Wu 0eee1568d8 feat(asset-gen): add animation_type rig mode to import_model_file
A rigged/animated FBX imported through import_model_file always arrived
with zero AnimationClips because the import pipeline hard-coded
ModelImporterAnimationType.None. Thread an optional animation_type param
(none|generic|humanoid|legacy) through the MCP tool, the CLI command, and
the C# pipeline so callers can surface a model's clips; the default stays
None, so existing imports are unchanged. glTF/GLB ignores the knob
(glTFast imports animation itself).

Also fold the tested Blender-handoff findings into the blender-to-unity
skill: GLB-vs-FBX fidelity guidance (with the new
references/bridge-fidelity.md matrix, force-added since .claude is
gitignored), FBX emission restoration steps, and the animation_type knob.
Sync uv.lock with the pyproject 10.0.0 version already on beta.

Claude-Session: https://claude.ai/code/session_01Cya1SZmg7CJgJS61nhjLH4
2026-07-11 22:59:48 -07:00
Beast-ofcourse 65983021aa fix(manage_build): stop forcing PVRTC texture compression on Android via subtarget
When manage_build builds for Android, BuildPlayerOptions.subtarget was
always set to StandaloneBuildSubtarget.Player (0). On Android, subtarget
maps to MobileTextureSubtarget (texture compression format). Building
with subtarget=0 on Unity 6000+ triggers a confirmed Unity bug (IN-102413)
that forces PVRTC texture compression — ignoring the project's Player
Settings (ASTC/ETC2).

Worse: setting subtarget persists the value in EditorUserBuildSettings,
overwriting whatever the user configured in Build Settings.

Fix: only set subtarget for Standalone platforms (Windows/OSX/Linux),
where it distinguishes Server vs Player builds. For all other platforms
(Android, iOS, tvOS, WebGL, etc.), leave subtarget at its default so
Unity respects the project's Player Settings.

Fixes #1212
2026-07-07 13:06:05 +05:30
Beast-ofcourse 127315b7d9 fix(execute_code): skip phantom CodeDom BOM error from mcs compiler
Mono's mcs compiler emits a stray U+FEFF BOM character on stdout during
compilation. CodeDom's CSharpCodeProvider misinterprets this as a compiler
error (no ErrorNumber, ErrorText is just the BOM), causing HasErrors to be
true and results.CompiledAssembly to be null — even though mcs exits 0 and
wrote the DLL successfully.

Fix:
- Compile to a temp DLL path (GenerateInMemory=false + OutputAssembly)
  instead of relying on results.CompiledAssembly.
- Skip phantom errors where ErrorNumber is empty and ErrorText is only
  BOM/whitespace.
- Load the produced DLL via Assembly.Load(File.ReadAllBytes(...)) when
  no real errors exist.
- Clean up the temp DLL in a finally block.

Fixes #1186
2026-07-07 11:57:56 +05:30
Vladimir Kuznetsov 85b693958a fix: preserve multi-line message body in read_console 2026-07-01 23:24:01 +07:00
Shutong Wu 36fcefbcbf Merge remote-tracking branch 'upstream/beta' into revamp/brand-distribution-analytics
# Conflicts:
#	README.md
2026-06-30 11:33:02 -07:00
Shutong Wu a884ada409 Clarify generate image unsupported action docs 2026-06-30 10:37:05 -07:00
Shutong Wu 39eb562d9a Harden asset generation file paths 2026-06-29 09:15:59 -07:00
Shutong Wu 4b4b7bd4dd fix(asset-gen): address image_path code-review findings
Follow-up to 2efb7860, from a focused AI review of the image_path work:

- Validate image input synchronously in the handlers: reject unsupported
  extensions (LocalImage.ResolveExisting) and Tripo local-image up front, so bad
  input fails immediately instead of returning a fake "pending" that only faults
  on the next poll.
- Meshy: the "no task id" submit/refine error again includes the response body
  (the PostTask refactor had dropped it).
- Sketchfab archive allowlist: add .dds/.ktx (real GPU texture formats) so glTF
  textures in those formats aren't silently skipped on extract.
- fal: don't send image_size to the /edit (image->image) endpoint, which derives
  size from the source image; build the request_id fallback poll URL from the base
  model id rather than the /edit sub-path.
- Meshy two-phase preview+refine now runs under a 600s job timeout (was 300s) so a
  default textured text->3D has room for both tasks.

Deferred (perf-only, noted on the PR): the blanket AssetDatabase.Refresh
double-import and the synchronous main-thread base64 of large local images.

Verified: package compiles clean; EditMode 977/1023 pass, 0 failures via
tools/local_harness.py.

Claude-Session: https://claude.ai/code/session_015DAUrMR5UaSEzEn2wNPrEP
2026-06-28 23:38:02 -07:00
Shutong Wu 2efb786042 fix(asset-gen): security hardening + provider correctness + local image_path
Security review + code review of the asset-gen feature surfaced concrete issues;
this fixes them and adds regression tests (request-shaping layer, FakeHttpTransport).

Security
- SafeZipExtractor enforces an extension allowlist; ModelImportPipeline passes an
  inert model/texture allowlist so a provider archive can't drop a .cs/.dll under
  Assets/ and have the Editor compile/load it (code execution on import).
- AssetGenJobManager refuses non-http(s) download URLs before fetching
  (file:// SSRF / local-file read into the project).

Provider correctness
- Meshy image->3D polls /openapi/v1/image-to-3d/{id} (was the v2 text URL).
- Meshy text->3D honors texture=true via the preview->refine two-phase flow.
- OpenRouter image->image attaches the reference image (content image_url part).
- fal image->image uses the /edit endpoint + image_urls array; width/height
  forwarded as image_size.
- Sketchfab search forwards categories/count/cursor/downloadable; preview doc
  corrected (returns metadata, not a base64 thumbnail).
- Job import calls AssetDatabase.Refresh() before importing a freshly written file.

Local image input (image_path)
- New LocalImage helper; image_path is read and sent inline as a base64 data URI
  for Meshy / fal / OpenRouter. Tripo rejects local images with a clear error
  (needs a hosted image_url; its upload flow is not wired).

Cleanup (no behavior change)
- Shared AssetGenPaths + ProviderHttp helpers, HttpResult.Ok, MissingKeyMessage,
  cached glTFast probe, dead-field / per-frame-alloc removal, CLI _emit.

Docs: README + manual-verification updated (image_path support; transparency is
import-flag-only; width/height fal-only).

Verified: package compiles clean; Python 1306 passed / 3 skipped. Meshy refine,
fal /edit, and image_path data-URI paths are unit-tested at the request layer
only -- live smoke per provider (real keys) still pending.

Claude-Session: https://claude.ai/code/session_015DAUrMR5UaSEzEn2wNPrEP
2026-06-28 21:30:00 -07:00
Shutong Wu 86cd4ee35a feat(asset-gen): import_model_file C# handler (local model import)
Adds ImportModelFile tool (import_model_file command) that copies an
on-disk model file (FBX/OBJ/GLB/glTF/zip) under Assets/ and runs it
through the shared ModelImportPipeline. Returns {asset_path, asset_guid}.
Adds ImportModelFileHandlerTests covering missing-source, unsupported
extension, and a real OBJ import.

Claude-Session: https://claude.ai/code/session_01NoHk4f7N1vUFs7gu817ihm
2026-06-28 19:25:21 -07:00
Shutong Wu 71887b9adc fix(asset-gen): import_model handler async to avoid Unity main-thread deadlock
ImportModel.HandleCommand was synchronous and blocked on .GetAwaiter().GetResult() for the
Sketchfab search/preview UnityWebRequest calls. UnityWebRequest completes on the editor
loop, which a blocked main thread cannot pump — freezing the editor. Make HandleCommand
(and Search/Preview) async Task<object> (CommandRegistry detects the Task return type) and
add a HandleCommand_IsAsync regression guard. Surfaced via live in-editor testing.

Claude-Session: https://claude.ai/code/session_01Tjpb5gYgUe2AUJuRdXr7Lv
2026-06-28 19:25:21 -07:00
Shutong Wu 48863078e2 feat(asset-gen): remove Hunyuan; update to current SOTA model defaults
Drop Hunyuan (Tencent TC3-HMAC) — high effort and not testable by the maintainer:
delete HunyuanAdapter + TencentCloud3Signer (+tests), unwire from AssetGenProviders,
SecureKeyStoreConstants, the GUI provider list, and Python/CLI/README/manual-verify docs.

Refresh model defaults to current (2026) SOTA, verified against provider docs:
- fal: fal-ai/flux/dev -> fal-ai/flux-2 (FLUX.2 dev; cheaper and higher quality)
- Tripo: v2.5-20250123 -> v3.1-20260211 (current recommended model)
- Meshy: drop deprecated art_style (errors on Meshy-6); pin ai_model=meshy-6
- OpenRouter: gemini-2.5-flash-image-preview (delisted) -> gemini-2.5-flash-image

Fix two tests that asserted the now-implemented 'meshy' provider throws (repointed to the
now-removed 'hunyuan'). They were silently wrong: the headless --full runs never executed
EditMode tests (Unity -quit exits before -runTests; compile-only). A real -runTests run
now passes 62/62 AssetGen EditMode tests; Python suite 1299 passed.

Claude-Session: https://claude.ai/code/session_01Tjpb5gYgUe2AUJuRdXr7Lv
2026-06-28 19:25:21 -07:00
Shutong Wu 8513ff8e92 feat(asset-gen): Meshy + Sketchfab import + Hunyuan providers (Phase 6)
MeshyAdapter (text/image->3D, model_urls by format). SketchfabAdapter
(IMarketplaceProviderAdapter: search/preview/resolve-download) + ImportModel.cs
[McpForUnityTool import_model] + AssetGenJobManager.StartMarketplaceImport. Hunyuan:
TencentCloud3Signer (TC3-HMAC-SHA256, golden test vs python hmac oracle) + HunyuanAdapter
(multi-secret JSON, Submit/Query Job). SafeZipExtractor (path-traversal guarded) +
ModelImportPipeline .zip handling (extract -> find fbx/obj/glb -> import) for Hunyuan/
Sketchfab; ProviderPollResult.ResultExt override. Adapters wired in AssetGenProviders.
Tests on FakeHttpTransport incl zip guard + TC3 known-answer. Compiles on 2021.3.45f2.

Claude-Session: https://claude.ai/code/session_01Tjpb5gYgUe2AUJuRdXr7Lv
2026-06-28 19:25:21 -07:00
Shutong Wu c5e3d85260 feat(asset-gen): 2D image generation — fal.ai + OpenRouter (Phase 7)
Generalized AssetGenJobManager to submit/poll/import delegates so model & image share
one state machine; added an inline-bytes path (ProviderPollResult.InlineData) for sync
providers that return base64. FalAdapter (queue API: submit -> poll status -> fetch
result image url) and OpenRouterAdapter (chat/completions, base64 image inline).
ImageImportPipeline (TextureImporter: Sprite vs Default, alphaIsTransparency, sRGB vs
linear). GenerateImage.cs [McpForUnityTool generate_image]. AssetGenProviders.Image
wired. 7 EditMode tests (adapters via FakeHttpTransport, handler routing, OpenRouter
inline end-to-end). Key read once at submit, never persisted. Compiles on 2021.3.45f2.

Claude-Session: https://claude.ai/code/session_01Tjpb5gYgUe2AUJuRdXr7Lv
2026-06-28 19:25:21 -07:00
Shutong Wu ca5f961b5d feat(asset-gen): job manager + generate_model handler + model import (Phase 3)
AssetGenJobManager: in-memory submit->poll->download->import state machine driven by
EditorApplication.update (asset import does not recompile, so no domain reload mid-job);
SessionState snapshots keep status queryable across unrelated reloads. Key read once at
submit, held in memory only, never persisted/logged/serialized onto the job.
ModelImportPipeline: ModelImporter settings for FBX/OBJ, glTFast-gated GLB with an
actionable error, best-effort scale normalize. GenerateModel.cs [McpForUnityTool
RequiresPolling] with generate/status/cancel/list_providers using PendingResponse polling
contract. 13 EditMode tests (state machine via FakeHttpTransport, handler routing,
no-key-leak, import guards). Compiles clean on Unity 2021.3.45f2.

Claude-Session: https://claude.ai/code/session_01Tjpb5gYgUe2AUJuRdXr7Lv
2026-06-28 19:25:20 -07:00
Shutong Wu 69eb51c661 chore(revamp): checkpoint WIP before testing feat/3d-asset-generation
README-zh.md edits + .meta/uv.lock churn on brand-distribution-analytics, committed
(not stashed) so the feature branch can be checked out in the main worktree for editor
testing. Untracked .agents/ and AGENTS.md intentionally left out.

Claude-Session: https://claude.ai/code/session_01Tjpb5gYgUe2AUJuRdXr7Lv
2026-06-28 11:11:59 -07:00
Shutong Wu 5699560672 Merge pull request #1125 from DLSinnocence/fix/memory-profiler-coremodule-api
Fix Memory Profiler snapshot actions on Unity 6
2026-06-14 23:50:14 -07:00
Shutong Wu 67445dff3d fix(execute_code): route CodeDom references through a response file (#1144)
`CodeDomCompile` in `MCPForUnity/Editor/Tools/ExecuteCode.cs` pushed every
filtered assembly path into `CompilerParameters.ReferencedAssemblies`. Mono's
`CSharpCodeCompiler.BuildArgs` (verified at
mcs/class/System/Microsoft.CSharp/CSharpCodeCompiler.cs:388-392) turns each
reference into a literal `/r:"<absolute_path>"` flag and concatenates them
inline on the `mono.exe csc ...` command line.

Projects with ~100+ asmdefs (a perfectly normal large Unity project) overflow
Windows' 32 KB CreateProcess argument limit and `Process.Start` throws
Win32 `ERROR_FILENAME_EXCED_RANGE`, which Mono surfaces as:

    SystemException: Error running …mono.exe: The filename or extension is too long.

…exactly the failure reported in #1144 at ExecuteCode.cs:115 on Windows
10/11 + Unity 2022.3.62f2 + MCP for Unity 9.6.9-beta.8.

Fix: write all `/r:"…"` lines to a GUID-named temp response file and pass
`@"<path>"` via `CompilerParameters.CompilerOptions` (which `BuildArgs`
appends verbatim, confirmed at line 396 of the same Mono source). One short
argument regardless of reference count — the 32 KB ceiling is no longer
reachable.

Both legacy mcs and Roslyn csc accept `@responsefile`, so the change is
cross-platform: macOS/Linux Mono behaves identically, and the path doesn't
have a 32 KB limit there to begin with. Response file is cleaned up in a
`finally` block (best-effort; OS reaps temp on its own otherwise).

Tests: added two EditMode regression tests in `ExecuteCodeTests.cs` that
exercise the codedom backend end-to-end:
- `Execute_CodedomBackend_CompilesAndRuns` — basic compile + execute.
- `Execute_CodedomBackend_ResolvesUnityTypes` — verifies Unity references
  resolve through the response file.

Could not reproduce the exact 32 KB failure on macOS (Mono on POSIX doesn't
hit the limit), but the response-file path is the only path in the new code,
so the fix is mechanically equivalent for any reference-set size on any
platform.

Sources:
- Mono CSharpCodeCompiler.cs — BuildArgs converts ReferencedAssemblies to
  `/r:"…"` inline and appends CompilerOptions verbatim.
- C# compiler — ResponseFiles option (`@responsefile` syntax).
2026-05-26 16:43:18 +08:00
Shutong Wu c56337ec03 refactor: drop defensive scaffolding made obsolete by module dep declarations (#1122)
Since #1122 declared the Physics 2D, Screen Capture, Image Conversion, etc.
built-in modules as required deps in package.json, Unity Package Manager now
keeps them enabled while MCP for Unity is installed. Two defensive layers
that protected against the now-impossible "module disabled" case become dead
code, plus one sub-floor preprocessor gate that was always-true on our
declared `unity: 2021.3` floor.

Tier 1A — UnityPhysicsCompat.cs
- Revert `Type.GetType("UnityEngine.Physics2D, UnityEngine.Physics2DModule")`
  to `typeof(Physics2D)`. The property-level reflection (for `autoSyncTransforms`
  deprecation in Unity 6.x) stays — that's a different concern.

Tier 1B — TrailControl.cs
- Strip `#if UNITY_2021_1_OR_NEWER` gate. Package floor is 2021.3 per
  package.json, so the `#else` branch ("AddPosition requires Unity 2021.1+")
  is unreachable.

Tier 2 — ScreenshotUtility.cs + callers
- Remove `IsScreenCaptureModuleAvailable` property, `ScreenCaptureModuleNotAvailableError`
  constant, `InvokeCaptureScreenshotAsTexture` helper, and the
  `s_captureScreenshotMethod` / `s_captureScreenshotAsTextureMethod` /
  `s_screenCaptureModuleAvailable` reflection caches.
- Replace reflective `MethodInfo.Invoke` calls with direct `ScreenCapture.X`
  calls in `CaptureToProjectFolder`, `CaptureComposited`, and the
  `ScreenshotCapturer` MonoBehaviour.
- Camera-based fallback path (`FindAvailableCamera` + `CaptureFromCameraToProjectFolder`)
  is preserved — it still handles transient null returns from `ScreenCapture`
  inside `CaptureComposited`.
- Drop pre-flight `if (!IsScreenCaptureModuleAvailable)` gates in
  `ManageUI.cs` (UI render) and `ManageScene.cs` (screenshot path).
- `ProjectInfo` resource: `screenCapture` field is now an invariant `true`
  (preserves API shape for `mcpforunity://project/info` consumers).

Net: 144 lines removed, 15 added. No new tests — the changes are pure
removal of code paths that cannot fire with the current deps declared.

Related: #1160, #1122.
2026-05-26 16:13:00 +08:00
KamilDev 6e4302db6a Merge remote-tracking branch 'upstream/beta' into fix/composited-screenshot-wait-for-end-of-frame
# Conflicts:
#	MCPForUnity/Runtime/Helpers/ScreenshotUtility.cs
2026-05-26 11:06:14 +10:00
KamilDev 2a7ec426c2 Merge remote-tracking branch 'upstream/beta' into fix/composited-screenshot-wait-for-end-of-frame 2026-05-26 10:42:05 +10:00
Shutong Wu 089d00668a Merge pull request #1113 from JMartinezRuiz/codex/fix-visionos-buildtarget
fix(build): avoid compile-time VisionOS enum references
2026-05-26 01:10:00 +08:00
KamilDev a697d09a42 refactor(screenshot): share end-of-frame capturer between render_ui and CaptureComposited
Per review feedback: ManageUI.MCP_ScreenCapturer and the new
CompositedFrameCapturer added in this PR were near-duplicates. Extract a single
public ScreenshotCapturer MonoBehaviour to Runtime/Helpers that takes a callback,
and have both call sites use it:

- ManageUI.render_ui (two-call pending/ready protocol): callback writes the
  texture and flips its own static done/started flags.
- ScreenshotUtility.CaptureCompositedAfterFrame (single-call sync spin):
  callback writes a local; spin loop pumps EditorApplication.Step until set.

Trimmed verbose comments in the same pass.

No behaviour change. Both paths re-verified end-to-end with a magenta UITK
panel-clear repro.
2026-05-24 21:00:57 +10:00
Shutong Wu b61238f680 Merge pull request #1116 from sMartz1/fix/roslyn-missing-system-runtime-compilerservices-unsafe
fix(roslyn): install missing System.Runtime.CompilerServices.Unsafe v6 + surface inner errors
2026-05-24 18:11:32 +08:00
Shutong Wu e2b570d938 fix(vfx): wire UNITY_VFX_GRAPH via versionDefines and drop 12.1-only allowlist
The whole manage_vfx tool dispatcher and every VfxGraph* helper is
gated behind '#if UNITY_VFX_GRAPH', but nothing in the Editor asmdef
ever defined that symbol. Users with com.unity.visualeffectgraph
properly installed (e.g. Unity 6.3 + VFX Graph 17.x) hit the false
"VFX Graph package (com.unity.visualeffectgraph) not installed" branch
for every action, including read-only ones like list_templates and
get_info — reported via Discord.

Add a versionDefines entry to MCPForUnity.Editor.asmdef so Unity sets
UNITY_VFX_GRAPH whenever the VFX Graph package is present at any
version (`expression: 0.0.0`). This is the canonical way to detect
optional packages and lets the existing #if branches do their job.

Also drop ValidateVfxGraphVersion's hard-coded {"12.1"} allowlist,
which only matches Unity 2022.3-era VFX Graph and would still block
CreateAsset on modern installs even after the compile-gate fix. The
asset-level APIs we touch (VisualEffectAsset, AssetDatabase.CopyAsset,
template enumeration via PackageInfo) are stable across the
12.x → 17.x range, so the safer guard is just "package present" with
the compile-time gate handling the real "not installed" path.
2026-05-22 15:31:54 +08:00
Shutong Wu 3ad6dc51af fix(registry): skip AutoDiscover in AssetImportWorker and guard reflection (#1134)
AssetImportWorker is a separate Editor subprocess that doesn't host the
MCP transport, so it has no reason to scan loaded assemblies. But our
[InitializeOnLoad] path runs there too, and Mono's reflection layer can
hard-crash inside type.GetCustomAttribute<T>() when assembly metadata
isn't fully restored after domain reload in the worker process — the
crash in the linked report freezes/kills the main Editor through the
import worker's IPC channel.

Detect the worker process via AssetDatabase.IsAssetImportWorkerProcess
(looked up reflectively so we tolerate visibility differences across
Unity versions) with a -importWorker command-line fallback, and bail
out of AutoDiscoverCommands before scanning. As a secondary belt, wrap
GetCustomAttribute<T>() in a try/catch helper so any single bad type
no longer aborts the entire registration pass.
2026-05-22 02:36:50 +08:00
DLSINNOCENCE d9431c5095 Guard memory snapshot overload invocation
The MemoryProfiler reflection code now verifies the selected overload's trailing parameter type before invoking it. This keeps Unity 6 CaptureFlags overloads and legacy uint overloads from being mixed in environments where both type families can be discovered.

Constraint: Reflection overload selection spans Unity versions with different trailing argument types.

Confidence: high

Scope-risk: narrow

Tested: Patched GameClient PackageCache and verified memory_take_snapshot, memory_list_snapshots, memory_compare_snapshots.

Tested: Verified profiler_status, get_frame_timing, get_counters, frame_debugger_get_events, and compare error paths.

Not-tested: Full Unity package test suite.
2026-05-14 17:06:56 +08:00
DLSINNOCENCE 488145371d Support Unity 6 memory snapshot capture API
Unity 6 exposes MemoryProfiler.TakeSnapshot from UnityEngine.CoreModule with CaptureFlags overloads, so the old Unity.MemoryProfiler.Editor reflection path makes memory_take_snapshot report that com.unity.memoryprofiler is missing even when the package is installed. The profiler list and compare actions only inspect snapshot files, so they no longer depend on MemoryProfiler type discovery.

Constraint: Unity 6 moved MemoryProfiler APIs into UnityEngine.CoreModule.

Rejected: Require com.unity.memoryprofiler editor assembly type discovery for all memory actions | list and compare only need filesystem metadata.

Confidence: high

Scope-risk: narrow

Tested: uv run --extra dev pytest tests/test_manage_profiler.py -v

Tested: Reflected Unity 6 MemoryProfiler.TakeSnapshot overloads in GameClient editor.

Not-tested: Full Unity package test suite.
2026-05-14 17:01:46 +08:00
SebM f116493d4d fix(roslyn): install missing System.Runtime.CompilerServices.Unsafe and surface inner errors
The RoslynInstaller downloads only 4 NuGet packages but Microsoft.CodeAnalysis 4.12.0
on netstandard2.0 also references System.Runtime.CompilerServices.Unsafe v6.0.0.0,
which is NOT what Unity ships (Unity bundles v4.x). The reference is unresolved at
runtime, so Roslyn's StringTable static cctor throws FileNotFoundException, which
in turn poisons CSharpSyntaxTree's cctor: every parse / compile attempt then
throws TypeInitializationException.

The error is invisible because RoslynCompiler.Compile's catch block only logs
e.Message, and for TargetInvocationException that string is the generic
"Exception has been thrown by the target of an invocation." — the real cause
in InnerException is silently dropped.

Repro:
  1. Trigger Tools > MCP for Unity > Install Roslyn on a fresh project.
  2. Ask the MCP execute_code tool to compile any Roslyn-only snippet.
  3. Observe: "Compilation failed: Roslyn compilation error: Exception has been
     thrown by the target of an invocation." — with no further detail.
  4. Drilling via reflection reveals:
       TypeInitializationException for CSharpSyntaxTree
        └─ TypeInitializationException for Roslyn.Utilities.StringTable
            └─ FileNotFoundException: Could not load file or assembly
                'System.Runtime.CompilerServices.Unsafe, Version=6.0.0.0'

Fix:
  * RoslynInstaller.NuGetEntries: add the missing dependency so a fresh install
    drops all 5 DLLs into Assets/Plugins/Roslyn/.
  * RoslynCompiler.Compile catch: walk the InnerException chain so a future
    bootstrap regression surfaces the actual cause (type + message) instead of
    the generic invocation wrapper.

Verified locally: after dropping System.Runtime.CompilerServices.Unsafe.dll v6
into the plugins folder and forcing a domain reload, the execute_code tool
compiles C# 7+ snippets via the Roslyn backend successfully.
2026-05-09 11:46:50 +02:00
JMartinezRuiz 75c844ffd6 fix(build): tighten BuildTarget enum parsing 2026-05-06 07:47:10 -06:00
JMartinezRuiz 2ab0b37da2 fix(build): avoid compile-time VisionOS enum references 2026-05-06 07:23:29 -06:00
dsarno c29821696d fix: unblock beta compile and gate releases on test success (#1103)
* fix: align CaptureComposited with renamed Project*-folder API

PR #1040 added CaptureComposited referencing the old Assets-folder names
(CaptureFromCameraToAssetsFolder, AssetsRelativePath) and called
PrepareCaptureResult without the now-required folderOverride argument.
Renames into the Project* equivalents; same fix at the call sites in
ManageScene.cs.

Closes #1100

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* ci: gate releases on test success and trigger tests on PRs

Beta-release was publishing to PyPI in parallel with Unity Tests, so
broken commits could ship if Unity tests failed (as happened with #1100
on 9.6.9-beta.5). Make publish/version-bump jobs depend on the test
jobs in both beta-release.yml and release.yml. The whole release halts
before any irreversible commit/tag/push if either test job fails.

Also extends the test workflows to fire on PRs so failures are caught
before merge:
- python-tests: pull_request trigger; runs on every PR (no secrets).
- unity-tests: pull_request_target [labeled] trigger gated on the
  safe-to-test label and on the PR being from a fork. Maintainers
  apply the label after reviewing the diff; the workflow then runs
  with UNITY_LICENSE in scope against the PR head SHA. Re-pushed
  commits do NOT auto-trigger; maintainer must remove and re-apply
  the label to re-run after additional review.

In-repo PRs continue to be tested via the existing push trigger, so
no labeling friction for collaborator branches.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(screenshot): propagate folderOverride to composited and specific-camera paths

Two pre-existing inconsistencies surfaced by CodeRabbit on #1103:

1. CaptureComposited dropped the caller's output_folder by hardcoding
   folderOverride: null in PrepareCaptureResult and the camera fallbacks.
   Adds the parameter to CaptureComposited's signature and plumbs it
   through both fallback paths.

2. The targetCamera and includeImage-in-play paths in ManageScene's
   game_view screenshot did not resolve cmd.outputFolder, so a request
   that selected a specific camera would always write to the default
   folder. Resolve via ScreenshotPreferences.Resolve as the other paths
   already do, and gate AssetDatabase.ImportAsset on IsUnderAssets so
   non-Assets folders don't trigger a futile import.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* ci(unity-tests): harden pull_request_target and gate artifact upload

Address CodeRabbit security review on #1103:

- persist-credentials: false on the checkout step, so GITHUB_TOKEN is
  not written to disk and cannot be read by subsequent steps running
  PR-controlled code.
- Explicit permissions: contents: read on the testAllModes job, scoping
  the workflow's token down from the default read/write set.
- Skip upload-artifact when the main test step was skipped (e.g.,
  because the preceding domain-reload step failed without
  continue-on-error). Avoids the noisy "No files were found" error
  on top of an already-failed run.

The label-gated trigger plus these mitigations narrow the blast radius
of the pull_request_target + checkout-PR-head pattern. The remaining
trust boundary is the maintainer review before applying safe-to-test;
documenting the review checklist (especially TestProjects/UnityMCPTests
diffs) is a follow-up.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-04 12:37:58 -07:00
Shutong Wu a90ab06844 Merge branch 'beta' into feature/game-view-uitoolkit-screenshot-capture 2026-05-03 20:10:38 -04:00
Shutong Wu 99a4687948 Merge pull request #1097 from Scriptwonder/chore/unity-version-compat-2026-04-27
Update0503
2026-05-03 20:05:24 -04:00
Shutong Wu 1b095fb301 PatchFix 2026-05-03 19:50:52 -04:00
Shutong Wu 1fba42998c Update0503
1.Add Compat based scripts revolving around UnityCompatShims.cs, that will document our current API Compatibility changes in several files.
2.Add custom screenshot folder selection
2026-05-03 18:16:10 -04:00
PaulLubos 8d51737f97 Add configurable init_timeout for PlayMode test initialization
PlayMode tests require entering play mode which triggers a domain reload.
On large projects this can take >15s, causing the hardcoded 15s init
timeout to auto-fail the test job before tests actually start.

This adds an `init_timeout` parameter to `run_tests` that flows through
the Python server → C# RunTests handler → TestJobManager. When set, the
per-job timeout overrides the 15s default. The value is persisted across
domain reloads via SessionState.

Changes:
- Python: Add `init_timeout` param to `run_tests()` function signature
- C# RunTests: Read `initTimeout` param and pass to `StartJob()`
- C# TestJobManager: Per-job `InitTimeoutMs` field with fallback to
  `DefaultInitializationTimeoutMs` (15s), persisted in SessionState

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-29 08:29:34 +02:00
Kenner Miner 7aa4315d16 fix: address screenshot PR feedback 2026-04-28 17:03:03 +08:00
Kenner Miner 56a14af834 Fix game_view screenshots to capture UI Toolkit overlays
Use ScreenCapture.CaptureScreenshotAsTexture() for game_view screenshots
when include_image=true and in Play mode. This captures the final composited
frame including UI Toolkit overlays, which camera.Render() misses since
UI Toolkit renders at the compositor level after camera rendering.

The camera-based path is still used when a specific camera is requested
or when not in Play mode.

Generated with [Claude Code](https://claude.ai/code)
via [Happy](https://happy.engineering)

Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: Happy <yesreply@happy.engineering>
2026-04-28 17:03:03 +08:00