122 Commits

Author SHA1 Message Date
Andriy Mykhaylyshyn 7997788edb fix: reject non-finite timeout env overrides; test env paths
Address CodeRabbit review on #1320:

- _env_float now requires math.isfinite(value): "inf"/"Infinity"/"1e309"/
  "nan" are positive-or-parseable but would produce unusable socket/timeout
  behaviour, so they now fall back to the default like other invalid input.
- test_config_default_values clears the two env vars first so ambient env
  can't mask the defaults.
- Added coverage for valid overrides and for invalid/zero/negative/
  non-finite values falling back to defaults.
2026-08-07 21:25:58 +03:00
Andriy Mykhaylyshyn d8d06cbd5a fix: make stdio bridge command timeout configurable (default 5m)
Long-running tool calls (asset imports, test runs, batched edits) were
cut off ~30-90s into execution, so the task could never finish. On the
stdio transport this was governed by hardcoded values on both hops:

- Unity side: StdioBridgeHost.FrameIOTimeoutMs (30s const) capped every
  command's execution and frame I/O; on timeout the client reconnected
  and re-sent, which force-closed the prior client and made the bridge
  restart on a new port (the repeated "StdioBridgeHost started on port
  6400/6402" churn).
- Server side: ServerConfig.connection_timeout (30s socket recv) and
  command_total_timeout (90s cross-retry ceiling) cut the command off
  first.

Unlike the WebSocket transport (WebSocketTransportClient reads a per-call
timeout off the wire), the stdio bridge had no way to raise these.

Make all three configurable with a 5-minute default:
- FrameIOTimeoutMs: 30s -> 300s, env UNITY_MCP_STDIO_COMMAND_TIMEOUT_MS.
  ReceiveTimeout now scales with it (max(60s, timeout)).
- connection_timeout: 30s -> 300s, env UNITY_MCP_CONNECTION_TIMEOUT.
- command_total_timeout: 90s -> 600s, env UNITY_MCP_COMMAND_TOTAL_TIMEOUT.

Invalid/non-positive env values fall back to the default so a bad
override can't disable the timeout. Updates the config characterization
test to the new defaults.
2026-08-07 21:00:55 +03: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
Shutong Wu 32428e820e Merge pull request #1302 from KamilDev/fix/resource-uri-prose
fix(server): address resources by URI in agent-facing prose
2026-08-02 12:21:12 -04:00
Shutong Wu c82502f1e6 test: fail on duplicate tool registrations instead of silently collapsing them
Copilot review on #1304. The tools fixture keyed a dict by tool name, so two
tools registering the same name would drop one entry — hiding the registry bug
and skipping the lost entry's annotations, in a guard whose whole purpose is
catching silent regressions.

No duplicates today (48 registrations, 48 unique names — the 49th
@mcp_for_unity_tool occurrence is a docstring mention at
services/tools/__init__.py:28, not a decoration), so this is a guard hardening
rather than a fix.

Also corrects the docstring grammar Copilot flagged.
2026-08-02 11:21:05 -04:00
KamilDev a0e489beec test: cover agent-facing markdown, drop dead type-hint guard
Review feedback on #1302.

The prose rule now also runs over the surfaces that tell a reader to go read a
resource: the skill agents load, and the per-tool reference pages whose example
blocks this PR fixed. Reverting those four lines makes it fail.

Scoped there deliberately. `website/docs/reference/resources/` is a generated
catalog that puts each name in a heading and its URI on the next line, and the
guides and getting-started pages name resources as the subject of a sentence
rather than instructing anyone to build a URI -- a blanket scan flags 38 lines,
none of them the defect.

Also drops the try/except around get_type_hints: it resolves for all 48
registered tools (266 annotated strings), so the except only had the power to
skip a tool's parameters silently. Without it a resolution failure surfaces as
the real error.
2026-08-01 13:36:03 +10: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
Shutong Wu 835bfcdd06 fix: stop 34 tools forcing an approval prompt on every call (#1288)
MCP clients gate a tool behind human approval unless it is read-only or
explicitly non-destructive, and destructiveHint defaults to true when omitted.
PR #480 set only `title=` on read_console, manage_editor and set_active_instance
despite its description claiming otherwise, so the spec default supplied
destructiveHint: true and nobody noticed.

Registering all 48 tools and dumping tools/list showed 34 of them serializing as
neither read-only nor explicitly non-destructive. find_gameobjects emitted
`annotations: null` outright.

State the hints explicitly across 10 modules. Four genuinely safe tools become
destructiveHint=False; the read-only set gets explicit hints instead of relying
on defaults; manage_editor and manage_components get explicit destructiveHint=True,
which changes no behaviour but stops them depending on the implicit default that
caused this. 34 gated -> 30, and the remaining 30 all write to the project.

find_gameobjects is deliberately not readOnlyHint=True: it calls
preflight(refresh_if_dirty=True), which can trigger a domain reload, and a
read-only promise would let a client do that unattended.

Add test_tool_annotations.py as the durable guard - it requires every tool to
state title and destructiveHint, and pins the auto-approvable set so a future
edit cannot silently flip one. Verified it fails by replaying the #480 regression.

test_tool_test_symmetry.py now excludes registry-wide guards from counting as
per-tool coverage, so one such file cannot satisfy the coverage guard for every
tool it happens to mention.

Does not fix the whole report: manage_asset(action="search") stays gated because
manage_asset can also delete. A read-only find_assets tool is the follow-up.
2026-07-29 00:18:06 -04: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 e2aacdf35d fix: camera CLI discarded its output and ignored --format
Follow-up to the inverted-argument fix in this branch: repairing the
run_command call order was necessary but not sufficient, and the group was
still effectively dead.

format_output(data, format_type: str = "text") returns a string. All 18 call
sites called format_output(result, config) and dropped the return value, so
every `unity-mcp camera` subcommand printed nothing at all. Passing the whole
CLIConfig where a format string was expected also meant the branch always fell
through to text, silently ignoring --format/UNITY_MCP_FORMAT.

Use click.echo(format_output(result, config.format)), matching every other
command module.

Adds two regression tests: one asserting the group emits non-empty output, one
asserting --format json yields parseable JSON. Both fail without this change.

Reported by Copilot on #1293.
2026-07-28 13:09:17 -04:00
Shutong Wu 2b2ca8a6f3 feat: add a clear_stuck escape hatch to run_tests (#1272)
A test job orphaned by a domain reload leaves TestRunStatus pinned with a
CurrentJobId that blocks every subsequent run, and there was no way to clear
it from the client side.

Add clear_stuck to the run_tests MCP tool and --clear-stuck to the editor CLI.
Both short-circuit ahead of the init_timeout validation and preflight, because
neither applies to clearing and preflight's requires_no_tests gate would reject
the very call that exists to release it.
2026-07-28 12:05:53 -04:00
Shutong Wu 82b1b732e2 fix: correct inverted run_command arguments in the camera CLI
All 18 call sites passed run_command(config, "manage_camera", params) while
the signature is run_command(tool, params, config), so the entire
`unity-mcp camera` command group was dead at beta HEAD.

test_cli.py asserted against call_args[0][2], which encoded the bug rather
than catching it; it now asserts the tool name at [0][0] and params at [0][1].
2026-07-28 12:05:53 -04: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 628efb6b8c Merge pull request #1262 from Scriptwonder/feat/import-model-file-animation-type
feat(asset-gen): add animation_type rig mode to import_model_file
2026-07-12 10:04:41 -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
Shutong Wu f5e63810df Merge pull request #1217 from mertakdut/beta
fix(stdio): recover from a reload-orphaned socket instead of hanging
2026-07-11 17:20:26 -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 e6ecad84c7 fix(http): list instance ids in the selection refusal; hint selection, not retry
Live-testing follow-ups from the two-client isolation pass:

- The HTTP refusal only logged the available instances server-side while the
  stdio guard lists them inline — an agent hitting the HTTP path needed an
  extra mcpforunity://instances hop to act. InstanceSelectionRequiredError now
  carries available_instances structurally and appends them to the message,
  fetched from the registry at raise time (error path only).
- The transport wrapper's blanket except turned the refusal into hint="retry",
  which is misleading: a blind retry fails identically. Selection errors now
  return hint="select_instance" with reason and available_instances in data,
  so clients can route straight to set_active_instance.
2026-07-05 20:22:05 -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 eb67603a83 fix(stdio): refuse to auto-route unbound session when 2+ instances connected (#1023)
The stdio connection pool's _resolve_instance_id silently returned the
most-recently-heartbeated editor when no unity_instance and no default
were supplied, letting an unbound session retarget another project's
Unity. Mirror the HTTP "multiple connected, no active set" guard: select
the sole instance when there is exactly one, otherwise raise ConnectionError
listing the available ids and requiring an explicit selection.

Pairs with the cherry-picked middleware fix that keys active-instance
state by ctx.session_id instead of the peer-supplied client_id.
2026-07-04 15:34:10 -07:00
Shutong Wu 18c31a6977 fix(middleware): delegate active-instance persistence to FastMCP session state (#1023)
UnityInstanceMiddleware previously kept its own dict (`_active_by_key`) keyed
by `client_id | user:{user_id} | "global"`. The MCP protocol's identity is
session-based, not client-based:

  - Streamable HTTP uses `Mcp-Session-Id` per the 2025-11-25 spec
  - stdio is intrinsically 1:1 (one subprocess = one session)
  - `client_id` is the peer-declared Implementation name, which is not
    unique — two Cursor instances both report `"cursor"` and collide

The `"global"` fallback compounded this: anonymous clients all collapsed onto
one record, so client A's `set_active_instance` retargeted client B (#1023).

FastMCP already exposes session-isolated state via `ctx.set_state` /
`ctx.get_state`, prefixed by `ctx.session_id` (verified in fastmcp 3.x
`server/context.py:1181-1238` — reads `mcp-session-id` header on HTTP,
generates a per-session UUID on stdio, caches on the session object).
Switching to it removes a whole concurrency surface and closes the bug class
by construction.

Changes:
- Replace `_active_by_key + _lock + get_session_key` with two-liner
  set/get/clear_active_instance backed by `ctx.set_state` under
  `mcpforunity.active_instance`.
- `set_active_instance` tool now returns `session_id` (the real key) instead
  of the bogus `session_key` field.
- `debug_request_context` drops `derived_key` and `all_keys_in_store`
  (no global dict to enumerate; per-session reads still report
  `active_instance` and the real `session_id`).
- Drop characterization/integration tests that pinned the removed
  `client_id → key → "global"` behaviour. Add a regression test that
  proves two ctxs with private state dicts can't read each other's
  active instance.

No protocol or wire-format change. The behavior shift is invisible to
single-client setups and removes the cross-client leak for multi-client
HTTP deployments.
2026-07-04 15:34:10 -07:00
Shutong Wu 6dcdda115f feat(asset-gen): import_model_file MCP tool + pass-through tests 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 b7652ca6fd feat(asset-gen): Python MCP tools + CLI pass-throughs (generate_model/import_model/generate_image)
Thin key-free pass-throughs in group asset_gen mirroring the C# command names:
camelCase param mapping, None-strip, async status/job_id (manage_packages shape).
CLI group 'asset-gen' registered in cli/main.py. No key/secret param exists on any
tool; tests assert sent payloads carry no key/secret/token. 34 tests pass.

Claude-Session: https://claude.ai/code/session_01Tjpb5gYgUe2AUJuRdXr7Lv
2026-06-28 19:25:20 -07:00
Shutong Wu d6604a571e feat(asset-gen): scaffold asset_gen tool group + non-secret prefs (Phase 0)
- Register 'asset_gen' tool group (off by default, parity with vfx/animation)
- EditorPrefKeys.AssetGen.* consts (non-secret config only; keys go to secure store)
- AssetGenPrefs helper (provider/format/output-root/normalize/enabled) + EditMode tests
- Python scaffold test asserts group present and disabled by default

Claude-Session: https://claude.ai/code/session_01Tjpb5gYgUe2AUJuRdXr7Lv
2026-06-28 19:25:20 -07:00
mert.akdut eb91b98689 fix(stdio): make the command deadline a hard ceiling on blocking I/O 2026-06-27 19:29:34 +03:00
mert.akdut de43399261 fix(stdio): recover from a reload-orphaned socket instead of hanging 2026-06-27 16:25:02 +03:00
Shutong Wu 86d6f7aff3 fix(harness): address Copilot/CodeRabbit review nits
- local_harness: fail fast (exit 2) on empty --legs and on --ci without
  UNITY_IMAGE, instead of silently exiting green / crashing with an
  unhandled CalledProcessError
- local_harness: correct the nearest-patch `best` type annotation
  (key is (patch:int, suffix:str), not a 3-tuple)
- test_tool_test_symmetry: fix the module docstring to match behavior
  (only test_*.py files are scanned; non-test_*.py scripts like
  tests/e2e/bridge_smoke.py do not count toward coverage)

Deferred CodeRabbit's SHA-pin suggestion for e2e-bridge.yml: the repo pins
actions by tag (@v4/@v6), not SHA, so pinning one workflow would be
inconsistent — left as a repo-wide policy decision.
2026-06-14 22:56:27 -07:00
Shutong Wu 555158d4c7 test(server): enforce tool/test symmetry with a shrink-only guard
- Server/tests/test_tool_test_symmetry.py discovers every module exposing an
  @mcp_for_unity_tool and fails CI if it is not referenced by any test
- A KNOWN_UNTESTED quarantine lets pre-existing gaps (execute_menu_item,
  manage_shader, manage_tools) skip rather than fail; a second test fails if a
  quarantine entry is stale, so the list can only shrink

Operationalizes the CLAUDE.md rule that every new tool ships with a test.
2026-06-14 22:02:33 -07:00
Shutong Wu 89f27f1e3b test(e2e): add headless bridge harness and deterministic no-LLM CI smoke
A one-command, no-API-key end-to-end gate for the Python<->Unity bridge,
runnable locally and in CI.

- tools/local_harness.py: boots a headless Hub-licensed Editor (or attaches
  with --reuse) and runs smoke + EditMode + PlayMode legs over the bridge,
  aggregating JUnit; exit codes 0-5 (pass / regression / unreachable /
  no-compile / no-license / no-editor)
- Server/tests/e2e/bridge_smoke.py: deterministic no-LLM contract driver over
  the real wire path; the no-LLM counterpart to claude-nl-suite.yml
- .github/workflows/e2e-bridge.yml: PR gate booting headless Unity in CI;
  self-skips (warns) when Unity license secrets are absent
- .github/workflows/python-tests.yml: run the hermetic harness unit tests and
  add tools/** to the path triggers
- tools/tests/test_local_harness.py: 69 hermetic unit tests for the harness's
  Unity-free decision logic (discovery, version resolution, exit-code mapping)
- Document the harness in CLAUDE.md and the contributor docs
2026-06-14 22:02:33 -07:00
PaulLubos e8e3b45882 Address code review: input validation and tests
- Clamp initTimeoutMs in StartJob: negative values → 0, cap at 600s
- Python: reject init_timeout <= 0 with explicit error before calling Unity
- Add 3 C# EditMode tests for per-job InitTimeoutMs behavior
  (custom timeout, default timeout auto-fail, persist/restore)
- Add 4 Python tests for init_timeout forwarding and validation

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-29 08:29:34 +02:00
Guilherme Gibertoni 153915ce20 improve: mcp python bridge logs are now redirected to a path that follows the OS overall structure 2026-04-22 11:16:14 -03:00
dsarno a3465587d2 fix: Unity 2021.3 compat — compile errors, Mono crash, 19 test failures (#1036) 2026-04-05 18:07:06 -07:00
Shutong Wu af37bccc3d Fix 2026-04-02 23:49:08 -04:00
Shutong Wu c76d0bb281 Fix on #837 2026-04-02 23:23:07 -04: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 2e3e65ed65 Merge pull request #1005 from Sibirius/feature/manage-gameobject-is-static
feat(manage_gameobject): add is_static parameter to modify action
2026-04-01 01:12:11 -04:00
Shutong Wu c41d365354 Update 2026-03-31 12:24:07 -04:00
Sebastian Muehr 632488c6dd fix(manage_gameobject): compare flags directly for partial static objects
targetGo.isStatic returns true when *any* flag is set, so a partially
static object (e.g. only Navigation) would skip the update when
is_static=true was requested. Compare GetStaticEditorFlags() against
the desired flags instead.

Also adds a "false" string coercion test per review feedback.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-30 22:57:18 +02:00
Sebastian Muehr 2d3c71ec24 test(manage_gameobject): add tests for is_static parameter
Tests cover: is_static=True, is_static=False, string coercion
("true" → True), and omission (isStatic excluded from params).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-30 22:38:53 +02:00
zaferdace c18ee94da8 feat: add execute_code tool for running arbitrary C# in Unity Editor
Adds a built-in `execute_code` tool that compiles and runs C# code
inside the Unity Editor via CSharpCodeProvider. No external dependencies
(Roslyn not required), no script files created.

## Actions
- `execute` — compile and run C# method body, return result
- `get_history` — list past executions with previews
- `replay` — re-run a history entry with original settings
- `clear_history` — clear execution history

## Safety
- `safety_checks` (default: true) blocks known dangerous patterns
  (File.Delete, Process.Start, AssetDatabase.DeleteAsset, infinite loops)
- Clearly documented as pattern-based blocklist, NOT a security sandbox
- `destructiveHint=True` annotation for MCP clients

## Features
- In-memory compilation with all loaded assembly references
- User-friendly error line numbers (wrapper offset subtracted)
- Execution history (max 50 entries) with code preview truncation
- Replay preserves original safety_checks setting
- CLI commands: `code execute`, `code history`, `code replay`, `code clear-history`

## Files
- C#: `MCPForUnity/Editor/Tools/ExecuteCode.cs` (329 lines)
- Python: `Server/src/services/tools/execute_code.py` (85 lines)
- CLI: `Server/src/cli/commands/code.py` (+89 lines)
- Tests: `Server/tests/test_execute_code.py` (17 tests, all passing)
- Manifest: added `execute_code` entry

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-29 16:20:28 +01:00
Shutong Wu f4e98d8a4f feat(profiler): rewrite Python MCP tool with 14 actions across 4 groups
Replaces the old 5-action read-only profiler with a comprehensive tool
covering session control, generic counter reads, memory snapshots, and
Frame Debugger. Adds "profiling" as a new opt-in tool group.
2026-03-28 21:55:49 -04:00
zaferdace 42b020b442 fix: address review feedback — correct counter names, CLI help text, strengthen tests 2026-03-28 21:37:55 -04:00
zaferdace 0011a7a5ee feat: add manage_profiler tool for CPU timing, GC alloc, and animation profiling
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-28 21:37:55 -04:00
Shutong Wu e2903a7005 Merge pull request #990 from zaferdace/feat/save-prefab-stage
feat(manage_editor): add save_prefab_stage action
2026-03-28 00:31:56 -04:00
Shutong Wu 53509f525c Update 2026-03-27 01:39:32 -04:00
Shutong Wu da1b700041 Merge branch 'CoplayDev:beta' into pr-980 2026-03-27 00:52:16 -04:00
Shutong Wu fd8a6da7a1 Update docs and CLI usage 2026-03-27 00:51:11 -04:00
zaferdace fea9ab8b5a feat(manage_editor): add save_prefab_stage action
Adds save_prefab_stage to manage_editor to complete the prefab stage
workflow alongside the existing open_prefab_stage and close_prefab_stage.

- C#: SavePrefabStage() uses EditorSceneManager.MarkSceneDirty +
  SaveScene on the prefab stage scene, returns ErrorResponse when no
  stage is open or save fails
- Python: adds save_prefab_stage to action Literal and tool description
- Tests: 3 new tests covering forwarding, description, and clean params

open_prefab_stage was already merged in #968. This PR only adds
save_prefab_stage.
2026-03-26 07:02:26 +00:00
Shutong Wu c718aaa4f4 Initial upload 2026-03-25 00:46:34 -04:00