125 Commits

Author SHA1 Message Date
Shutong Wu ea3ce5a7e0 chore(asset-gen): "Asset Gen" tool-group label + regenerate reference docs
- McpToolsSection: add the asset_gen -> "Asset Gen" group display name so
  the tool-group tab no longer falls back to the raw "Asset_gen".
- Update the asset_gen group blurb (registry + CLI) to include audio gen.
- Regenerate the tool reference docs from the Python registry: add the
  missing generate_audio.md and refresh the asset_gen landscape/index.

Claude-Session: https://claude.ai/code/session_015KYy51gwBuhDuLZXXoqc98
2026-07-13 00:29:58 -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 5e3107e470 feat(asset-gen): group 3D/2D/sound into darker panels; inline key status
Wraps each category (3D Models / 2D Images / Sound) in its own darker rounded panel
so they read as distinct blocks, and moves the per-provider key status (saved/not set)
up into the header row to the right of the provider name — reclaiming a line per
provider. Verified live: 3 panels (bg alpha 0.20), status inline in each header.

Claude-Session: https://claude.ai/code/session_01GCxmdd4qo7MG6J4M6WcT9Y
2026-07-12 14:32:25 -07:00
Shutong Wu 94122fb10e feat(asset-gen): rename the 'Asset Gen' tab to 'Generative'
Visible tab label only; the internal assetgen-tab id (and all C# wiring) is unchanged.

Claude-Session: https://claude.ai/code/session_01GCxmdd4qo7MG6J4M6WcT9Y
2026-07-12 14:24:57 -07:00
Shutong Wu a2890bf81a fix(asset-gen): model dropdown ballooned to a huge box
The provider row is a vertical (column) container, so the setting-dropdown-inline
class's flex-grow:1 stretched the DropdownField vertically. Wrap the Model dropdown
in a horizontal .setting-row with a .setting-label (matching the Format row) and use
a label-less DropdownField. Verified live: all 5 model dropdowns now render at 18px
inside setting-row containers instead of ~400px boxes.

Claude-Session: https://claude.ai/code/session_01GCxmdd4qo7MG6J4M6WcT9Y
2026-07-12 14:23:16 -07:00
Shutong Wu 236e718bab feat(asset-gen): GUI model dropdowns + fal audio row + refresh
Phases 4-5. Extends the existing Asset Gen tab (no new window): per-provider
'Model' dropdowns on the image + 3D rows with price/duration/use-case metadata, a
fal audio row (no key field — reuses the shared fal key) with a model dropdown and
the Stable Audio license caveat, and a Refresh button that re-validates key
presence + the curated catalog. Selecting a model writes the per-(kind,provider)
pref that generate_* reads as its default. fal has no public list-models API, so
Refresh is a curated re-validate (the plan's gated fallback), never a network fetch.
Characterization tests assert the new builders + three-phase lifecycle.

Claude-Session: https://claude.ai/code/session_01GCxmdd4qo7MG6J4M6WcT9Y
2026-07-12 11:21:25 -07:00
Shutong Wu e67d653502 Merge pull request #1234 from Scriptwonder/fix/issue-1229-reload-resume-race
fix(editor): survive domain reloads in HTTP auto-start and reload-resume (#1229)
2026-07-11 10:05:56 -07:00
Shutong Wu 9eaadd8dcd Merge beta (b7213383) into #1207 orphan-detector branch
Semantic coexistence resolution in McpConnectionSection.cs: keep both
sibling debounce mechanisms intact — beta's health-check verification
debounce (UnhealthyVerificationThreshold / ShouldReportUnhealthy /
consecutiveVerifyFailures, c344625e) and this branch's orphaned-session
down-poll debounce (OrphanedSessionDownPollThreshold /
ShouldEndOrphanedSession / consecutiveServerDownPolls + reset on
session start). Both EditMode test files
(McpConnectionSectionHealthDebounceTests, McpConnectionSectionOrphanDetectionTests)
are preserved.

Claude-Session: https://claude.ai/code/session_01C8TU8ibk8gv3h4LqBxPbQi
2026-07-10 22:53:48 -07:00
Shutong Wu c344625e4c fix(editor): stop stdio connection UI showing false "Bridge not running"
Two related stdio connection-UI reliability bugs surfaced while testing
with a live domain-reload cycle:

1. Start Session race (severe). StdioTransportClient.StartAsync returned
   true unconditionally, then callers immediately verified — but
   VerifyAsync only reads StdioBridgeHost.IsRunning, which is still false
   while the previous port releases after a reload (Start() defers the
   bind to an editor-idle retry, or falls back to a new port after
   BusyPortFallbackWindowSeconds). Result: "Connection verification
   failed: Bridge not running", and Start Session only connected after
   several clicks. StartAsync now waits (bounded, ReadyWaitTimeoutSeconds)
   for the bridge to actually bind before reporting success.

2. Health-indicator flash. VerifyBridgeConnectionInternalAsync flipped the
   indicator to Unhealthy on a single transient verify miss during a
   reload/port-hop, then recovered — misleading. It now debounces via
   UnhealthyVerificationThreshold (mirrors the #1207 orphan-session
   debounce), resetting on any reachable result.

Both decisions are pure, unit-tested helpers (ShouldKeepWaitingForReady,
ShouldReportUnhealthy). 16 EditMode tests green (4 readiness + 5 debounce
+ 7 existing #1207 orphan, no regression).

Claude-Session: https://claude.ai/code/session_015JRaRFZy4piZzZtW5NabJS
2026-07-06 11:06:01 -07:00
Shutong Wu 2c8ae3ec33 fix(editor): reset the orphan down-poll streak when a session starts
Review follow-up: a streak accumulated while no session was running (detector
inert, counter still counting) survived into a freshly started session and
could satisfy orphan detection before the first post-start probe refreshed.
Reset on the not-running -> running transition, which covers every start path
(manual Connect, auto-start, resume) since UpdateConnectionStatus runs on the
UI tick.
2026-07-04 17:49:13 -07:00
Shutong Wu 18158fa353 fix: stop tearing down healthy HTTP sessions on transient probe misses (#1207)
The orphaned-session detector ended an active session on a SINGLE stale
reachability reading, and the reading came from a lone 50ms TCP connect
cached for 0.75s — trivially false-negative on a machine busy with test
runs or domain reloads. Evidence bundles in #1207 show 13 teardowns and
147 socket closures in one session from exactly this loop, wedging the
bridge in no_unity_session churn until manual recovery.

- Require 3 consecutive failed polls (0.75s cadence) before declaring a
  session orphaned; probe readings taken while the editor is compiling or
  importing don't count toward teardown, and detection is skipped entirely
  while busy.
- Raise the probe's connect wait 50ms -> 250ms, as an overall budget shared
  across candidate hosts so the worst-case main-thread wait cannot multiply.
- Honor UNITY_MCP_SESSION_RESOLVE_MAX_WAIT_S above 20s (ceiling now 120s;
  default unchanged): the old ceiling equalled the default, silently
  neutering the documented escape hatch for projects whose reloads or test
  boundaries legitimately exceed 20s. Same treatment for
  UNITY_MCP_SESSION_READY_WAIT_SECONDS, and both now share one bounded
  env-read helper.

The remaining piece of #1207 (keepalive reload-awareness in
WebSocketTransportClient) is untouched here: the resume machinery reworked
in #1234 already covers reload boundaries, and the detector debounce
removes the dominant churn source dsarno identified.
2026-07-04 15:42:52 -07:00
Shutong Wu 301511700c fix(editor): survive domain reloads in HTTP auto-start and reload-resume (#1229)
Auto-start died for the whole session whenever startup included a compile:
the ctor latched SessionState before the delayCall ran, and the reload wiped
the delayCall. Reload-resume died at multi-pass compiles: the one-shot flag
was consumed before the deferred (delayCall) resume ran, and the next
boundary deleted it again.

- Replace delayCall with EditorApplication.update ticks that the
  [InitializeOnLoad] ctor re-arms on every domain load; latch only when the
  deferred work actually dispatches, retry (bounded per domain) while editor
  services are still initializing, and skip the subscription entirely in the
  common case where auto-start is off and nothing is pending.
- Move the resume flag from EditorPrefs (per-user machine-global, survives
  crashes, leaks across concurrently open editors) to SessionState; keep it
  until the resume succeeds, is cancelled, or exhausts its retries, instead
  of consuming it at boundaries where the bridge is down. Manual Connect,
  End Session, transport switch, and orphan cleanup cancel a pending resume
  through a named seam (CancelPendingResume), which also aborts an in-flight
  retry loop; exhaustion erases the flag so later reloads don't replay 49s
  failure loops.
- Serialize TransportManager.StartAsync per mode: concurrent starts coalesce
  onto one in-flight attempt, so a manual Connect can no longer race the
  resume/auto-start loops into bouncing a just-established session
  (WebSocketTransportClient.StartAsync tears down a live connection first).
- A SessionState connect-pending marker lets the next domain load finish an
  auto-start whose connect phase a reload killed — connect-only, never
  re-spawning (StartLocalHttpServer stops a still-booting server first).
  Whether a launch-process handle exists is now answered live by
  ServerManagementService.HasManagedServerLaunchHandle; without one (post-
  reload, or an externally started server) the wait polls to the 5-minute
  hard cap instead of fail-fasting.
- Busy gate uses EditorStateCache.GetActualIsCompiling (now internal, with
  the CompilationPipeline reflection bound once as a delegate): raw
  isCompiling stays true all play session under
  Recompile-After-Finished-Playing (#549).
- One-time (per session) migration deletes the legacy EditorPrefs flag.

The stdio sibling has the same defect class (StdioBridgeReloadHandler.cs:65
delete-when-not-running, :133 delayCall) — follow-up, kept out of scope here,
along with the remaining stdio copies of the isCompiling probe.
2026-07-04 10:46:24 -07:00
Shutong Wu 42d9faaaf2 refactor(editor): drop OceanMark test, tuck version-gated members into the #if
- Remove OceanMarkTests (geometry/color mapping + construction) per request;
  the mark is verified visually in-Editor.
- With the test gone, MapSvgPoint/FromHex no longer need to be `internal`,
  and they (plus the SvgOrigin/SvgSize consts) are only used by the Painter2D
  path — move them inside `#if UNITY_2022_1_OR_NEWER` and make them `private`,
  so the 2021.3 fallback carries no unused members.

Result of a /simplify pass; reuse/altitude/efficiency angles came back clean
(logo-injection duplication left at 2 call sites per the repo's 3+-uses rule).
2026-07-03 17:16:46 -07:00
Shutong Wu c46d86dd18 fix(editor): harden uv-install lifecycle + log brand-icon fallback failure
Addresses AI review (Copilot + CodeRabbit) on #1231:
- PollUvInstall: bail out (and detach) if the window/UI was torn down while
  the task ran, guarding against dereferencing UI fields after teardown.
- OnEnable: resume polling if an install was still in flight when the window
  was disabled, so completion is still processed (button reset, deps re-checked).
- OceanMark.LoadBrandTexture: log a warning instead of silently swallowing
  asset-load failures on the 2021.3 raster-fallback path.

Declined CodeRabbit's "use a compat shim" suggestion for OceanMark's
#if UNITY_2022_1_OR_NEWER: per UnityCompatShims.cs policy, shims are for
[Obsolete] APIs, 3+ gated call sites, or announced removals — Painter2D is a
new API used via static dispatch in one self-contained file, which the policy
explicitly says to handle with #if, not a shim.
2026-07-03 15:15:06 -07:00
Shutong Wu 3e36002ded fix(editor): polish setup wizard + main-window narrow-width layout
- Setup wizard: de-box the title beside the logo; footer buttons size to
  content and sit together on one row (Refresh no longer stretches
  full-width and overflows); rename the deps-step "Done" button to "Next".
- Client Configuration dialog: show the count + only failures + next step
  instead of enumerating every successfully configured client.
- Main window header: version pill never shrinks/clips (flex-shrink 0);
  title stays at natural width.
- Tabs: flex-shrink so all six stay reachable when docked narrow.
- Connection status label wraps/shrinks instead of sliding under the
  Disconnect button (scoped to #connection-section).
2026-07-03 11:19:02 -07:00
Shutong Wu e677663497 feat(editor): embed Ocean brand mark in Editor UI + setup polish
Add the Ocean split-cube brand mark to the Editor UI and land three
low-risk installation-flow improvements.

Logo:
- OceanMark: reusable UI Toolkit control drawing the mark in Painter2D
  vector (geometry transcribed from logo-mark.svg, brand colors baked in),
  crisp at any DPI with no new package dependency. Painter2D is 2022.1+,
  so it is guarded by #if UNITY_2022_1_OR_NEWER with a raster fallback
  (package-icon.png) on the 2021.3 floor.
- Embedded in the main window header (#header-left group) and the
  setup-wizard header.
- Ship package-icon.png inside the package (listing icon + 2021.3 fallback).

Installation polish:
- One-click uv install: UvInstaller builds the official installer command
  per platform; setup wizard runs it off-thread with a confirm dialog and
  re-checks dependencies.
- Claude CLI auto-discovery: consolidate the weaker resolver into
  ExecPath.ResolveClaude() and add the ~/.claude/local (migrate-installer)
  location both resolvers missed.
- Setup wizard clarity: success/next-step copy after configuring clients.

Tests: EditMode coverage for the geometry/color mapping, per-platform uv
command, and the Claude override contract.

Verified: compiles clean on 2021.3.45f2 (fallback path) and 6000.4.11f1
(Painter2D path).
2026-07-02 22:09:23 -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 5ad4e5eb55 feat(asset-gen): Blender -> Unity handoff row in the Asset Gen tab
Informational row (not a keyed provider) with a best-effort "Blender app detected"
status and a pointer to the blender-to-unity workflow / import_model_file. BlenderMCP
runs in the AI client and isn't detectable from Unity, so this reports only the
locally installed Blender app.

- BlenderDetection helper: checks well-known install paths + PATH per platform; the
  pure DetectIn(candidates, exists) core is unit-tested with a fake predicate.
- McpAssetGenSection: AddBlenderHandoffRow() appended after the provider rows.
- README: note the tab's best-effort detection + that BlenderMCP lives in the AI client.

Verified: package compiles clean; EditMode 972/1018 pass, 0 failures (4 new
BlenderDetection tests) via tools/local_harness.py.

Claude-Session: https://claude.ai/code/session_015DAUrMR5UaSEzEn2wNPrEP
2026-06-28 23:19:59 -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 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 57d252372e feat(asset-gen): glTFast Deps-tab row + README + manual-verify checklist (Phase 8)
Add glTFast (com.unity.cloud.gltfast) as an optional dependency in the Dependencies tab
(detect + Install/Remove + bulk Install-All), so GLB generation/import is one click away.
README 'AI Asset Generation' section (providers, BYO-key in the Asset Gen tab, OS secure
store, manage_tools to enable, async tool usage). docs/asset-gen-manual-verification.md
checklist for live validation (real keys + licensed editor; Hunyuan TC3 two-header
caveat). Drop an incidental 'manage_tools' mention from a Phase 0 test comment so the
tool-symmetry quarantine guard stays honest.

Claude-Session: https://claude.ai/code/session_01Tjpb5gYgUe2AUJuRdXr7Lv
2026-06-28 19:25:21 -07:00
Shutong Wu 3e5128944f feat(asset-gen): Asset Generation GUI tab (config-only) (Phase 5)
New 'Asset Gen' tab in MCPForUnityEditorWindow: per-provider API-key entry backed by
SecureKeyStore (never EditorPrefs/project — field never reads the stored key back),
enable toggles, Test presence-check, Clear; non-secret prefs (default format, output
root, auto-normalize); glTFast-missing notice pointing to the Dependencies tab. No
Generate button / prompt — generation is MCP/CLI only. Wired exactly like the Advanced
tab. 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 45e5c883bd chore(review): fold in minor review findings
- standardize 5 stray 'MCP For Unity' doc-comments -> 'MCP for Unity'
- external-analytics: link in-product telemetry doc instead of plain ref
- fetch-stats: anchor output path to import.meta.url (run-from-anywhere)
- zh README: add Docs badge for EN/zh badge-row parity

Claude-Session: https://claude.ai/code/session_01XFiuAUxNS9riUJFFBEHvui
2026-06-27 16:24:16 -07:00
Shutong Wu 93b4e0d3b1 refactor(brand): ProductInfo constant; standardize Editor UI to 'MCP for Unity'
Single ProductInfo.{ProductName,MenuRoot} const; routes all user-facing
'MCP For Unity' (capital F) display strings to it. Internal identifiers,
namespaces, and manifest.json 'Unity MCP' unchanged.

Claude-Session: https://claude.ai/code/session_01XFiuAUxNS9riUJFFBEHvui
2026-06-27 16:14:20 -07:00
Shutong Wu d109211836 fix(server): headless local HTTP server launch with per-port logs and liveness polling
Convert the local MCP HTTP server launch from a visible-terminal model to a
headless background launch and make startup diagnosable and robust.

- Launch windowless via TerminalLauncher.CreateHeadlessProcessStartInfo;
  combined stdout/stderr redirected to a per-port log at
  Library/MCPForUnity/Logs/server-launch-{port}.log (truncated each launch)
- Replace the per-launch confirmation dialog with a one-time confirm gated on
  the new EditorPrefs key HttpServerLaunchConfirmed; the quiet auto-start path
  skips it and does not set the flag
- Prepend platform uv/uvx PATH entries so bare uvx/uv resolves under
  GUI-launched Unity's minimal non-login PATH (notably macOS)
- Replace the fixed ~30-attempt reachability waits with open-ended polling
  tied to the launched process's liveness (5-minute hard cap), emitting a
  tail-of-log failure report when the server dies — in both
  HttpAutoStartHandler and McpConnectionSection
- Simplify quit-time cleanup to a handshake-scoped StopManagedLocalHttpServer
  so headless servers are not left as invisible orphans
- Transport-aware UI button labels (Connect/Disconnect vs Start/End Session),
  a transient "Starting…" state, and clearer lifecycle logging

Tests: TerminalLauncherTests covers the headless start-info contract;
ServerManagementServiceCharacterizationTests covers the one-time-confirm gate,
quiet-path bypass, and per-port log redirection.
2026-06-14 16:44:08 -07:00
Shutong Wu b909d430f2 fix(clients): make Configure always idempotent-write; route Unregister via UI
User-reported regression from the previous commit on this PR:
"Configure All Detected Clients does not actually configure all IDEs;
after I click Configure each individual IDE still shows missing
configs, and if one is configured Configure All resets it."

Root cause was the toggle I'd put inside JsonFileMcpConfigurator.Configure():
when ConfigureAllDetectedClients walked the registry and called
configurator.Configure() on each one, any client whose status was
already Configured took the Unregister branch — so the bulk action
wiped every already-configured JSON client instead of refreshing it.
(The same trap existed for ClaudeCli even before this PR, but Claude
Code's CLI registration path is rarely hit through the bulk button so
nobody had reported it.)

Fix: move the Configure↔Unregister toggle out of the configurator and
into the UI handler. The configurator API now has two clearly-split
operations:

- IMcpClientConfigurator.Configure(): always idempotent-write. Safe to
  call repeatedly. This is what ConfigureAllDetectedClients calls and
  what makes the "refresh transport / server version drift" use case
  work without resetting anything.

- IMcpClientConfigurator.Unregister(): removes UnityMCP from this
  client's config. McpClientConfiguratorBase ships a no-op default;
  JsonFileMcpConfigurator overrides with the JObject-parse + remove
  path that used to be the private UnregisterFromConfig helper.
  Codex's TOML still has no remove path so it inherits the no-op,
  matching the previous commit's stance.

UI per-client click (OnConfigureClicked) now reads client.Status and
routes: Configured → client.Unregister(); else →
MCPServiceLocator.Client.ConfigureClient(client). The button label
toggle from GetConfigureActionLabel is preserved — it's purely
informational and stays consistent with the routed action.

Secondary fix in OnConfigureAllClientsClicked: clear the
lastStatusChecks cache after the bulk run so dropdown-switching to
any non-currently-selected client immediately reads its post-bulk
status from disk instead of waiting out the 45-second throttle. This
was the "after clicking Configure All each individual IDE still shows
missing configs" symptom even when the writes had actually succeeded.

ClaudeCli's existing internal toggle is preserved (its async path in
ConfigureClaudeCliAsync handles the Configuring…/Unregistering… UX
itself, and OnConfigureClicked still early-routes to that helper).
2026-05-24 11:55:59 +08:00
Shutong Wu f932764968 fix(ui): default the per-client setup foldout to expanded
Community report: "the Unregister button was removed in 9.7.0 — it did a
wrong registration with stdio transport and now it's difficult to
re-register; something is stuck on stdio and I can't switch to local."

The button wasn't actually removed — the 9.7.0 UI shuffle put the
single-client Configure (which toggles to Unregister for CLI-based
clients like Claude Code) inside a "Per-client setup" foldout that
defaulted to collapsed. With Configure All sitting prominently above it,
the foldout looked like terminal styling rather than the entry point to
manual per-client management, so users who needed to wipe a bad stdio
registration and re-add with HTTP couldn't find the button.

Flip the default to expanded in both UXML and the EditorPrefs fallback.
The state still persists per-user, so anyone who explicitly collapses it
keeps that preference — only the never-touched default changes.
2026-05-24 11:00:38 +08:00
Shutong Wu b70babfa98 fix: wizard empty-selection, README transport contradiction, test polish (PR review)
Four smaller items flagged on PR #1142.

MCPSetupWindow.OnConfigureSelectedClicked marked setup as completed
and closed the window even when the user hadn't ticked any client —
clicking "Configure Selected" with everything unchecked would silently
skip setup forever. Show a prompt and return early instead.

README listed Claude Desktop under the HTTP-default group while a
neighboring paragraph correctly says Claude Desktop is stdio-only;
removed it from the HTTP list and pointed at the stdio block.

StartupConfigRewrite_TypeExists only checked that the type resolved,
not that it's public — added an explicit IsPublic assertion so the
test matches its own message about the [InitializeOnLoad] requirement.

ConfigureDetectedClientsTests calls the real ClientConfigurationService
which walks McpClientRegistry and Configure()s every detected client —
on CI this is harmless (no clients installed) but on a dev machine it
mutates real user config files. Marked both tests [Explicit] until we
DI the configurator list through the service.
2026-05-22 19:39:22 +08:00
Shutong Wu 324c484436 Merge remote-tracking branch 'upstream/beta' into fix/auto-test-multi 2026-05-22 17:17:27 +08:00
Shutong Wu 643670d913 fix(setup): unstick per-dependency Install/Uninstall buttons on UPM failure
AddDependencyRow's click handlers flipped the button to "Installing..."
and only restored it inside a synchronous try/catch. But the per-package
call sites passed `() => InstallUpmPackage(...)` — no onComplete — so
when the UPM AddAndRemove request eventually completed (success OR
failure), nothing in PollUpmRequest's continuation ever told the button
to revert. A network timeout on com.unity.cinemachine therefore parked
the button on "Installing..." forever (Discord report); same for
Removing... on uninstall. The "Install All" path was fine because it
already threaded an onComplete callback through.

Change the install/uninstall hooks to Action<Action>, thread a `restore`
callback from the click handler into the UPM helpers, and invoke it
from PollUpmRequest's completion regardless of StatusCode. Roslyn (the
one synchronous install) just invokes the callback inline after Install
returns. The unrelated CRLF/LF noise in the diff is a side effect of
normalizing the file (988 CRLF lines, 41 LF lines pre-existing) onto
the dominant CRLF convention while my edits were in flight.
2026-05-22 15:46:37 +08:00
Shutong Wu 9c01c4a3db ui(client-config): drop the helper text, rename foldout to "Per-client setup"
Helper line under the green button was visual noise — the button label
already says what it does. "Configure a single client" was redundant
inside a section titled "Client Configuration"; "Per-client setup"
reads cleaner. Also drop the now-orphan .primary-button-hint style.
2026-05-22 15:05:22 +08:00
Shutong Wu 29711c1756 ui(client-config): lead with Configure All, collapse per-client details
Now that "Configure All Detected Clients" actually does what its name
says (auto-rewrite + per-client transport coercion + IsInstalled
filtering all landed in the recent client-config work), it's the path
we want first-run users on — not buried at the bottom of the panel.

Layout changes in McpClientConfigSection.uxml:
- Move the Configure-All button to the top of the section, right under
  the "Client Configuration" header, with a one-line helper underneath.
- Wrap the dropdown / status / single-client Configure button / Claude
  CLI path / project-dir / Manual Configuration foldout in a new
  "Configure a single client" foldout, collapsed by default. Persist
  its open/closed state via a new EditorPrefs key.

Style changes in Common.uss:
- New .primary-button class (bright green, 34px, bold) for the
  one-click action so it visually distinguishes itself from the regular
  blue .action-button rows.
- Light/dark-aware foldout header styling for the new client-details
  foldout that matches the existing manual-command-foldout treatment.
2026-05-22 15:01:00 +08:00
Shutong Wu 98c9df1431 feat(setup): add client picker step to first-run wizard 2026-05-18 16:39:57 +08:00
Shutong Wu 03daf97000 feat(clients): filter ConfigureAll to only detected clients 2026-05-18 16:31:38 +08:00
Warlander 0c2e275d9d Cleanup before the PR 2026-05-06 00:16:57 +02:00
Warlander 63731fcd12 Enable project-scoped custom tools in stdio mode
- ToolDiscoveryService: add AppDomain fallback scan for [McpForUnityTool] types
- StdioBridgeHost: include project_scoped_tools flag in heartbeat JSON
- McpToolsSection: update tooltip and default to reflect stdio support
- models.py: add project_scoped_tools field to UnityInstanceInfo
- port_discovery.py: read project_scoped_tools from status JSON
- main.py: enable project-scoped tools when Unity instance requests it
2026-05-05 22:06:10 +02: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
chenkunqing 68cd175470 Fix MCP window reopening and profiler warning 2026-04-26 21:47:37 +08:00
Shutong Wu 97a61efdec Merge pull request #1001 from zaferdace/feat/execute-code
feat: add execute_code tool for running arbitrary C# in Unity Editor
2026-04-01 15:41:36 -04:00
Shutong Wu 6312ec2180 Update 2026-04-01 15:32:27 -04:00
Shutong Wu 88fc8f1d18 Update based on review 2026-04-01 15:15:40 -04:00
Shutong Wu fa0c6d123a Update with Roslyn as optional execute_code usage, and UX update 2026-04-01 14:21:09 -04:00
Shutong Wu e15a377980 Update through field deployment - bug fix / doc update / UX change 2026-03-31 01:08:27 -04:00
Shutong Wu 4f52b866c6 fix: move EditorPrefs access to main thread for background update checks
Address CodeRabbit review feedback on PR #954: CheckForUpdate was running
entirely inside Task.Run, including EditorPrefs reads/writes and
MCPServiceLocator.Updates lazy initialization — both unsafe off the main
thread.

Split CheckForUpdate into three thread-aware methods:
- TryGetCachedResult: reads EditorPrefs cache (main thread)
- FetchAndCompare: network I/O only (background thread safe)
- CacheFetchResult: writes EditorPrefs cache (main thread)

The editor window now resolves the service and checks cache on the main
thread, runs only network I/O in Task.Run, and caches results back on the
main thread via EditorApplication.delayCall.
2026-03-23 16:59:41 -04:00
jfjia b05090791a perf: avoid blocking update checks in editor window 2026-03-19 16:21:48 +08:00
Shutong Wu 09342739b2 Merge branch 'feat/scene-view-screenshot-capture-v2' of https://github.com/jiajunfeng/unity-mcp into feat/scene-view-screenshot-capture-v2 2026-03-15 19:50:45 -04:00
Shutong Wu 38a4daeca0 Update on GUI 2026-03-15 19:50:35 -04:00
Shutong Wu c30405417c Update 2026-03-15 17:27:37 -04:00
Shutong Wu 7383a7f662 Update 2026-03-11 15:47:11 -04:00