Development-Fable-Improvements → main: ProBuilder, per-action undo, lazy discovery, data-safety & devblog (→ 2.39.3) - #21
Merged
Conversation
…ontextResponse reads EditorPrefs (main-thread-only) but ran on the HTTP ThreadPool thread; both context routes now go through ExecuteOnMainThread like every other sync route (root cause + fix per community PR #17 by rcasaleiro). Verified live: the exact call that 500'd now returns a graceful context payload [FEAT] Security, browser CSRF / DNS-rebinding guard : the bridge can execute arbitrary editor code but accepted any local HTTP request; IsTrustedLocalRequest now rejects non-loopback Host and any non-loopback Origin (403) before touching editor state. Local tools (no Origin) unaffected - verified live: evil Origin 403, evil Host 400/403, MCP servers fine [FEAT] Capabilities, protocol handshake plugin half : ping advertises monotonic protocolVersion (1) + pluginVersion (from PackageInfo); unknown routes return HTTP 404 on the legacy path so servers can degrade gracefully across version drift (re-implementation of community PR #20 by D3vCrow). Verified live [FIX] RouteRegistry, GetRegisteredRoutes had drifted to ~150 of 321 routes with wrong names (breaking dynamic tool discovery) : route list is now GENERATED from the RouteRequest dispatch switch into MCPBridgeServer.Routes.g.cs by tools~/generate-routes.mjs; CI workflow fails on drift (--check). 321 routes, verified superset of every truly-dispatchable old entry [FIX] ExecuteCode, numbers serialized as strings : SerializeResult ToString'd every reflected property and list element (42 became "42"); new depth-capped recursive SerializeValue preserves primitive types, recurses dicts/lists/anonymous objects (depth 4, 1000-item cap), and renders UnityEngine.Object graphs compactly. Verified live: int/float/bool/mixed-list all come back typed [FIX] ActionHistory, TargetInstanceId was int and silently truncated 64-bit Unity 6.5 EntityIds : now an opaque string end to end (record, JsonUtility persistence DTO, history window, select-target via MCPObjectId). Old persisted entries lose only this field on first load [FIX] BridgeServer, error responses leaked exception stack traces to the wire : traces now go to the editor log only [FIX] MacOS, ExecuteCode could not find Roslyn : add Contents/Resources/Scripting to the assembly search paths (community PR #19 by JetNik)
… credits), version bump 2.32.0 -> 2.33.0
…i-CSRF/DNS-rebind guard was bypassable (http://localhost.evil.com passed), re-opening browser-reachable arbitrary editor code execution : Origin is now parsed and its host matched EXACTLY via Uri.TryCreate (malformed/"null" Origin rejected). Verified live: evil-Origin 403, loopback-Origin 200, no-Origin 200 [FIX] Security, stack traces still reached the wire via the main-thread execution path (200 responses) : ExecuteOnMainThread/ExecuteOnMainThreadDeferred error returns now log the trace to the editor and send {error: message} only - completing the "traces off the wire" change on the common route path [FIX] RouteGen, deferred-route extraction was dead (a <[^>]+>> type matcher can't span the nested generic Dictionary<string,Action<...>>>, silently captured nothing) so testing/list-tests was absent from the registry : match from field name to the initializer brace, throw if zero deferred routes extracted. Registry 321->322, testing/list-tests present (verified live in _meta/routes) [FIX] TrustGuard, Host check was case-sensitive and mis-parsed bare unbracketed IPv6 : shared IsLoopbackHostName (OrdinalIgnoreCase) + bare-::1 guard [CLEAN] Repo : .gitattributes pins the generated .g.cs to LF so a CRLF regenerate can't spuriously fail the CI drift --check; honest comments on the int->string history-field mismatch (JsonUtility parses to "") and on conditional-compilation routes always listing in the generated set
…ot resolution via GetDirectoryName(dataPath), path canonicalization + traversal/absolute-escape guard, existence-based overwrite guard). Live-verified: traversal ../../evil.cs and absolute C:/evil.cs both rejected
[FIX] ScriptCommands, three source-code-loss vectors : (1) dataPath.Replace("/Assets","") stripped EVERY occurrence -> wrong root for projects under a /Assets path -> writes landed outside the project (import silently no-op); now GetDirectoryName(dataPath). (2) Update with missing/empty content truncated the target source file to 0 bytes -> content now required. (3) Create silently overwrote existing scripts -> overwrite guard. (4) raw path traversal/absolute escape -> confined under project root. Live-verified all four
[FIX] CreateAsset overwrite destroys existing assets (systemic) : added the existence guard to ScriptableObject, Terrain CreateTerrain, Animation controller + clip, Material, and asset Import (CreateAsset/File.Copy reuse the GUID and wipe the existing asset + every reference). SpriteAtlas already did this; now consistent. overwrite:true opts in
[FIX] Queue double-execution : a ticket whose 30s sync waiter already gave up (TimedOut) stayed in the queue and still executed later -> a client retry ran the non-idempotent action twice. ProcessNextRequests now drops TimedOut tickets before executing (race-safe under _queueLock)
[FIX] SetProperty silent no-op reported success : Color/Vector2/3/4/Rect branches did nothing when the value wasn't an object (agent passing "1,0,0,1" or an array) yet returned success; now they throw a clear ArgumentException the handler surfaces as an error
[FIX] asmdef RemoveReferences removed the wrong reference : Contains substring match (removing Unity.InputSystem also matched Unity.InputSystem.ForUI, breaking compilation while reporting success) -> exact name/GUID match only
… real GraphData API : - New MCPShaderGraphApi: reflection wrapper over ShaderGraph's real editor model (GraphData / MultiJson / FileUtilities). create/add-node/connect/disconnect now build the graph through the actual model and let ShaderGraph serialize it, so the output is ALWAYS a valid, importable asset (the old regex/JSON string-surgery produced invalid graphs) - BUG 1 create ignored template -> identical 809-byte INVALID graph (no target, dangling m_OutputNode, fails import): now builds a real URP Lit/Unlit graph (target + default surface/vertex blocks) via new GraphData + AddContexts + InitializeOutputs + AddRemoveBlocksFromActiveList; 'blank' = valid empty graph. Live: urp_lit 15KB/9 blocks compiles - BUG 3 add-node PropertyNode/empty-slot nodes broke import (NRE): nodes are instantiated through the model with their real slots; graph compiles - BUG 4 add-node ignored x/y: position set on the node's DrawState (struct written back); accepts positionX/positionY and x/y. Live: 150/400 honored - BUG 2 disconnect matched by output slot only (dropped sibling edges) AND blanked surviving multi-line edges to m_Id:"": now removes exactly the tuple-matched edges via graph.RemoveEdge; live proof: 2 edges from one output slot, disconnect one, the sibling SURVIVES with its real id, graph compiles - connect now validates slot compatibility / refuses cycles (graph.Connect) instead of blind edge-JSON insertion - Path-safety: create/add/connect/disconnect resolve under the project root (MCPAssetSafety); create gets an overwrite guard - Removed the superseded dead helpers (GetMinimalShaderGraphJson, GetMenuPathForTemplate, GetNodeTemplate, TrySerializeNodeViaReflection). Read-only JSON parsing helpers (get-nodes/get-edges/remove-node) untouched - Fail-closed: if the ShaderGraph API is unavailable (version drift), handlers return a clear error rather than corrupting anything
…Block -> address all HIGH) : - HIGH-1 script/update still truncated on content:"" (guard only caught missing/null) : now rejects empty like Create. Live-verified - HIGH-2 OverwriteGuard checked the RAW path against AssetDatabase while the write used the canonical path -> a double slash / . / .. spelling (or a not-yet-imported file) bypassed it and clobbered the existing asset : AssetWouldOverwrite now checks File.Exists(canonical) OR the DB. Live-verified Assets//X blocked - HIGH-3 remove-node was still the regex edge-blanking corruption path (mislabeled read-only) : migrated to real GraphData.RemoveNode via MCPShaderGraphApi; set-node-property now path-confined. Live-verified remove-node keeps the graph valid + compiling - M-1 confinement now enforces its stated contract (must be under Assets/ or Packages/) instead of anywhere-under-root (ProjectSettings/.git/Library no longer writable). Live-verified ProjectSettings rejected - M-2 pin the 4-arg MultiJson.Deserialize shape at init (fail closed on arity drift, not TargetParameterCountException at call time) - M-3 create no longer silently downgrades an explicit URP template to blank when URP is absent -> clear error - M-5 asmdef remove-references no-match returns a warning instead of a false success + needless recompile - LOW: removed the dead File.Exists/is-Dictionary guard in shadergraph create (overwrite:true now works there too); editor/state + project/info report the project path via the corrected root helper; TryResolveProjectPath IsPathRooted moved inside try; case-sensitivity uses Ordinal on Linux - CHANGELOG 2.34.0 documents the data-safety + ShaderGraph work AND the overwrite:true breaking change
- Full com.unity.probuilder integration: 14 probuilder/* handlers (MCPProBuilderCommands) - create-shape (cube/plane/cylinder/prism/stair/cone/door/pipe/arch/sphere/torus), extrude/bevel/subdivide/delete/translate/flip-normals faces, set-face-material, boolean CSG (union/subtract/intersect), combine, probuilderize, center-pivot, export-mesh - Gated on PROBUILDER_INSTALLED (asmdef versionDefine >= 4.0.0); clear not-installed stubs when absent, matching the UMA optional-integration pattern - Every mutation registers Undo so ProBuilder edits compose with the multi-agent queue; export-mesh uses outputPath and is confined under Assets/ via MCPAssetSafety - New probuilder capability category; route registry regenerated (336 routes) - Bump package 2.34.0 -> 2.35.0
- Per-action / per-agent undo: new undo/last reverts the most recent undoable MCP action as one whole step; agentId targets a specific agent's last action - Honest about Unity's linear undo: undo/last refuses to cascade and lists collateral (newer actions stacked on top) unless force:true - undo/history is now a per-agent action log (newest-first, agentId/count filters, undoable flags, current group) instead of only the current group name [REFACTO] MCPRequestQueue : - Wrap every WRITE action in its own named, collapsed Undo group so each agent's action is independently revertable and named in Unity's Undo history - Detect real undoability from the actual stack via internal Undo.GetRecords (old GetCurrentGroup before/after diff missed most edits e.g. RegisterCreatedObjectUndo); reads, undo-ops, empty groups, failures and execute-code (incidental temp-host churn) stay non-undoable. Fails open if the internal API is unavailable - Register undo/last route; regenerate route registry (337 routes) - Bump package 2.35.0 -> 2.36.0
- Dense per-node output by default: omit default-valued fields (active=true, tag=Untagged, layer=Default, origin position, universal Transform component, childCount implied by a complete children array); absent field means the default - Non-default info always preserved (verified live: inactive/tag/layer/position all survive) - verbose:true restores the old always-present shape (behavior change disclosed in CHANGELOG) - Measured 43% smaller on a representative 221-node hierarchy (42.3KB -> 24.4KB) - Bump package 2.36.0 -> 2.37.0
… CSG crash 'Value cannot be null: key') : create-shape always assigns explicit material or BuiltinMaterials.defaultMaterial; boolean pre-flights operand materials (Undo-tracked, materialsDefaulted disclosed) [FIX] ProBuilder, boolean result double-offset (CSG output is world-space but result was placed at target position, carved walls landed outside the level) : result sits at identity then pivot-centered on its geometry [FIX] Graphics, scene-capture rendered a stale camera (backgrounded editor never repaints the SceneView so code-driven LookAt/focus was ignored) : sync render camera to view pivot/rotation/size before rendering All three found by the live ProBuilder level-build test scenario (unity-mcp-server 2.35.1); level built + visually verified via the fixed capture. Bump 2.37.0 -> 2.37.1
- Mobile-style devlog notifications: MCPNewsService polls the AnkleBreaker devlog RSS (6h cadence, plain GET, cached across reloads), per-user read state (global EditorPrefs), first run seeds backlog read except the newest post (a single gentle '1') - Toolbar unseen badge: 'MCP #N' text badge on the native 6000.3+ element, real accent-orange badge pill on the pre-6000.3 fallback; tooltip lists new-post count - Toolbar dropdown gains an AnkleBreaker News section (latest 5, unseen marked, click opens + marks read, Mark All Read, Open Devlog Page) + Settings/News Notifications opt-out - Live-validated against the real feed: 6 posts parsed, unseen=1 on first run, badge clears on read, seen state persists [REFACTO] Dashboard : - Rebuilt Window > AB Unity MCP in UI Toolkit, branded to the studio palette (warm-brown + molten-orange): new MCPTheme loads the shared welcome-window brand sheet cross-assembly + MCPDashboardStyles.uss (BEM) - All IMGUI capabilities preserved (status, controls, queue + per-agent depth, project context, agent sessions, recent actions, categories + self-tests, settings, version/updates) + new News panel with category chips and unseen highlight - Sections refresh on signature change only, in isolation (one failing source cannot blank others), self-heal against Unity's layout-restore stomping (first population deferred one frame, content-aware skip guards) - Bump package 2.37.1 -> 2.38.0
…mma locales (battle-test BUG 1) : new MCPArgs reads MiniJson's boxed double/long typed-first (no ToString round-trip); ProBuilder GetFloat/GetInt/GetBool + UMA GetOptionalFloat delegate to it; present-but-invalid params now throw a clear error instead of silently defaulting; create-shape echoes appliedSize/appliedPosition. Reproduced + verified under forced fr-FR; also closes the create-shape 'race' (BUG 2 = dropped float positions) [FIX] ProBuilder, info bounds unreliable (BUG 3) : local bounds recalculated before reporting + new worldBounds (renderer AABB reflecting the transform); verified on a 2x-scaled cube [FIX] ProBuilder, boolean left operands overlapping the result (BUG 4) : sources deleted by default (Undo-tracked; deleteSources:false to keep), new name param, response reports sourceInstanceIds + sourcesDeleted - Bump package 2.38.0 -> 2.39.0
- Confine feed links to http/https before Application.OpenURL (OS shell): a spoofed/compromised feed could smuggle file://, UNC or a URI-scheme handler; validated at parse (bad-scheme item never becomes a clickable post) + defense-in-depth re-check in OpenPost. Live-verified file://, javascript:, steam:, ms-msdt:, UNC, vscode: all dropped incl. a crafted file:// item with a disguised <color> title - Strip <>/ from feed title/category (UI Toolkit labels render rich text; / is a GenericMenu separator) + enableRichText=false on news labels — blocks impersonation of plugin UI - Bound ingestion: reject >1MB responses, cap 50 posts (no unbounded alloc / EditorPrefs blob) - Strip ';' (the seen-set delimiter) from slugs so a crafted slug can't corrupt read-state [FIX] SelfTest : - Resume-after-domain-reload used delayCall (droppable during InitializeOnLoad) -> now first EditorApplication.update; new ProBuilder probe (create/verify/destroy cube when installed, pass-through when absent). Full battery 44/45 pass, 0 fail (mppm has no probe) - Bump package 2.39.0 -> 2.39.1
- CRITICAL asmdef path confinement: all 7 asmdef/* handlers took raw 'path' into File.Read/WriteAllText (the one asset-writer the data-safety wave missed) -> route through MCPAssetSafety.TryResolveProjectPath; live-verified ../../hosts and C:/.../evil.asmdef rejected, normal create/info/ref still work - CRITICAL deferred-route self-deadlock: a direct (non queue/submit) POST to a deferred route froze the editor for the full sync timeout -> return 409 directing to queue/submit; remove the orphaned ExecuteOnMainThreadDeferred primitive. Async queue path (what the server uses) unaffected - ProBuilder combine now records the surviving target before merge -> undo/last fully reverts (target geometry + sources); live-verified 6->12->6 - Deferred tickets no longer open an undo group (their late collapse could fold a concurrent agent's group in) - shadergraph set-node-property: real error when the property is absent (was silent success on an unchanged file) + literal MatchEvaluator so a $ in the value can't splice captured text - ProBuilder null-arg guards (name/material/faceIndices elem), execute-code dict item-cap, toolbar dot-texture cleanup before reload, dead var removed - Bump 2.39.1 -> 2.39.2
…eport A1) - unity_gameobject_delete refuses when the target's runtime (non-asset) mesh is still referenced by MeshFilters outside its subtree, incl. inactive objects (FindObjectsInactive.Include) — returns requiresForce/sharedWith; force:true overrides - Only runtime meshes are scanned, and only when the target has one, so normal asset-mesh deletes keep the fast path [FIX] ProBuilder : duplicate no longer shares a clone's runtime mesh (report A1) - unity_gameobject_duplicate calls MakeUnique() on each cloned ProBuilderMesh so the clone gets an independent, still-editable mesh; destroying either object no longer blanks the copies - Response reports proBuilderMeshesIsolated [FIX] ProBuilder : create_shape material resolution + no silent failure (report B1/B2) - create_shape and set_face_material share one resolver accepting a full asset path OR a bare name - Unresolved material now yields materialWarning (was a silent fall-back to the default with success:true); an ambiguous bare name makes a deterministic pick disclosed via materialWarning + appliedMaterialPath - Fixes the apparent combine material loss (B1): with per-piece materials actually applied, CombineMeshes.Combine preserves submeshes [FIX] ProBuilder : bevel_edges reports coplanar no-ops (report B3) - Returns changed (vertex/face-count delta) + a note when the bevel produces no geometry change [FIX] ProBuilder : MeshCollider re-synced after every rebuild (report B5) - Rebuild null-then-sets the MeshCollider's sharedMesh so physics matches the mesh after combine/boolean/any edit [FEAT] ProBuilder : create_shape layer / addCollider / parent (report B8) - Applied at creation with layerWarning/parentWarning on an unknown value; response echoes layer + hasCollider - parent falls back to a unique inactive-object match for a bare name
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Brings the full
Development-Fable-Improvementsline intomain.origin/mainis an ancestor of this branch, so this is a clean fast-forward — zero conflicts. Merge-audited (GO verdict) and re-verified after the latest round.Headline capabilities
probuilder/*handlers (MCPProBuilderCommands,#if PROBUILDER_INSTALLEDgated): shapes (11 types), extrude/bevel/subdivide/delete/translate/flip, per-face materials, boolean CSG, combine, probuilderize, center-pivot, export-mesh.duplicate(independent mesh per clone viaMakeUnique()),deleteguard against blanking shared-mesh clones (incl. inactive siblings), material name-or-path resolution with no silent failure + ambiguity disclosure, bevel no-op reporting, MeshCollider re-sync after every rebuild,create_shapelayer/addCollider/parent. (Discord report — fixed, adversarially reviewed, live-verified.)unity_undo_lastreverts the newest undoable MCP action whole; per-agent action log; integrated into the multi-agent queue..shadergraphassets (create, disconnect, add_node) #18) — create/add-node/connect/disconnect now build through the real GraphData API → always valid, importable.shadergraphassets.Data-safety & security hardening
asmdef path confinement; News feed URL/rich-text hardening; Origin allowlist exact-match (anti DNS-rebind RCE); overwrite guards on asset creators; deferred-route self-deadlock fix; locale-safe numeric parsing (
MCPArgs).Community contributions re-implemented (credited)
Context-route main-thread fix (#17, @rcasaleiro); capability handshake / protocolVersion in ping (#20, @D3vCrow).
Gates
Compiles clean; plugin self-test green (+ ProBuilder probe); companion server 59/59 mock + 15/15 live; route registry 337 in sync; capability parity 337/337 bridge→plugin (0 orphans; 69 core + 268 advanced); merge-tree vs
mainCLEAN.Full detail: see
CHANGELOG.md(through 2.39.3).