mcpforunityserver 8.5.0__py3-none-any.whl → 9.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (79) hide show
  1. cli/__init__.py +3 -0
  2. cli/commands/__init__.py +3 -0
  3. cli/commands/animation.py +87 -0
  4. cli/commands/asset.py +310 -0
  5. cli/commands/audio.py +133 -0
  6. cli/commands/batch.py +184 -0
  7. cli/commands/code.py +189 -0
  8. cli/commands/component.py +212 -0
  9. cli/commands/editor.py +487 -0
  10. cli/commands/gameobject.py +510 -0
  11. cli/commands/instance.py +101 -0
  12. cli/commands/lighting.py +128 -0
  13. cli/commands/material.py +268 -0
  14. cli/commands/prefab.py +144 -0
  15. cli/commands/scene.py +255 -0
  16. cli/commands/script.py +240 -0
  17. cli/commands/shader.py +238 -0
  18. cli/commands/ui.py +263 -0
  19. cli/commands/vfx.py +439 -0
  20. cli/main.py +248 -0
  21. cli/utils/__init__.py +31 -0
  22. cli/utils/config.py +58 -0
  23. cli/utils/connection.py +191 -0
  24. cli/utils/output.py +195 -0
  25. main.py +207 -62
  26. {mcpforunityserver-8.5.0.dist-info → mcpforunityserver-9.1.0.dist-info}/METADATA +4 -2
  27. mcpforunityserver-9.1.0.dist-info/RECORD +96 -0
  28. {mcpforunityserver-8.5.0.dist-info → mcpforunityserver-9.1.0.dist-info}/WHEEL +1 -1
  29. {mcpforunityserver-8.5.0.dist-info → mcpforunityserver-9.1.0.dist-info}/entry_points.txt +1 -0
  30. {mcpforunityserver-8.5.0.dist-info → mcpforunityserver-9.1.0.dist-info}/top_level.txt +1 -2
  31. services/custom_tool_service.py +179 -19
  32. services/resources/__init__.py +6 -1
  33. services/resources/active_tool.py +1 -1
  34. services/resources/custom_tools.py +2 -2
  35. services/resources/editor_state.py +283 -21
  36. services/resources/gameobject.py +243 -0
  37. services/resources/layers.py +1 -1
  38. services/resources/prefab_stage.py +1 -1
  39. services/resources/project_info.py +1 -1
  40. services/resources/selection.py +1 -1
  41. services/resources/tags.py +1 -1
  42. services/resources/unity_instances.py +1 -1
  43. services/resources/windows.py +1 -1
  44. services/state/external_changes_scanner.py +245 -0
  45. services/tools/__init__.py +6 -1
  46. services/tools/batch_execute.py +24 -9
  47. services/tools/debug_request_context.py +8 -2
  48. services/tools/execute_custom_tool.py +6 -1
  49. services/tools/execute_menu_item.py +6 -3
  50. services/tools/find_gameobjects.py +89 -0
  51. services/tools/find_in_file.py +26 -19
  52. services/tools/manage_asset.py +19 -43
  53. services/tools/manage_components.py +131 -0
  54. services/tools/manage_editor.py +9 -8
  55. services/tools/manage_gameobject.py +120 -79
  56. services/tools/manage_material.py +80 -31
  57. services/tools/manage_prefabs.py +7 -1
  58. services/tools/manage_scene.py +34 -13
  59. services/tools/manage_script.py +62 -19
  60. services/tools/manage_scriptable_object.py +22 -10
  61. services/tools/manage_shader.py +8 -1
  62. services/tools/manage_vfx.py +738 -0
  63. services/tools/preflight.py +110 -0
  64. services/tools/read_console.py +81 -18
  65. services/tools/refresh_unity.py +153 -0
  66. services/tools/run_tests.py +202 -41
  67. services/tools/script_apply_edits.py +15 -7
  68. services/tools/set_active_instance.py +12 -7
  69. services/tools/utils.py +60 -6
  70. transport/legacy/port_discovery.py +2 -2
  71. transport/legacy/unity_connection.py +129 -26
  72. transport/plugin_hub.py +191 -19
  73. transport/unity_instance_middleware.py +93 -2
  74. transport/unity_transport.py +17 -6
  75. utils/focus_nudge.py +321 -0
  76. __init__.py +0 -0
  77. mcpforunityserver-8.5.0.dist-info/RECORD +0 -66
  78. routes/__init__.py +0 -0
  79. {mcpforunityserver-8.5.0.dist-info → mcpforunityserver-9.1.0.dist-info}/licenses/LICENSE +0 -0
@@ -4,6 +4,7 @@ from typing import Annotated, Any, Literal
4
4
  from urllib.parse import urlparse, unquote
5
5
 
6
6
  from fastmcp import FastMCP, Context
7
+ from mcp.types import ToolAnnotations
7
8
 
8
9
  from services.registry import mcp_for_unity_tool
9
10
  from services.tools import get_unity_instance_from_context
@@ -15,7 +16,7 @@ def _split_uri(uri: str) -> tuple[str, str]:
15
16
  """Split an incoming URI or path into (name, directory) suitable for Unity.
16
17
 
17
18
  Rules:
18
- - unity://path/Assets/... → keep as Assets-relative (after decode/normalize)
19
+ - mcpforunity://path/Assets/... → keep as Assets-relative (after decode/normalize)
19
20
  - file://... → percent-decode, normalize, strip host and leading slashes,
20
21
  then, if any 'Assets' segment exists, return path relative to that 'Assets' root.
21
22
  Otherwise, fall back to original name/dir behavior.
@@ -23,8 +24,8 @@ def _split_uri(uri: str) -> tuple[str, str]:
23
24
  return relative to 'Assets'.
24
25
  """
25
26
  raw_path: str
26
- if uri.startswith("unity://path/"):
27
- raw_path = uri[len("unity://path/"):]
27
+ if uri.startswith("mcpforunity://path/"):
28
+ raw_path = uri[len("mcpforunity://path/"):]
28
29
  elif uri.startswith("file://"):
29
30
  parsed = urlparse(uri)
30
31
  host = (parsed.netloc or "").strip()
@@ -63,8 +64,9 @@ def _split_uri(uri: str) -> tuple[str, str]:
63
64
  return name, directory
64
65
 
65
66
 
66
- @mcp_for_unity_tool(description=(
67
- """Apply small text edits to a C# script identified by URI.
67
+ @mcp_for_unity_tool(
68
+ description=(
69
+ """Apply small text edits to a C# script identified by URI.
68
70
  IMPORTANT: This tool replaces EXACT character positions. Always verify content at target lines/columns BEFORE editing!
69
71
  RECOMMENDED WORKFLOW:
70
72
  1. First call resources/read with start_line/line_count to verify exact content
@@ -76,10 +78,15 @@ def _split_uri(uri: str) -> tuple[str, str]:
76
78
  - For pattern-based replacements, consider anchor operations in script_apply_edits
77
79
  - Lines, columns are 1-indexed
78
80
  - Tabs count as 1 column"""
79
- ))
81
+ ),
82
+ annotations=ToolAnnotations(
83
+ title="Apply Text Edits",
84
+ destructiveHint=True,
85
+ ),
86
+ )
80
87
  async def apply_text_edits(
81
88
  ctx: Context,
82
- uri: Annotated[str, "URI of the script to edit under Assets/ directory, unity://path/Assets/... or file://... or Assets/..."],
89
+ uri: Annotated[str, "URI of the script to edit under Assets/ directory, mcpforunity://path/Assets/... or file://... or Assets/..."],
83
90
  edits: Annotated[list[dict[str, Any]], "List of edits to apply to the script, i.e. a list of {startLine,startCol,endLine,endCol,newText} (1-indexed!)"],
84
91
  precondition_sha256: Annotated[str,
85
92
  "Optional SHA256 of the script to edit, used to prevent concurrent edits"] | None = None,
@@ -367,7 +374,13 @@ async def apply_text_edits(
367
374
  return {"success": False, "message": str(resp)}
368
375
 
369
376
 
370
- @mcp_for_unity_tool(description=("Create a new C# script at the given project path."))
377
+ @mcp_for_unity_tool(
378
+ description="Create a new C# script at the given project path.",
379
+ annotations=ToolAnnotations(
380
+ title="Create Script",
381
+ destructiveHint=True,
382
+ ),
383
+ )
371
384
  async def create_script(
372
385
  ctx: Context,
373
386
  path: Annotated[str, "Path under Assets/ to create the script at, e.g., 'Assets/Scripts/My.cs'"],
@@ -412,10 +425,16 @@ async def create_script(
412
425
  return resp if isinstance(resp, dict) else {"success": False, "message": str(resp)}
413
426
 
414
427
 
415
- @mcp_for_unity_tool(description=("Delete a C# script by URI or Assets-relative path."))
428
+ @mcp_for_unity_tool(
429
+ description="Delete a C# script by URI or Assets-relative path.",
430
+ annotations=ToolAnnotations(
431
+ title="Delete Script",
432
+ destructiveHint=True,
433
+ ),
434
+ )
416
435
  async def delete_script(
417
436
  ctx: Context,
418
- uri: Annotated[str, "URI of the script to delete under Assets/ directory, unity://path/Assets/... or file://... or Assets/..."],
437
+ uri: Annotated[str, "URI of the script to delete under Assets/ directory, mcpforunity://path/Assets/... or file://... or Assets/..."],
419
438
  ) -> dict[str, Any]:
420
439
  """Delete a C# script by URI."""
421
440
  unity_instance = get_unity_instance_from_context(ctx)
@@ -434,10 +453,16 @@ async def delete_script(
434
453
  return resp if isinstance(resp, dict) else {"success": False, "message": str(resp)}
435
454
 
436
455
 
437
- @mcp_for_unity_tool(description=("Validate a C# script and return diagnostics."))
456
+ @mcp_for_unity_tool(
457
+ description="Validate a C# script and return diagnostics.",
458
+ annotations=ToolAnnotations(
459
+ title="Validate Script",
460
+ readOnlyHint=True,
461
+ ),
462
+ )
438
463
  async def validate_script(
439
464
  ctx: Context,
440
- uri: Annotated[str, "URI of the script to validate under Assets/ directory, unity://path/Assets/... or file://... or Assets/..."],
465
+ uri: Annotated[str, "URI of the script to validate under Assets/ directory, mcpforunity://path/Assets/... or file://... or Assets/..."],
441
466
  level: Annotated[Literal['basic', 'standard'],
442
467
  "Validation level"] = "basic",
443
468
  include_diagnostics: Annotated[bool,
@@ -475,14 +500,20 @@ async def validate_script(
475
500
  return resp if isinstance(resp, dict) else {"success": False, "message": str(resp)}
476
501
 
477
502
 
478
- @mcp_for_unity_tool(description=("Compatibility router for legacy script operations. Prefer apply_text_edits (ranges) or script_apply_edits (structured) for edits."))
503
+ @mcp_for_unity_tool(
504
+ description="Compatibility router for legacy script operations. Prefer apply_text_edits (ranges) or script_apply_edits (structured) for edits. Read-only action: read. Modifying actions: create, delete.",
505
+ annotations=ToolAnnotations(
506
+ title="Manage Script",
507
+ destructiveHint=True,
508
+ ),
509
+ )
479
510
  async def manage_script(
480
511
  ctx: Context,
481
512
  action: Annotated[Literal['create', 'read', 'delete'], "Perform CRUD operations on C# scripts."],
482
513
  name: Annotated[str, "Script name (no .cs extension)", "Name of the script to create"],
483
514
  path: Annotated[str, "Asset path (default: 'Assets/')", "Path under Assets/ to create the script at, e.g., 'Assets/Scripts/My.cs'"],
484
515
  contents: Annotated[str, "Contents of the script to create",
485
- "C# code for 'create'/'update'"] | None = None,
516
+ "C# code for 'create' action"] | None = None,
486
517
  script_type: Annotated[str, "Script type (e.g., 'C#')",
487
518
  "Type hint (e.g., 'MonoBehaviour')"] | None = None,
488
519
  namespace: Annotated[str, "Namespace for the script"] | None = None,
@@ -543,14 +574,20 @@ async def manage_script(
543
574
  }
544
575
 
545
576
 
546
- @mcp_for_unity_tool(description=(
547
- """Get manage_script capabilities (supported ops, limits, and guards).
577
+ @mcp_for_unity_tool(
578
+ description=(
579
+ """Get manage_script capabilities (supported ops, limits, and guards).
548
580
  Returns:
549
581
  - ops: list of supported structured ops
550
582
  - text_ops: list of supported text ops
551
583
  - max_edit_payload_bytes: server edit payload cap
552
584
  - guards: header/using guard enabled flag"""
553
- ))
585
+ ),
586
+ annotations=ToolAnnotations(
587
+ title="Manage Script Capabilities",
588
+ readOnlyHint=True,
589
+ ),
590
+ )
554
591
  async def manage_script_capabilities(ctx: Context) -> dict[str, Any]:
555
592
  await ctx.info("Processing manage_script_capabilities")
556
593
  try:
@@ -575,10 +612,16 @@ async def manage_script_capabilities(ctx: Context) -> dict[str, Any]:
575
612
  return {"success": False, "error": f"capabilities error: {e}"}
576
613
 
577
614
 
578
- @mcp_for_unity_tool(description="Get SHA256 and basic metadata for a Unity C# script without returning file contents")
615
+ @mcp_for_unity_tool(
616
+ description="Get SHA256 and basic metadata for a Unity C# script without returning file contents",
617
+ annotations=ToolAnnotations(
618
+ title="Get SHA",
619
+ readOnlyHint=True,
620
+ ),
621
+ )
579
622
  async def get_sha(
580
623
  ctx: Context,
581
- uri: Annotated[str, "URI of the script to edit under Assets/ directory, unity://path/Assets/... or file://... or Assets/..."],
624
+ uri: Annotated[str, "URI of the script to edit under Assets/ directory, mcpforunity://path/Assets/... or file://... or Assets/..."],
582
625
  ) -> dict[str, Any]:
583
626
  unity_instance = get_unity_instance_from_context(ctx)
584
627
  await ctx.info(
@@ -13,6 +13,7 @@ from __future__ import annotations
13
13
  from typing import Annotated, Any, Literal
14
14
 
15
15
  from fastmcp import Context
16
+ from mcp.types import ToolAnnotations
16
17
 
17
18
  from services.registry import mcp_for_unity_tool
18
19
  from services.tools import get_unity_instance_from_context
@@ -22,20 +23,33 @@ from transport.legacy.unity_connection import async_send_command_with_retry
22
23
 
23
24
 
24
25
  @mcp_for_unity_tool(
25
- description="Creates and modifies ScriptableObject assets using Unity SerializedObject property paths."
26
+ description="Creates and modifies ScriptableObject assets using Unity SerializedObject property paths.",
27
+ annotations=ToolAnnotations(
28
+ title="Manage Scriptable Object",
29
+ destructiveHint=True,
30
+ ),
26
31
  )
27
32
  async def manage_scriptable_object(
28
33
  ctx: Context,
29
34
  action: Annotated[Literal["create", "modify"], "Action to perform: create or modify."],
30
35
  # --- create params ---
31
- type_name: Annotated[str | None, "Namespace-qualified ScriptableObject type name (for create)."] = None,
32
- folder_path: Annotated[str | None, "Target folder under Assets/... (for create)."] = None,
33
- asset_name: Annotated[str | None, "Asset file name without extension (for create)."] = None,
34
- overwrite: Annotated[bool | str | None, "If true, overwrite existing asset at same path (for create)."] = None,
36
+ type_name: Annotated[str | None,
37
+ "Namespace-qualified ScriptableObject type name (for create)."] = None,
38
+ folder_path: Annotated[str | None,
39
+ "Target folder under Assets/... (for create)."] = None,
40
+ asset_name: Annotated[str | None,
41
+ "Asset file name without extension (for create)."] = None,
42
+ overwrite: Annotated[bool | str | None,
43
+ "If true, overwrite existing asset at same path (for create)."] = None,
35
44
  # --- modify params ---
36
- target: Annotated[dict[str, Any] | str | None, "Target asset reference {guid|path} (for modify)."] = None,
45
+ target: Annotated[dict[str, Any] | str | None,
46
+ "Target asset reference {guid|path} (for modify)."] = None,
37
47
  # --- shared ---
38
- patches: Annotated[list[dict[str, Any]] | str | None, "Patch list (or JSON string) to apply."] = None,
48
+ patches: Annotated[list[dict[str, Any]] | str | None,
49
+ "Patch list (or JSON string) to apply."] = None,
50
+ # --- validation ---
51
+ dry_run: Annotated[bool | str | None,
52
+ "If true, validate patches without applying (modify only)."] = None,
39
53
  ) -> dict[str, Any]:
40
54
  unity_instance = get_unity_instance_from_context(ctx)
41
55
 
@@ -57,6 +71,7 @@ async def manage_scriptable_object(
57
71
  "overwrite": coerce_bool(overwrite, default=None),
58
72
  "target": parsed_target,
59
73
  "patches": parsed_patches,
74
+ "dryRun": coerce_bool(dry_run, default=None),
60
75
  }
61
76
 
62
77
  # Remove None values to keep Unity handler simpler
@@ -70,6 +85,3 @@ async def manage_scriptable_object(
70
85
  )
71
86
  await ctx.info(f"Response {response}")
72
87
  return response if isinstance(response, dict) else {"success": False, "message": "Unexpected response from Unity."}
73
-
74
-
75
-
@@ -2,6 +2,8 @@ import base64
2
2
  from typing import Annotated, Any, Literal
3
3
 
4
4
  from fastmcp import Context
5
+ from mcp.types import ToolAnnotations
6
+
5
7
  from services.registry import mcp_for_unity_tool
6
8
  from services.tools import get_unity_instance_from_context
7
9
  from transport.unity_transport import send_with_unity_instance
@@ -9,7 +11,12 @@ from transport.legacy.unity_connection import async_send_command_with_retry
9
11
 
10
12
 
11
13
  @mcp_for_unity_tool(
12
- description="Manages shader scripts in Unity (create, read, update, delete)."
14
+ description="Manages shader scripts in Unity (create, read, update, delete). Read-only action: read. Modifying actions: create, update, delete.",
15
+ annotations=ToolAnnotations(
16
+ title="Manage Shader",
17
+ # Note: 'read' action is non-destructive; 'create', 'update', 'delete' are destructive
18
+ destructiveHint=True,
19
+ ),
13
20
  )
14
21
  async def manage_shader(
15
22
  ctx: Context,