#
tokens: 49316/50000 122/263 files (page 1/13)
lines: off (toggle) GitHub
raw markdown copy
This is page 1 of 13. Use http://codebase.md/justinpbarnett/unity-mcp?page={x} to view the full context.

# Directory Structure

```
├── .claude
│   ├── prompts
│   │   ├── nl-unity-suite-nl.md
│   │   └── nl-unity-suite-t.md
│   └── settings.json
├── .github
│   ├── scripts
│   │   └── mark_skipped.py
│   └── workflows
│       ├── bump-version.yml
│       ├── claude-nl-suite.yml
│       ├── github-repo-stats.yml
│       └── unity-tests.yml
├── .gitignore
├── deploy-dev.bat
├── docs
│   ├── CURSOR_HELP.md
│   ├── CUSTOM_TOOLS.md
│   ├── README-DEV-zh.md
│   ├── README-DEV.md
│   ├── screenshots
│   │   ├── v5_01_uninstall.png
│   │   ├── v5_02_install.png
│   │   ├── v5_03_open_mcp_window.png
│   │   ├── v5_04_rebuild_mcp_server.png
│   │   ├── v5_05_rebuild_success.png
│   │   ├── v6_2_create_python_tools_asset.png
│   │   ├── v6_2_python_tools_asset.png
│   │   ├── v6_new_ui_asset_store_version.png
│   │   ├── v6_new_ui_dark.png
│   │   └── v6_new_ui_light.png
│   ├── TELEMETRY.md
│   ├── v5_MIGRATION.md
│   └── v6_NEW_UI_CHANGES.md
├── LICENSE
├── logo.png
├── mcp_source.py
├── MCPForUnity
│   ├── Editor
│   │   ├── AssemblyInfo.cs
│   │   ├── AssemblyInfo.cs.meta
│   │   ├── Data
│   │   │   ├── DefaultServerConfig.cs
│   │   │   ├── DefaultServerConfig.cs.meta
│   │   │   ├── McpClients.cs
│   │   │   ├── McpClients.cs.meta
│   │   │   ├── PythonToolsAsset.cs
│   │   │   └── PythonToolsAsset.cs.meta
│   │   ├── Data.meta
│   │   ├── Dependencies
│   │   │   ├── DependencyManager.cs
│   │   │   ├── DependencyManager.cs.meta
│   │   │   ├── Models
│   │   │   │   ├── DependencyCheckResult.cs
│   │   │   │   ├── DependencyCheckResult.cs.meta
│   │   │   │   ├── DependencyStatus.cs
│   │   │   │   └── DependencyStatus.cs.meta
│   │   │   ├── Models.meta
│   │   │   ├── PlatformDetectors
│   │   │   │   ├── IPlatformDetector.cs
│   │   │   │   ├── IPlatformDetector.cs.meta
│   │   │   │   ├── LinuxPlatformDetector.cs
│   │   │   │   ├── LinuxPlatformDetector.cs.meta
│   │   │   │   ├── MacOSPlatformDetector.cs
│   │   │   │   ├── MacOSPlatformDetector.cs.meta
│   │   │   │   ├── PlatformDetectorBase.cs
│   │   │   │   ├── PlatformDetectorBase.cs.meta
│   │   │   │   ├── WindowsPlatformDetector.cs
│   │   │   │   └── WindowsPlatformDetector.cs.meta
│   │   │   └── PlatformDetectors.meta
│   │   ├── Dependencies.meta
│   │   ├── External
│   │   │   ├── Tommy.cs
│   │   │   └── Tommy.cs.meta
│   │   ├── External.meta
│   │   ├── Helpers
│   │   │   ├── AssetPathUtility.cs
│   │   │   ├── AssetPathUtility.cs.meta
│   │   │   ├── CodexConfigHelper.cs
│   │   │   ├── CodexConfigHelper.cs.meta
│   │   │   ├── ConfigJsonBuilder.cs
│   │   │   ├── ConfigJsonBuilder.cs.meta
│   │   │   ├── ExecPath.cs
│   │   │   ├── ExecPath.cs.meta
│   │   │   ├── GameObjectSerializer.cs
│   │   │   ├── GameObjectSerializer.cs.meta
│   │   │   ├── McpConfigFileHelper.cs
│   │   │   ├── McpConfigFileHelper.cs.meta
│   │   │   ├── McpConfigurationHelper.cs
│   │   │   ├── McpConfigurationHelper.cs.meta
│   │   │   ├── McpLog.cs
│   │   │   ├── McpLog.cs.meta
│   │   │   ├── McpPathResolver.cs
│   │   │   ├── McpPathResolver.cs.meta
│   │   │   ├── PackageDetector.cs
│   │   │   ├── PackageDetector.cs.meta
│   │   │   ├── PackageInstaller.cs
│   │   │   ├── PackageInstaller.cs.meta
│   │   │   ├── PortManager.cs
│   │   │   ├── PortManager.cs.meta
│   │   │   ├── PythonToolSyncProcessor.cs
│   │   │   ├── PythonToolSyncProcessor.cs.meta
│   │   │   ├── Response.cs
│   │   │   ├── Response.cs.meta
│   │   │   ├── ServerInstaller.cs
│   │   │   ├── ServerInstaller.cs.meta
│   │   │   ├── ServerPathResolver.cs
│   │   │   ├── ServerPathResolver.cs.meta
│   │   │   ├── TelemetryHelper.cs
│   │   │   ├── TelemetryHelper.cs.meta
│   │   │   ├── Vector3Helper.cs
│   │   │   └── Vector3Helper.cs.meta
│   │   ├── Helpers.meta
│   │   ├── Importers
│   │   │   ├── PythonFileImporter.cs
│   │   │   └── PythonFileImporter.cs.meta
│   │   ├── Importers.meta
│   │   ├── MCPForUnity.Editor.asmdef
│   │   ├── MCPForUnity.Editor.asmdef.meta
│   │   ├── MCPForUnityBridge.cs
│   │   ├── MCPForUnityBridge.cs.meta
│   │   ├── Models
│   │   │   ├── Command.cs
│   │   │   ├── Command.cs.meta
│   │   │   ├── McpClient.cs
│   │   │   ├── McpClient.cs.meta
│   │   │   ├── McpConfig.cs
│   │   │   ├── McpConfig.cs.meta
│   │   │   ├── MCPConfigServer.cs
│   │   │   ├── MCPConfigServer.cs.meta
│   │   │   ├── MCPConfigServers.cs
│   │   │   ├── MCPConfigServers.cs.meta
│   │   │   ├── McpStatus.cs
│   │   │   ├── McpStatus.cs.meta
│   │   │   ├── McpTypes.cs
│   │   │   ├── McpTypes.cs.meta
│   │   │   ├── ServerConfig.cs
│   │   │   └── ServerConfig.cs.meta
│   │   ├── Models.meta
│   │   ├── Resources
│   │   │   ├── McpForUnityResourceAttribute.cs
│   │   │   ├── McpForUnityResourceAttribute.cs.meta
│   │   │   ├── MenuItems
│   │   │   │   ├── GetMenuItems.cs
│   │   │   │   └── GetMenuItems.cs.meta
│   │   │   ├── MenuItems.meta
│   │   │   ├── Tests
│   │   │   │   ├── GetTests.cs
│   │   │   │   └── GetTests.cs.meta
│   │   │   └── Tests.meta
│   │   ├── Resources.meta
│   │   ├── Services
│   │   │   ├── BridgeControlService.cs
│   │   │   ├── BridgeControlService.cs.meta
│   │   │   ├── ClientConfigurationService.cs
│   │   │   ├── ClientConfigurationService.cs.meta
│   │   │   ├── IBridgeControlService.cs
│   │   │   ├── IBridgeControlService.cs.meta
│   │   │   ├── IClientConfigurationService.cs
│   │   │   ├── IClientConfigurationService.cs.meta
│   │   │   ├── IPackageUpdateService.cs
│   │   │   ├── IPackageUpdateService.cs.meta
│   │   │   ├── IPathResolverService.cs
│   │   │   ├── IPathResolverService.cs.meta
│   │   │   ├── IPythonToolRegistryService.cs
│   │   │   ├── IPythonToolRegistryService.cs.meta
│   │   │   ├── ITestRunnerService.cs
│   │   │   ├── ITestRunnerService.cs.meta
│   │   │   ├── IToolSyncService.cs
│   │   │   ├── IToolSyncService.cs.meta
│   │   │   ├── MCPServiceLocator.cs
│   │   │   ├── MCPServiceLocator.cs.meta
│   │   │   ├── PackageUpdateService.cs
│   │   │   ├── PackageUpdateService.cs.meta
│   │   │   ├── PathResolverService.cs
│   │   │   ├── PathResolverService.cs.meta
│   │   │   ├── PythonToolRegistryService.cs
│   │   │   ├── PythonToolRegistryService.cs.meta
│   │   │   ├── TestRunnerService.cs
│   │   │   ├── TestRunnerService.cs.meta
│   │   │   ├── ToolSyncService.cs
│   │   │   └── ToolSyncService.cs.meta
│   │   ├── Services.meta
│   │   ├── Setup
│   │   │   ├── SetupWizard.cs
│   │   │   ├── SetupWizard.cs.meta
│   │   │   ├── SetupWizardWindow.cs
│   │   │   └── SetupWizardWindow.cs.meta
│   │   ├── Setup.meta
│   │   ├── Tools
│   │   │   ├── CommandRegistry.cs
│   │   │   ├── CommandRegistry.cs.meta
│   │   │   ├── ExecuteMenuItem.cs
│   │   │   ├── ExecuteMenuItem.cs.meta
│   │   │   ├── ManageAsset.cs
│   │   │   ├── ManageAsset.cs.meta
│   │   │   ├── ManageEditor.cs
│   │   │   ├── ManageEditor.cs.meta
│   │   │   ├── ManageGameObject.cs
│   │   │   ├── ManageGameObject.cs.meta
│   │   │   ├── ManageScene.cs
│   │   │   ├── ManageScene.cs.meta
│   │   │   ├── ManageScript.cs
│   │   │   ├── ManageScript.cs.meta
│   │   │   ├── ManageShader.cs
│   │   │   ├── ManageShader.cs.meta
│   │   │   ├── McpForUnityToolAttribute.cs
│   │   │   ├── McpForUnityToolAttribute.cs.meta
│   │   │   ├── Prefabs
│   │   │   │   ├── ManagePrefabs.cs
│   │   │   │   └── ManagePrefabs.cs.meta
│   │   │   ├── Prefabs.meta
│   │   │   ├── ReadConsole.cs
│   │   │   ├── ReadConsole.cs.meta
│   │   │   ├── RunTests.cs
│   │   │   └── RunTests.cs.meta
│   │   ├── Tools.meta
│   │   ├── Windows
│   │   │   ├── ManualConfigEditorWindow.cs
│   │   │   ├── ManualConfigEditorWindow.cs.meta
│   │   │   ├── MCPForUnityEditorWindow.cs
│   │   │   ├── MCPForUnityEditorWindow.cs.meta
│   │   │   ├── MCPForUnityEditorWindowNew.cs
│   │   │   ├── MCPForUnityEditorWindowNew.cs.meta
│   │   │   ├── MCPForUnityEditorWindowNew.uss
│   │   │   ├── MCPForUnityEditorWindowNew.uss.meta
│   │   │   ├── MCPForUnityEditorWindowNew.uxml
│   │   │   ├── MCPForUnityEditorWindowNew.uxml.meta
│   │   │   ├── VSCodeManualSetupWindow.cs
│   │   │   └── VSCodeManualSetupWindow.cs.meta
│   │   └── Windows.meta
│   ├── Editor.meta
│   ├── package.json
│   ├── package.json.meta
│   ├── README.md
│   ├── README.md.meta
│   ├── Runtime
│   │   ├── MCPForUnity.Runtime.asmdef
│   │   ├── MCPForUnity.Runtime.asmdef.meta
│   │   ├── Serialization
│   │   │   ├── UnityTypeConverters.cs
│   │   │   └── UnityTypeConverters.cs.meta
│   │   └── Serialization.meta
│   ├── Runtime.meta
│   └── UnityMcpServer~
│       └── src
│           ├── __init__.py
│           ├── config.py
│           ├── Dockerfile
│           ├── models.py
│           ├── module_discovery.py
│           ├── port_discovery.py
│           ├── pyproject.toml
│           ├── pyrightconfig.json
│           ├── registry
│           │   ├── __init__.py
│           │   ├── resource_registry.py
│           │   └── tool_registry.py
│           ├── reload_sentinel.py
│           ├── resources
│           │   ├── __init__.py
│           │   ├── menu_items.py
│           │   └── tests.py
│           ├── server_version.txt
│           ├── server.py
│           ├── telemetry_decorator.py
│           ├── telemetry.py
│           ├── test_telemetry.py
│           ├── tools
│           │   ├── __init__.py
│           │   ├── execute_menu_item.py
│           │   ├── manage_asset.py
│           │   ├── manage_editor.py
│           │   ├── manage_gameobject.py
│           │   ├── manage_prefabs.py
│           │   ├── manage_scene.py
│           │   ├── manage_script.py
│           │   ├── manage_shader.py
│           │   ├── read_console.py
│           │   ├── resource_tools.py
│           │   ├── run_tests.py
│           │   └── script_apply_edits.py
│           ├── unity_connection.py
│           └── uv.lock
├── prune_tool_results.py
├── README-zh.md
├── README.md
├── restore-dev.bat
├── scripts
│   └── validate-nlt-coverage.sh
├── test_unity_socket_framing.py
├── TestProjects
│   └── UnityMCPTests
│       ├── .gitignore
│       ├── Assets
│       │   ├── Editor.meta
│       │   ├── Scenes
│       │   │   ├── SampleScene.unity
│       │   │   └── SampleScene.unity.meta
│       │   ├── Scenes.meta
│       │   ├── Scripts
│       │   │   ├── Hello.cs
│       │   │   ├── Hello.cs.meta
│       │   │   ├── LongUnityScriptClaudeTest.cs
│       │   │   ├── LongUnityScriptClaudeTest.cs.meta
│       │   │   ├── TestAsmdef
│       │   │   │   ├── CustomComponent.cs
│       │   │   │   ├── CustomComponent.cs.meta
│       │   │   │   ├── TestAsmdef.asmdef
│       │   │   │   └── TestAsmdef.asmdef.meta
│       │   │   └── TestAsmdef.meta
│       │   ├── Scripts.meta
│       │   ├── Tests
│       │   │   ├── EditMode
│       │   │   │   ├── Data
│       │   │   │   │   ├── PythonToolsAssetTests.cs
│       │   │   │   │   └── PythonToolsAssetTests.cs.meta
│       │   │   │   ├── Data.meta
│       │   │   │   ├── Helpers
│       │   │   │   │   ├── CodexConfigHelperTests.cs
│       │   │   │   │   ├── CodexConfigHelperTests.cs.meta
│       │   │   │   │   ├── WriteToConfigTests.cs
│       │   │   │   │   └── WriteToConfigTests.cs.meta
│       │   │   │   ├── Helpers.meta
│       │   │   │   ├── MCPForUnityTests.Editor.asmdef
│       │   │   │   ├── MCPForUnityTests.Editor.asmdef.meta
│       │   │   │   ├── Resources
│       │   │   │   │   ├── GetMenuItemsTests.cs
│       │   │   │   │   └── GetMenuItemsTests.cs.meta
│       │   │   │   ├── Resources.meta
│       │   │   │   ├── Services
│       │   │   │   │   ├── PackageUpdateServiceTests.cs
│       │   │   │   │   ├── PackageUpdateServiceTests.cs.meta
│       │   │   │   │   ├── PythonToolRegistryServiceTests.cs
│       │   │   │   │   ├── PythonToolRegistryServiceTests.cs.meta
│       │   │   │   │   ├── ToolSyncServiceTests.cs
│       │   │   │   │   └── ToolSyncServiceTests.cs.meta
│       │   │   │   ├── Services.meta
│       │   │   │   ├── Tools
│       │   │   │   │   ├── AIPropertyMatchingTests.cs
│       │   │   │   │   ├── AIPropertyMatchingTests.cs.meta
│       │   │   │   │   ├── CommandRegistryTests.cs
│       │   │   │   │   ├── CommandRegistryTests.cs.meta
│       │   │   │   │   ├── ComponentResolverTests.cs
│       │   │   │   │   ├── ComponentResolverTests.cs.meta
│       │   │   │   │   ├── ExecuteMenuItemTests.cs
│       │   │   │   │   ├── ExecuteMenuItemTests.cs.meta
│       │   │   │   │   ├── ManageGameObjectTests.cs
│       │   │   │   │   ├── ManageGameObjectTests.cs.meta
│       │   │   │   │   ├── ManagePrefabsTests.cs
│       │   │   │   │   ├── ManagePrefabsTests.cs.meta
│       │   │   │   │   ├── ManageScriptValidationTests.cs
│       │   │   │   │   └── ManageScriptValidationTests.cs.meta
│       │   │   │   ├── Tools.meta
│       │   │   │   ├── Windows
│       │   │   │   │   ├── ManualConfigJsonBuilderTests.cs
│       │   │   │   │   └── ManualConfigJsonBuilderTests.cs.meta
│       │   │   │   └── Windows.meta
│       │   │   └── EditMode.meta
│       │   └── Tests.meta
│       ├── Packages
│       │   └── manifest.json
│       └── ProjectSettings
│           ├── Packages
│           │   └── com.unity.testtools.codecoverage
│           │       └── Settings.json
│           └── ProjectVersion.txt
├── tests
│   ├── conftest.py
│   ├── test_edit_normalization_and_noop.py
│   ├── test_edit_strict_and_warnings.py
│   ├── test_find_in_file_minimal.py
│   ├── test_get_sha.py
│   ├── test_improved_anchor_matching.py
│   ├── test_logging_stdout.py
│   ├── test_manage_script_uri.py
│   ├── test_read_console_truncate.py
│   ├── test_read_resource_minimal.py
│   ├── test_resources_api.py
│   ├── test_script_editing.py
│   ├── test_script_tools.py
│   ├── test_telemetry_endpoint_validation.py
│   ├── test_telemetry_queue_worker.py
│   ├── test_telemetry_subaction.py
│   ├── test_transport_framing.py
│   └── test_validate_script_summary.py
├── tools
│   └── stress_mcp.py
└── UnityMcpBridge
    ├── Editor
    │   ├── AssemblyInfo.cs
    │   ├── AssemblyInfo.cs.meta
    │   ├── Data
    │   │   ├── DefaultServerConfig.cs
    │   │   ├── DefaultServerConfig.cs.meta
    │   │   ├── McpClients.cs
    │   │   └── McpClients.cs.meta
    │   ├── Data.meta
    │   ├── Dependencies
    │   │   ├── DependencyManager.cs
    │   │   ├── DependencyManager.cs.meta
    │   │   ├── Models
    │   │   │   ├── DependencyCheckResult.cs
    │   │   │   ├── DependencyCheckResult.cs.meta
    │   │   │   ├── DependencyStatus.cs
    │   │   │   └── DependencyStatus.cs.meta
    │   │   ├── Models.meta
    │   │   ├── PlatformDetectors
    │   │   │   ├── IPlatformDetector.cs
    │   │   │   ├── IPlatformDetector.cs.meta
    │   │   │   ├── LinuxPlatformDetector.cs
    │   │   │   ├── LinuxPlatformDetector.cs.meta
    │   │   │   ├── MacOSPlatformDetector.cs
    │   │   │   ├── MacOSPlatformDetector.cs.meta
    │   │   │   ├── PlatformDetectorBase.cs
    │   │   │   ├── PlatformDetectorBase.cs.meta
    │   │   │   ├── WindowsPlatformDetector.cs
    │   │   │   └── WindowsPlatformDetector.cs.meta
    │   │   └── PlatformDetectors.meta
    │   ├── Dependencies.meta
    │   ├── External
    │   │   ├── Tommy.cs
    │   │   └── Tommy.cs.meta
    │   ├── External.meta
    │   ├── Helpers
    │   │   ├── AssetPathUtility.cs
    │   │   ├── AssetPathUtility.cs.meta
    │   │   ├── CodexConfigHelper.cs
    │   │   ├── CodexConfigHelper.cs.meta
    │   │   ├── ConfigJsonBuilder.cs
    │   │   ├── ConfigJsonBuilder.cs.meta
    │   │   ├── ExecPath.cs
    │   │   ├── ExecPath.cs.meta
    │   │   ├── GameObjectSerializer.cs
    │   │   ├── GameObjectSerializer.cs.meta
    │   │   ├── McpConfigFileHelper.cs
    │   │   ├── McpConfigFileHelper.cs.meta
    │   │   ├── McpConfigurationHelper.cs
    │   │   ├── McpConfigurationHelper.cs.meta
    │   │   ├── McpLog.cs
    │   │   ├── McpLog.cs.meta
    │   │   ├── McpPathResolver.cs
    │   │   ├── McpPathResolver.cs.meta
    │   │   ├── PackageDetector.cs
    │   │   ├── PackageDetector.cs.meta
    │   │   ├── PackageInstaller.cs
    │   │   ├── PackageInstaller.cs.meta
    │   │   ├── PortManager.cs
    │   │   ├── PortManager.cs.meta
    │   │   ├── Response.cs
    │   │   ├── Response.cs.meta
    │   │   ├── ServerInstaller.cs
    │   │   ├── ServerInstaller.cs.meta
    │   │   ├── ServerPathResolver.cs
    │   │   ├── ServerPathResolver.cs.meta
    │   │   ├── TelemetryHelper.cs
    │   │   ├── TelemetryHelper.cs.meta
    │   │   ├── Vector3Helper.cs
    │   │   └── Vector3Helper.cs.meta
    │   ├── Helpers.meta
    │   ├── MCPForUnity.Editor.asmdef
    │   ├── MCPForUnity.Editor.asmdef.meta
    │   ├── MCPForUnityBridge.cs
    │   ├── MCPForUnityBridge.cs.meta
    │   ├── Models
    │   │   ├── Command.cs
    │   │   ├── Command.cs.meta
    │   │   ├── McpClient.cs
    │   │   ├── McpClient.cs.meta
    │   │   ├── McpConfig.cs
    │   │   ├── McpConfig.cs.meta
    │   │   ├── MCPConfigServer.cs
    │   │   ├── MCPConfigServer.cs.meta
    │   │   ├── MCPConfigServers.cs
    │   │   ├── MCPConfigServers.cs.meta
    │   │   ├── McpStatus.cs
    │   │   ├── McpStatus.cs.meta
    │   │   ├── McpTypes.cs
    │   │   ├── McpTypes.cs.meta
    │   │   ├── ServerConfig.cs
    │   │   └── ServerConfig.cs.meta
    │   ├── Models.meta
    │   ├── Setup
    │   │   ├── SetupWizard.cs
    │   │   ├── SetupWizard.cs.meta
    │   │   ├── SetupWizardWindow.cs
    │   │   └── SetupWizardWindow.cs.meta
    │   ├── Setup.meta
    │   ├── Tools
    │   │   ├── CommandRegistry.cs
    │   │   ├── CommandRegistry.cs.meta
    │   │   ├── ManageAsset.cs
    │   │   ├── ManageAsset.cs.meta
    │   │   ├── ManageEditor.cs
    │   │   ├── ManageEditor.cs.meta
    │   │   ├── ManageGameObject.cs
    │   │   ├── ManageGameObject.cs.meta
    │   │   ├── ManageScene.cs
    │   │   ├── ManageScene.cs.meta
    │   │   ├── ManageScript.cs
    │   │   ├── ManageScript.cs.meta
    │   │   ├── ManageShader.cs
    │   │   ├── ManageShader.cs.meta
    │   │   ├── McpForUnityToolAttribute.cs
    │   │   ├── McpForUnityToolAttribute.cs.meta
    │   │   ├── MenuItems
    │   │   │   ├── ManageMenuItem.cs
    │   │   │   ├── ManageMenuItem.cs.meta
    │   │   │   ├── MenuItemExecutor.cs
    │   │   │   ├── MenuItemExecutor.cs.meta
    │   │   │   ├── MenuItemsReader.cs
    │   │   │   └── MenuItemsReader.cs.meta
    │   │   ├── MenuItems.meta
    │   │   ├── Prefabs
    │   │   │   ├── ManagePrefabs.cs
    │   │   │   └── ManagePrefabs.cs.meta
    │   │   ├── Prefabs.meta
    │   │   ├── ReadConsole.cs
    │   │   └── ReadConsole.cs.meta
    │   ├── Tools.meta
    │   ├── Windows
    │   │   ├── ManualConfigEditorWindow.cs
    │   │   ├── ManualConfigEditorWindow.cs.meta
    │   │   ├── MCPForUnityEditorWindow.cs
    │   │   ├── MCPForUnityEditorWindow.cs.meta
    │   │   ├── VSCodeManualSetupWindow.cs
    │   │   └── VSCodeManualSetupWindow.cs.meta
    │   └── Windows.meta
    ├── Editor.meta
    ├── package.json
    ├── package.json.meta
    ├── README.md
    ├── README.md.meta
    ├── Runtime
    │   ├── MCPForUnity.Runtime.asmdef
    │   ├── MCPForUnity.Runtime.asmdef.meta
    │   ├── Serialization
    │   │   ├── UnityTypeConverters.cs
    │   │   └── UnityTypeConverters.cs.meta
    │   └── Serialization.meta
    ├── Runtime.meta
    └── UnityMcpServer~
        └── src
            ├── __init__.py
            ├── config.py
            ├── Dockerfile
            ├── port_discovery.py
            ├── pyproject.toml
            ├── pyrightconfig.json
            ├── registry
            │   ├── __init__.py
            │   └── tool_registry.py
            ├── reload_sentinel.py
            ├── server_version.txt
            ├── server.py
            ├── telemetry_decorator.py
            ├── telemetry.py
            ├── test_telemetry.py
            ├── tools
            │   ├── __init__.py
            │   ├── manage_asset.py
            │   ├── manage_editor.py
            │   ├── manage_gameobject.py
            │   ├── manage_menu_item.py
            │   ├── manage_prefabs.py
            │   ├── manage_scene.py
            │   ├── manage_script.py
            │   ├── manage_shader.py
            │   ├── read_console.py
            │   ├── resource_tools.py
            │   └── script_apply_edits.py
            ├── unity_connection.py
            └── uv.lock
```

# Files

--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------

```
# AI-related files
.cursorrules
.cursorignore
.windsurf
.codeiumignore
.kiro
CLAUDE.md

# Code-copy related files
.clipignore

# Python-generated files
__pycache__/
__pycache__.meta
build/
dist/
wheels/
*.egg-info
UnityMcpServer/**/*.meta
UnityMcpServer.meta

# Virtual environments
.venv

# Unity Editor
*.unitypackage
*.asset
LICENSE.meta
CONTRIBUTING.md.meta

# IDE
.idea/
.vscode/
.aider*
.DS_Store*
# Unity test project lock files
TestProjects/UnityMCPTests/Packages/packages-lock.json

# Backup artifacts
*.backup
*.backup.meta

```

--------------------------------------------------------------------------------
/TestProjects/UnityMCPTests/.gitignore:
--------------------------------------------------------------------------------

```
# This .gitignore file should be placed at the root of your Unity project directory
#
# Get latest from https://github.com/github/gitignore/blob/main/Unity.gitignore
#
.utmp/
/[Ll]ibrary/
/[Tt]emp/
/[Oo]bj/
/[Bb]uild/
/[Bb]uilds/
/[Ll]ogs/
/[Uu]ser[Ss]ettings/
*.log

# By default unity supports Blender asset imports, *.blend1 blender files do not need to be commited to version control.
*.blend1
*.blend1.meta

# MemoryCaptures can get excessive in size.
# They also could contain extremely sensitive data
/[Mm]emoryCaptures/

# Recordings can get excessive in size
/[Rr]ecordings/

# Uncomment this line if you wish to ignore the asset store tools plugin
# /[Aa]ssets/AssetStoreTools*

# Autogenerated Jetbrains Rider plugin
/[Aa]ssets/Plugins/Editor/JetBrains*
# Jetbrains Rider personal-layer settings
*.DotSettings.user

# Visual Studio cache directory
.vs/

# Gradle cache directory
.gradle/

# Autogenerated VS/MD/Consulo solution and project files
ExportedObj/
.consulo/
*.csproj
*.unityproj
*.sln
*.suo
*.tmp
*.user
*.userprefs
*.pidb
*.booproj
*.svd
*.pdb
*.mdb
*.opendb
*.VC.db

# Unity3D generated meta files
*.pidb.meta
*.pdb.meta
*.mdb.meta

# Unity3D generated file on crash reports
sysinfo.txt

# Mono auto generated files
mono_crash.*

# Builds
*.apk
*.aab
*.unitypackage
*.unitypackage.meta
*.app

# Crashlytics generated file
crashlytics-build.properties

# TestRunner generated files
InitTestScene*.unity*

# Addressables default ignores, before user customizations
/ServerData
/[Aa]ssets/StreamingAssets/aa*
/[Aa]ssets/AddressableAssetsData/link.xml*
/[Aa]ssets/Addressables_Temp*
# By default, Addressables content builds will generate addressables_content_state.bin
# files in platform-specific subfolders, for example:
# /Assets/AddressableAssetsData/OSX/addressables_content_state.bin
/[Aa]ssets/AddressableAssetsData/*/*.bin*

# Visual Scripting auto-generated files
/[Aa]ssets/Unity.VisualScripting.Generated/VisualScripting.Flow/UnitOptions.db
/[Aa]ssets/Unity.VisualScripting.Generated/VisualScripting.Flow/UnitOptions.db.meta
/[Aa]ssets/Unity.VisualScripting.Generated/VisualScripting.Core/Property Providers
/[Aa]ssets/Unity.VisualScripting.Generated/VisualScripting.Core/Property Providers.meta

# Auto-generated scenes by play mode tests
/[Aa]ssets/[Ii]nit[Tt]est[Ss]cene*.unity*

.vscode
.cursor
.windsurf
.claude
.DS_Store

```

--------------------------------------------------------------------------------
/MCPForUnity/README.md:
--------------------------------------------------------------------------------

```markdown
# MCP for Unity — Editor Plugin Guide

Use this guide to configure and run MCP for Unity inside the Unity Editor. Installation is covered elsewhere; this document focuses on the Editor window, client configuration, and troubleshooting.

## Open the window
- Unity menu: Window > MCP for Unity

The window has four areas: Server Status, Unity Bridge, MCP Client Configuration, and Script Validation.

---

## Quick start
1. Open Window > MCP for Unity.
2. Click “Auto-Setup”.
3. If prompted:
   - Select the server folder that contains `server.py` (UnityMcpServer~/src).
   - Install Python and/or uv if missing.
   - For Claude Code, ensure the `claude` CLI is installed.
4. Click “Start Bridge” if the Unity Bridge shows “Stopped”.
5. Use your MCP client (Cursor, VS Code, Windsurf, Claude Code) to connect.

---

## Server Status
- Status dot and label:
  - Installed / Installed (Embedded) / Not Installed.
- Mode and ports:
  - Mode: Auto or Standard.
  - Ports: Unity (varies; shown in UI), MCP 6500.
- Actions:
  - Auto-Setup: Registers/updates your selected MCP client(s), ensures bridge connectivity. Shows “Connected ✓” after success.
  - Rebuild MCP Server: Rebuilds the Python based MCP server
  - Select server folder…: Choose the folder containing `server.py`.
  - Verify again: Re-checks server presence.
  - If Python isn’t detected, use “Open Install Instructions”.

---

## Unity Bridge
- Shows Running or Stopped with a status dot.
- Start/Stop Bridge button toggles the Unity bridge process used by MCP clients to talk to Unity.
- Tip: After Auto-Setup, the bridge may auto-start in Auto mode.

---

## MCP Client Configuration
- Select Client: Choose your target MCP client (e.g., Cursor, VS Code, Windsurf, Claude Code).
- Per-client actions:
  - Cursor / VS Code / Windsurf:
    - Auto Configure: Writes/updates your config to launch the server via uv:
      - Command: uv
      - Args: run --directory <pythonDir> server.py
    - Manual Setup: Opens a window with a pre-filled JSON snippet to copy/paste into your client config.
    - Choose `uv` Install Location: If uv isn’t on PATH, select the uv binary.
    - A compact “Config:” line shows the resolved config file name once uv/server are detected.
  - Claude Code:
    - Register with Claude Code / Unregister MCP for Unity with Claude Code.
    - If the CLI isn’t found, click “Choose Claude Install Location”.
    - The window displays the resolved Claude CLI path when detected.

Notes:
- The UI shows a status dot and a short status text (e.g., “Configured”, “uv Not Found”, “Claude Not Found”).
- Use “Auto Configure” for one-click setup; use “Manual Setup” when you prefer to review/copy config.

---

## Script Validation
- Validation Level options:
  - Basic — Only syntax checks
  - Standard — Syntax + Unity practices
  - Comprehensive — All checks + semantic analysis
  - Strict — Full semantic validation (requires Roslyn)
- Pick a level based on your project’s needs. A description is shown under the dropdown.

---

## Troubleshooting
- Python or `uv` not found:
  - Help: [Fix MCP for Unity with Cursor, VS Code & Windsurf](https://github.com/CoplayDev/unity-mcp/wiki/1.-Fix-Unity-MCP-and-Cursor,-VSCode-&-Windsurf)
- Claude CLI not found:
  - Help: [Fix MCP for Unity with Claude Code](https://github.com/CoplayDev/unity-mcp/wiki/2.-Fix-Unity-MCP-and-Claude-Code)

---

## Tips
- Enable “Show Debug Logs” in the header for more details in the Console when diagnosing issues.

---
```

--------------------------------------------------------------------------------
/UnityMcpBridge/README.md:
--------------------------------------------------------------------------------

```markdown
# MCP for Unity — Editor Plugin Guide

Use this guide to configure and run MCP for Unity inside the Unity Editor. Installation is covered elsewhere; this document focuses on the Editor window, client configuration, and troubleshooting.

## Open the window
- Unity menu: Window > MCP for Unity

The window has four areas: Server Status, Unity Bridge, MCP Client Configuration, and Script Validation.

---

## Quick start
1. Open Window > MCP for Unity.
2. Click “Auto-Setup”.
3. If prompted:
   - Select the server folder that contains `server.py` (UnityMcpServer~/src).
   - Install Python and/or uv if missing.
   - For Claude Code, ensure the `claude` CLI is installed.
4. Click “Start Bridge” if the Unity Bridge shows “Stopped”.
5. Use your MCP client (Cursor, VS Code, Windsurf, Claude Code) to connect.

---

## Server Status
- Status dot and label:
  - Installed / Installed (Embedded) / Not Installed.
- Mode and ports:
  - Mode: Auto or Standard.
  - Ports: Unity (varies; shown in UI), MCP 6500.
- Actions:
  - Auto-Setup: Registers/updates your selected MCP client(s), ensures bridge connectivity. Shows “Connected ✓” after success.
  - Rebuild MCP Server: Rebuilds the Python based MCP server
  - Select server folder…: Choose the folder containing `server.py`.
  - Verify again: Re-checks server presence.
  - If Python isn’t detected, use “Open Install Instructions”.

---

## Unity Bridge
- Shows Running or Stopped with a status dot.
- Start/Stop Bridge button toggles the Unity bridge process used by MCP clients to talk to Unity.
- Tip: After Auto-Setup, the bridge may auto-start in Auto mode.

---

## MCP Client Configuration
- Select Client: Choose your target MCP client (e.g., Cursor, VS Code, Windsurf, Claude Code).
- Per-client actions:
  - Cursor / VS Code / Windsurf:
    - Auto Configure: Writes/updates your config to launch the server via uv:
      - Command: uv
      - Args: run --directory <pythonDir> server.py
    - Manual Setup: Opens a window with a pre-filled JSON snippet to copy/paste into your client config.
    - Choose `uv` Install Location: If uv isn’t on PATH, select the uv binary.
    - A compact “Config:” line shows the resolved config file name once uv/server are detected.
  - Claude Code:
    - Register with Claude Code / Unregister MCP for Unity with Claude Code.
    - If the CLI isn’t found, click “Choose Claude Install Location”.
    - The window displays the resolved Claude CLI path when detected.

Notes:
- The UI shows a status dot and a short status text (e.g., “Configured”, “uv Not Found”, “Claude Not Found”).
- Use “Auto Configure” for one-click setup; use “Manual Setup” when you prefer to review/copy config.

---

## Script Validation
- Validation Level options:
  - Basic — Only syntax checks
  - Standard — Syntax + Unity practices
  - Comprehensive — All checks + semantic analysis
  - Strict — Full semantic validation (requires Roslyn)
- Pick a level based on your project’s needs. A description is shown under the dropdown.

---

## Troubleshooting
- Python or `uv` not found:
  - Help: [Fix MCP for Unity with Cursor, VS Code & Windsurf](https://github.com/CoplayDev/unity-mcp/wiki/1.-Fix-Unity-MCP-and-Cursor,-VSCode-&-Windsurf)
- Claude CLI not found:
  - Help: [Fix MCP for Unity with Claude Code](https://github.com/CoplayDev/unity-mcp/wiki/2.-Fix-Unity-MCP-and-Claude-Code)

---

## Tips
- Enable “Show Debug Logs” in the header for more details in the Console when diagnosing issues.

---
```

--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------

```markdown
<img width="676" height="380" alt="MCP for Unity" src="https://github.com/user-attachments/assets/b712e41d-273c-48b2-9041-82bd17ace267" />

| [English](README.md) | [简体中文](README-zh.md) |
|----------------------|---------------------------------|

#### Proudly sponsored and maintained by [Coplay](https://www.coplay.dev/?ref=unity-mcp) -- the best AI assistant for Unity.

[![Discord](https://img.shields.io/badge/discord-join-red.svg?logo=discord&logoColor=white)](https://discord.gg/y4p8KfzrN4)
[![](https://img.shields.io/badge/Website-Visit-purple)](https://www.coplay.dev/?ref=unity-mcp)
[![](https://img.shields.io/badge/Unity-000000?style=flat&logo=unity&logoColor=blue 'Unity')](https://unity.com/releases/editor/archive)
[![python](https://img.shields.io/badge/Python-3.12-3776AB.svg?style=flat&logo=python&logoColor=white)](https://www.python.org)
[![](https://badge.mcpx.dev?status=on 'MCP Enabled')](https://modelcontextprotocol.io/introduction)
![GitHub commit activity](https://img.shields.io/github/commit-activity/w/CoplayDev/unity-mcp)
![GitHub Issues or Pull Requests](https://img.shields.io/github/issues/CoplayDev/unity-mcp)
[![](https://img.shields.io/badge/License-MIT-red.svg 'MIT License')](https://opensource.org/licenses/MIT)

**Create your Unity apps with LLMs!**

MCP for Unity acts as a bridge, allowing AI assistants (like Claude, Cursor) to interact directly with your Unity Editor via a local **MCP (Model Context Protocol) Client**. Give your LLM tools to manage assets, control scenes, edit scripts, and automate tasks within Unity.

---

### 💬 Join Our [Discord](https://discord.gg/y4p8KfzrN4)

**Get help, share ideas, and collaborate with other MCP for Unity developers!**  

---

## Key Features 🚀

*   **🗣️ Natural Language Control:** Instruct your LLM to perform Unity tasks.
*   **🛠️ Powerful Tools:** Manage assets, scenes, materials, scripts, and editor functions.
*   **🤖 Automation:** Automate repetitive Unity workflows.
*   **🧩 Extensible:** Designed to work with various MCP Clients.

<details open>
  <summary><strong> Available Tools </strong></summary>

  Your LLM can use functions like:

  *   `read_console`: Gets messages from or clears the console.
  *   `manage_script`: Manages C# scripts (create, read, update, delete).
  *   `manage_editor`: Controls and queries the editor's state and settings.
  *   `manage_scene`: Manages scenes (load, save, create, get hierarchy, etc.).
  *   `manage_asset`: Performs asset operations (import, create, modify, delete, etc.).
  *   `manage_shader`: Performs shader CRUD operations (create, read, modify, delete).
  *   `manage_gameobject`: Manages GameObjects: create, modify, delete, find, and component operations.
  *   `execute_menu_item`: Executes Unity Editor menu items (e.g., "File/Save Project").
  *   `apply_text_edits`: Precise text edits with precondition hashes and atomic multi-edit batches.
  *   `script_apply_edits`: Structured C# method/class edits (insert/replace/delete) with safer boundaries.
  *   `validate_script`: Fast validation (basic/standard) to catch syntax/structure issues before/after writes.
</details>

---

## How It Works 

MCP for Unity connects your tools using two components:

1.  **MCP for Unity Bridge:** A Unity package running inside the Editor. (Installed via Package Manager).
2.  **MCP for Unity Server:** A Python server that runs locally, communicating between the Unity Bridge and your MCP Client. (Installed automatically by the package on first run or via Auto-Setup; manual setup is available as a fallback).

<img width="562" height="121" alt="image" src="https://github.com/user-attachments/assets/9abf9c66-70d1-4b82-9587-658e0d45dc3e" />

---

## Installation ⚙️

### Prerequisites

  *   **Python:** Version 3.12 or newer. [Download Python](https://www.python.org/downloads/)
  *   **Unity Hub & Editor:** Version 2021.3 LTS or newer. [Download Unity](https://unity.com/download)
  *   **uv (Python toolchain manager):**
      ```bash
      # macOS / Linux
      curl -LsSf https://astral.sh/uv/install.sh | sh

      # Windows (PowerShell)
      winget install --id=astral-sh.uv  -e

      # Docs: https://docs.astral.sh/uv/getting-started/installation/
      ```
      
  *   **An MCP Client:** : [Claude Desktop](https://claude.ai/download) | [Claude Code](https://github.com/anthropics/claude-code) | [Cursor](https://www.cursor.com/en/downloads) | [Visual Studio Code Copilot](https://code.visualstudio.com/docs/copilot/overview) | [Windsurf](https://windsurf.com) | Others work with manual config

 *    <details> <summary><strong>[Optional] Roslyn for Advanced Script Validation</strong></summary>

        For **Strict** validation level that catches undefined namespaces, types, and methods: 

        **Method 1: NuGet for Unity (Recommended)**
        1. Install [NuGetForUnity](https://github.com/GlitchEnzo/NuGetForUnity)
        2. Go to `Window > NuGet Package Manager`
        3. Search for `Microsoft.CodeAnalysis`, select version 4.14.0, and install the package
        4. Also install package `SQLitePCLRaw.core` and `SQLitePCLRaw.bundle_e_sqlite3`.
        5. Go to `Player Settings > Scripting Define Symbols`
        6. Add `USE_ROSLYN`
        7. Restart Unity

        **Method 2: Manual DLL Installation**
        1. Download Microsoft.CodeAnalysis.CSharp.dll and dependencies from [NuGet](https://www.nuget.org/packages/Microsoft.CodeAnalysis.CSharp/)
        2. Place DLLs in `Assets/Plugins/` folder
        3. Ensure .NET compatibility settings are correct
        4. Add `USE_ROSLYN` to Scripting Define Symbols
        5. Restart Unity

        **Note:** Without Roslyn, script validation falls back to basic structural checks. Roslyn enables full C# compiler diagnostics with precise error reporting.</details>

---
### 🌟 Step 1: Install the Unity Package

#### To install via Git URL

1.  Open your Unity project.
2.  Go to `Window > Package Manager`.
3.  Click `+` -> `Add package from git URL...`.
4.  Enter:
    ```
    https://github.com/CoplayDev/unity-mcp.git?path=/MCPForUnity
    ```
5.  Click `Add`.
6. The MCP server is installed automatically by the package on first run or via Auto-Setup. If that fails, use Manual Configuration (below).

#### To install via OpenUPM

1.  Install the [OpenUPM CLI](https://openupm.com/docs/getting-started-cli.html)
2.  Open a terminal (PowerShell, Terminal, etc.) and navigate to your Unity project directory
3.  Run `openupm add com.coplaydev.unity-mcp`

**Note:** If you installed the MCP Server before Coplay's maintenance, you will need to uninstall the old package before re-installing the new one.

### 🛠️ Step 2: Configure Your MCP Client
Connect your MCP Client (Claude, Cursor, etc.) to the Python server set up in Step 1 (auto) or via Manual Configuration (below).

<img width="648" height="599" alt="MCPForUnity-Readme-Image" src="https://github.com/user-attachments/assets/b4a725da-5c43-4bd6-80d6-ee2e3cca9596" />

**Option A: Auto-Setup (Recommended for Claude/Cursor/VSC Copilot)**

1.  In Unity, go to `Window > MCP for Unity`.
2.  Click `Auto-Setup`.
3.  Look for a green status indicator 🟢 and "Connected ✓". *(This attempts to modify the MCP Client's config file automatically).* 

<details><summary><strong>Client-specific troubleshooting</strong></summary>

  - **VSCode**: uses `Code/User/mcp.json` with top-level `servers.unityMCP` and `"type": "stdio"`. On Windows, MCP for Unity writes an absolute `uv.exe` (prefers WinGet Links shim) to avoid PATH issues.
  - **Cursor / Windsurf** [(**help link**)](https://github.com/CoplayDev/unity-mcp/wiki/1.-Fix-Unity-MCP-and-Cursor,-VSCode-&-Windsurf): if `uv` is missing, the MCP for Unity window shows "uv Not Found" with a quick [HELP] link and a "Choose `uv` Install Location" button.
  - **Claude Code** [(**help link**)](https://github.com/CoplayDev/unity-mcp/wiki/2.-Fix-Unity-MCP-and-Claude-Code): if `claude` isn't found, the window shows "Claude Not Found" with [HELP] and a "Choose Claude Location" button. Unregister now updates the UI immediately.</details>


**Option B: Manual Configuration**

If Auto-Setup fails or you use a different client:

1.  **Find your MCP Client's configuration file.** (Check client documentation).
    *   *Claude Example (macOS):* `~/Library/Application Support/Claude/claude_desktop_config.json`
    *   *Claude Example (Windows):* `%APPDATA%\Claude\claude_desktop_config.json`
2.  **Edit the file** to add/update the `mcpServers` section, using the *exact* paths from Step 1.

<details>
<summary><strong>Click for Client-Specific JSON Configuration Snippets...</strong></summary>

  ---
**Claude Code**

If you're using Claude Code, you can register the MCP server using the below commands:
🚨**make sure to run these from your Unity project's home directory**🚨

**macOS:**

```bash
claude mcp add UnityMCP -- uv --directory /Users/USERNAME/Library/AppSupport/UnityMCP/UnityMcpServer/src run server.py
```

**Windows:**

```bash
claude mcp add UnityMCP -- "C:/Users/USERNAME/AppData/Local/Microsoft/WinGet/Links/uv.exe" --directory "C:/Users/USERNAME/AppData/Local/UnityMCP/UnityMcpServer/src" run server.py
```
**VSCode (all OS)**

```json
{
  "servers": {
    "unityMCP": {
      "command": "uv",
      "args": ["--directory","<ABSOLUTE_PATH_TO>/UnityMcpServer/src","run","server.py"],
      "type": "stdio"
    }
  }
}
```

On Windows, set `command` to the absolute shim, e.g. `C:\\Users\\YOU\\AppData\\Local\\Microsoft\\WinGet\\Links\\uv.exe`.

**Windows:**

  ```json
  {
    "mcpServers": {
      "UnityMCP": {
        "command": "uv",
        "args": [
          "run",
          "--directory",
          "C:\\Users\\YOUR_USERNAME\\AppData\\Local\\UnityMCP\\UnityMcpServer\\src",
          "server.py"
        ]
      }
      // ... other servers might be here ...
    }
  }
``` 

(Remember to replace YOUR_USERNAME and use double backslashes \\)

**macOS:**

```json
{
  "mcpServers": {
    "UnityMCP": {
      "command": "uv",
      "args": [
        "run",
        "--directory",
        "/Users/YOUR_USERNAME/Library/AppSupport/UnityMCP/UnityMcpServer/src",
        "server.py"
      ]
    }
    // ... other servers might be here ...
  }
}
```

(Replace YOUR_USERNAME. Note: AppSupport is a symlink to "Application Support" to avoid quoting issues)

**Linux:**

```json
{
  "mcpServers": {
    "UnityMCP": {
      "command": "uv",
      "args": [
        "run",
        "--directory",
        "/home/YOUR_USERNAME/.local/share/UnityMCP/UnityMcpServer/src",
        "server.py"
      ]
    }
    // ... other servers might be here ...
  }
}
```

(Replace YOUR_USERNAME)


</details>

---

## Usage ▶️

1. **Open your Unity Project.** The MCP for Unity package should connect automatically. Check status via Window > MCP for Unity.
    
2. **Start your MCP Client** (Claude, Cursor, etc.). It should automatically launch the MCP for Unity Server (Python) using the configuration from Installation Step 2.
    
3. **Interact!** Unity tools should now be available in your MCP Client.
    
    Example Prompt: `Create a 3D player controller`, `Create a tic-tac-toe game in 3D`, `Create a cool shader and apply to a cube`.

---

## Development & Contributing 🛠️

### Adding Custom Tools

MCP for Unity uses a Python MCP Server tied with Unity's C# scripts for tools. If you'd like to extend the functionality with your own tools, learn how to do so in **[CUSTOM_TOOLS.md](docs/CUSTOM_TOOLS.md)**.

### Contributing to the Project

If you're contributing to MCP for Unity or want to test core changes, we have development tools to streamline your workflow:

- **Development Deployment Scripts**: Quickly deploy and test your changes to MCP for Unity Bridge and Python Server
- **Automatic Backup System**: Safe testing with easy rollback capabilities  
- **Hot Reload Workflow**: Fast iteration cycle for core development

📖 **See [README-DEV.md](docs/README-DEV.md)** for complete development setup and workflow documentation.

### Contributing 🤝

Help make MCP for Unity better!

1. **Fork** the main repository.
2. **Create a branch** (`feature/your-idea` or `bugfix/your-fix`).
3. **Make changes.**
4. **Commit** (feat: Add cool new feature).
5. **Push** your branch.
6. **Open a Pull Request** against the main branch.

---

## 📊 Telemetry & Privacy

MCP for Unity includes **privacy-focused, anonymous telemetry** to help us improve the product. We collect usage analytics and performance data, but **never** your code, project names, or personal information.

- **🔒 Anonymous**: Random UUIDs only, no personal data
- **🚫 Easy opt-out**: Set `DISABLE_TELEMETRY=true` environment variable
- **📖 Transparent**: See [TELEMETRY.md](docs/TELEMETRY.md) for full details

Your privacy matters to us. All telemetry is optional and designed to respect your workflow.

---

## Troubleshooting ❓

<details>  
<summary><strong>Click to view common issues and fixes...</strong></summary>  

- **Unity Bridge Not Running/Connecting:**
    - Ensure Unity Editor is open.
    - Check the status window: Window > MCP for Unity.
    - Restart Unity.
- **MCP Client Not Connecting / Server Not Starting:**
    - **Verify Server Path:** Double-check the --directory path in your MCP Client's JSON config. It must exactly match the installation location:
      - **Windows:** `%USERPROFILE%\AppData\Local\UnityMCP\UnityMcpServer\src`
      - **macOS:** `~/Library/AppSupport/UnityMCP/UnityMcpServer\src` 
      - **Linux:** `~/.local/share/UnityMCP/UnityMcpServer\src`
    - **Verify uv:** Make sure `uv` is installed and working (`uv --version`).
    - **Run Manually:** Try running the server directly from the terminal to see errors: 
      ```bash
      cd /path/to/your/UnityMCP/UnityMcpServer/src
      uv run server.py
      ```
- **Auto-Configure Failed:**
    - Use the Manual Configuration steps. Auto-configure might lack permissions to write to the MCP client's config file.

</details>  

Still stuck? [Open an Issue](https://github.com/CoplayDev/unity-mcp/issues) or [Join the Discord](https://discord.gg/y4p8KfzrN4)!

---

## License 📜

MIT License. See [LICENSE](LICENSE) file.

---

## Star History

[![Star History Chart](https://api.star-history.com/svg?repos=CoplayDev/unity-mcp&type=Date)](https://www.star-history.com/#CoplayDev/unity-mcp&Date)

## Unity AI Tools by Coplay

Coplay offers 2 AI tools for Unity
- **MCP for Unity** is available freely under the MIT license.
- **Coplay** is a premium Unity AI assistant that sits within Unity and is more than the MCP for Unity.

(These tools have different tech stacks. See this blog post [comparing Coplay to MCP for Unity](https://www.coplay.dev/blog/comparing-coplay-and-unity-mcp).)

## Disclaimer

This project is a free and open-source tool for the Unity Editor, and is not affiliated with Unity Technologies.

```

--------------------------------------------------------------------------------
/MCPForUnity/UnityMcpServer~/src/__init__.py:
--------------------------------------------------------------------------------

```python

```

--------------------------------------------------------------------------------
/MCPForUnity/UnityMcpServer~/src/server_version.txt:
--------------------------------------------------------------------------------

```
6.2.0

```

--------------------------------------------------------------------------------
/UnityMcpBridge/UnityMcpServer~/src/server_version.txt:
--------------------------------------------------------------------------------

```
4.1.1

```

--------------------------------------------------------------------------------
/UnityMcpBridge/UnityMcpServer~/src/__init__.py:
--------------------------------------------------------------------------------

```python
"""
MCP for Unity Server package.
"""

```

--------------------------------------------------------------------------------
/TestProjects/UnityMCPTests/ProjectSettings/ProjectVersion.txt:
--------------------------------------------------------------------------------

```
m_EditorVersion: 2021.3.45f2
m_EditorVersionWithRevision: 2021.3.45f2 (88f88f591b2e)

```

--------------------------------------------------------------------------------
/MCPForUnity/Editor/AssemblyInfo.cs:
--------------------------------------------------------------------------------

```csharp
using System.Runtime.CompilerServices;

[assembly: InternalsVisibleTo("MCPForUnityTests.EditMode")]

```

--------------------------------------------------------------------------------
/UnityMcpBridge/Editor/AssemblyInfo.cs:
--------------------------------------------------------------------------------

```csharp
using System.Runtime.CompilerServices;

[assembly: InternalsVisibleTo("MCPForUnityTests.EditMode")]

```

--------------------------------------------------------------------------------
/TestProjects/UnityMCPTests/Assets/Scripts/Hello.cs:
--------------------------------------------------------------------------------

```csharp
using UnityEngine;
using System.Collections;

public class Hello : MonoBehaviour
{
    void Start()
    {
        Debug.Log("Hello World");
    }
}

```

--------------------------------------------------------------------------------
/TestProjects/UnityMCPTests/ProjectSettings/Packages/com.unity.testtools.codecoverage/Settings.json:
--------------------------------------------------------------------------------

```json
{
    "m_Name": "Settings",
    "m_Path": "ProjectSettings/Packages/com.unity.testtools.codecoverage/Settings.json",
    "m_Dictionary": {
        "m_DictionaryValues": []
    }
}
```

--------------------------------------------------------------------------------
/MCPForUnity/UnityMcpServer~/src/pyrightconfig.json:
--------------------------------------------------------------------------------

```json
{
  "typeCheckingMode": "basic",
  "reportMissingImports": "none",
  "pythonVersion": "3.11",
  "executionEnvironments": [
    {
      "root": ".",
      "pythonVersion": "3.11"
    }
  ]
}

```

--------------------------------------------------------------------------------
/UnityMcpBridge/UnityMcpServer~/src/pyrightconfig.json:
--------------------------------------------------------------------------------

```json
{
  "typeCheckingMode": "basic",
  "reportMissingImports": "none",
  "pythonVersion": "3.11",
  "executionEnvironments": [
    {
      "root": ".",
      "pythonVersion": "3.11"
    }
  ]
}

```

--------------------------------------------------------------------------------
/MCPForUnity/UnityMcpServer~/src/models.py:
--------------------------------------------------------------------------------

```python
from typing import Any
from pydantic import BaseModel


class MCPResponse(BaseModel):
    success: bool
    message: str | None = None
    error: str | None = None
    data: Any | None = None

```

--------------------------------------------------------------------------------
/MCPForUnity/Editor/Models/McpTypes.cs:
--------------------------------------------------------------------------------

```csharp
namespace MCPForUnity.Editor.Models
{
    public enum McpTypes
    {
        ClaudeCode,
        ClaudeDesktop,
        Codex,
        Cursor,
        Kiro,
        VSCode,
        Windsurf,
    }
}

```

--------------------------------------------------------------------------------
/UnityMcpBridge/Editor/Models/McpTypes.cs:
--------------------------------------------------------------------------------

```csharp
namespace MCPForUnity.Editor.Models
{
    public enum McpTypes
    {
        ClaudeCode,
        ClaudeDesktop,
        Codex,
        Cursor,
        Kiro,
        VSCode,
        Windsurf,
    }
}

```

--------------------------------------------------------------------------------
/MCPForUnity/Editor/Models/McpConfig.cs:
--------------------------------------------------------------------------------

```csharp
using System;
using Newtonsoft.Json;

namespace MCPForUnity.Editor.Models
{
    [Serializable]
    public class McpConfig
    {
        [JsonProperty("mcpServers")]
        public McpConfigServers mcpServers;
    }
}

```

--------------------------------------------------------------------------------
/UnityMcpBridge/Editor/Models/McpConfig.cs:
--------------------------------------------------------------------------------

```csharp
using System;
using Newtonsoft.Json;

namespace MCPForUnity.Editor.Models
{
    [Serializable]
    public class McpConfig
    {
        [JsonProperty("mcpServers")]
        public McpConfigServers mcpServers;
    }
}

```

--------------------------------------------------------------------------------
/MCPForUnity/Editor/Models/MCPConfigServers.cs:
--------------------------------------------------------------------------------

```csharp
using System;
using Newtonsoft.Json;

namespace MCPForUnity.Editor.Models
{
    [Serializable]
    public class McpConfigServers
    {
        [JsonProperty("unityMCP")]
        public McpConfigServer unityMCP;
    }
}

```

--------------------------------------------------------------------------------
/UnityMcpBridge/Editor/Models/MCPConfigServers.cs:
--------------------------------------------------------------------------------

```csharp
using System;
using Newtonsoft.Json;

namespace MCPForUnity.Editor.Models
{
    [Serializable]
    public class McpConfigServers
    {
        [JsonProperty("unityMCP")]
        public McpConfigServer unityMCP;
    }
}

```

--------------------------------------------------------------------------------
/MCPForUnity/UnityMcpServer~/src/Dockerfile:
--------------------------------------------------------------------------------

```dockerfile
FROM python:3.13-slim

RUN apt-get update && apt-get install -y --no-install-recommends \
    git \
    && rm -rf /var/lib/apt/lists/*

WORKDIR /app

RUN pip install uv

COPY . /app

RUN uv sync

CMD ["uv", "run", "server.py"]

```

--------------------------------------------------------------------------------
/UnityMcpBridge/UnityMcpServer~/src/registry/__init__.py:
--------------------------------------------------------------------------------

```python
"""
Registry package for MCP tool auto-discovery.
"""
from .tool_registry import (
    mcp_for_unity_tool,
    get_registered_tools,
    clear_registry
)

__all__ = [
    'mcp_for_unity_tool',
    'get_registered_tools',
    'clear_registry'
]

```

--------------------------------------------------------------------------------
/.claude/settings.json:
--------------------------------------------------------------------------------

```json
{
  "permissions": {
    "allow": [
      "mcp__unity",
      "Edit(reports/**)",
      "MultiEdit(reports/**)"
    ],
    "deny": [
      "Bash",
      "WebFetch",
      "WebSearch",
      "Task",
      "TodoWrite",
      "NotebookEdit",
      "NotebookRead"
    ]
  }
}

```

--------------------------------------------------------------------------------
/tests/conftest.py:
--------------------------------------------------------------------------------

```python
import os

# Ensure telemetry is disabled during test collection and execution to avoid
# any background network or thread startup that could slow or block pytest.
os.environ.setdefault("DISABLE_TELEMETRY", "true")
os.environ.setdefault("UNITY_MCP_DISABLE_TELEMETRY", "true")
os.environ.setdefault("MCP_DISABLE_TELEMETRY", "true")

```

--------------------------------------------------------------------------------
/scripts/validate-nlt-coverage.sh:
--------------------------------------------------------------------------------

```bash
#!/usr/bin/env bash
set -euo pipefail
cd "$(git rev-parse --show-toplevel)"
missing=()
for id in NL-0 NL-1 NL-2 NL-3 NL-4 T-A T-B T-C T-D T-E T-F T-G T-H T-I T-J; do
  [[ -s "reports/${id}_results.xml" ]] || missing+=("$id")
done
if (( ${#missing[@]} )); then
  echo "Missing fragments: ${missing[*]}"
  exit 2
fi
echo "All NL/T fragments present."

```

--------------------------------------------------------------------------------
/MCPForUnity/UnityMcpServer~/src/reload_sentinel.py:
--------------------------------------------------------------------------------

```python
"""
Deprecated: Sentinel flipping is handled inside Unity via the MCP menu
'MCP/Flip Reload Sentinel'. This module remains only as a compatibility shim.
All functions are no-ops to prevent accidental external writes.
"""


def flip_reload_sentinel(*args, **kwargs) -> str:
    return "reload_sentinel.py is deprecated; use execute_menu_item → 'MCP/Flip Reload Sentinel'"

```

--------------------------------------------------------------------------------
/UnityMcpBridge/UnityMcpServer~/src/reload_sentinel.py:
--------------------------------------------------------------------------------

```python
"""
Deprecated: Sentinel flipping is handled inside Unity via the MCP menu
'MCP/Flip Reload Sentinel'. This module remains only as a compatibility shim.
All functions are no-ops to prevent accidental external writes.
"""


def flip_reload_sentinel(*args, **kwargs) -> str:
    return "reload_sentinel.py is deprecated; use execute_menu_item → 'MCP/Flip Reload Sentinel'"

```

--------------------------------------------------------------------------------
/TestProjects/UnityMCPTests/Assets/Scripts/TestAsmdef/CustomComponent.cs:
--------------------------------------------------------------------------------

```csharp
using UnityEngine;

namespace TestNamespace
{
    public class CustomComponent : MonoBehaviour
    {
        [SerializeField]
        private string customText = "Hello from custom asmdef!";

        [SerializeField]
        private float customFloat = 42.0f;

        void Start()
        {
            Debug.Log($"CustomComponent started: {customText}, value: {customFloat}");
        }
    }
}

```

--------------------------------------------------------------------------------
/MCPForUnity/Editor/Services/IPythonToolRegistryService.cs:
--------------------------------------------------------------------------------

```csharp
using System.Collections.Generic;
using UnityEngine;
using MCPForUnity.Editor.Data;

namespace MCPForUnity.Editor.Services
{
    public interface IPythonToolRegistryService
    {
        IEnumerable<PythonToolsAsset> GetAllRegistries();
        bool NeedsSync(PythonToolsAsset registry, TextAsset file);
        void RecordSync(PythonToolsAsset registry, TextAsset file);
        string ComputeHash(TextAsset file);
    }
}

```

--------------------------------------------------------------------------------
/MCPForUnity/Editor/Models/MCPConfigServer.cs:
--------------------------------------------------------------------------------

```csharp
using System;
using Newtonsoft.Json;

namespace MCPForUnity.Editor.Models
{
    [Serializable]
    public class McpConfigServer
    {
        [JsonProperty("command")]
        public string command;

        [JsonProperty("args")]
        public string[] args;

        // VSCode expects a transport type; include only when explicitly set
        [JsonProperty("type", NullValueHandling = NullValueHandling.Ignore)]
        public string type;
    }
}

```

--------------------------------------------------------------------------------
/UnityMcpBridge/Editor/Models/MCPConfigServer.cs:
--------------------------------------------------------------------------------

```csharp
using System;
using Newtonsoft.Json;

namespace MCPForUnity.Editor.Models
{
    [Serializable]
    public class McpConfigServer
    {
        [JsonProperty("command")]
        public string command;

        [JsonProperty("args")]
        public string[] args;

        // VSCode expects a transport type; include only when explicitly set
        [JsonProperty("type", NullValueHandling = NullValueHandling.Ignore)]
        public string type;
    }
}

```

--------------------------------------------------------------------------------
/MCPForUnity/Editor/Models/Command.cs:
--------------------------------------------------------------------------------

```csharp
using Newtonsoft.Json.Linq;

namespace MCPForUnity.Editor.Models
{
    /// <summary>
    /// Represents a command received from the MCP client
    /// </summary>
    public class Command
    {
        /// <summary>
        /// The type of command to execute
        /// </summary>
        public string type { get; set; }

        /// <summary>
        /// The parameters for the command
        /// </summary>
        public JObject @params { get; set; }
    }
}


```

--------------------------------------------------------------------------------
/UnityMcpBridge/Editor/Models/Command.cs:
--------------------------------------------------------------------------------

```csharp
using Newtonsoft.Json.Linq;

namespace MCPForUnity.Editor.Models
{
    /// <summary>
    /// Represents a command received from the MCP client
    /// </summary>
    public class Command
    {
        /// <summary>
        /// The type of command to execute
        /// </summary>
        public string type { get; set; }

        /// <summary>
        /// The parameters for the command
        /// </summary>
        public JObject @params { get; set; }
    }
}


```

--------------------------------------------------------------------------------
/MCPForUnity/UnityMcpServer~/src/registry/__init__.py:
--------------------------------------------------------------------------------

```python
"""
Registry package for MCP tool auto-discovery.
"""
from .tool_registry import (
    mcp_for_unity_tool,
    get_registered_tools,
    clear_tool_registry,
)
from .resource_registry import (
    mcp_for_unity_resource,
    get_registered_resources,
    clear_resource_registry,
)

__all__ = [
    'mcp_for_unity_tool',
    'get_registered_tools',
    'clear_tool_registry',
    'mcp_for_unity_resource',
    'get_registered_resources',
    'clear_resource_registry'
]

```

--------------------------------------------------------------------------------
/MCPForUnity/Editor/Services/IToolSyncService.cs:
--------------------------------------------------------------------------------

```csharp
using System.Collections.Generic;

namespace MCPForUnity.Editor.Services
{
    public class ToolSyncResult
    {
        public int CopiedCount { get; set; }
        public int SkippedCount { get; set; }
        public int ErrorCount { get; set; }
        public List<string> Messages { get; set; } = new List<string>();
        public bool Success => ErrorCount == 0;
    }

    public interface IToolSyncService
    {
        ToolSyncResult SyncProjectTools(string destToolsDir);
    }
}

```

--------------------------------------------------------------------------------
/UnityMcpBridge/UnityMcpServer~/src/pyproject.toml:
--------------------------------------------------------------------------------

```toml
[project]
name = "MCPForUnityServer"
version = "4.1.1"
description = "MCP for Unity Server: A Unity package for Unity Editor integration via the Model Context Protocol (MCP)."
readme = "README.md"
requires-python = ">=3.10"
dependencies = [
 "httpx>=0.27.2",
 "mcp[cli]>=1.15.0",
 "tomli>=2.3.0",
]

[build-system]
requires = ["setuptools>=64.0.0", "wheel"]
build-backend = "setuptools.build_meta"

[tool.setuptools]
py-modules = ["config", "server", "unity_connection"]
packages = ["tools"]

```

--------------------------------------------------------------------------------
/MCPForUnity/UnityMcpServer~/src/pyproject.toml:
--------------------------------------------------------------------------------

```toml
[project]
name = "MCPForUnityServer"
version = "6.2.0"
description = "MCP for Unity Server: A Unity package for Unity Editor integration via the Model Context Protocol (MCP)."
readme = "README.md"
requires-python = ">=3.10"
dependencies = [
 "httpx>=0.27.2",
 "mcp[cli]>=1.17.0",
 "pydantic>=2.12.0",
 "tomli>=2.3.0",
]

[build-system]
requires = ["setuptools>=64.0.0", "wheel"]
build-backend = "setuptools.build_meta"

[tool.setuptools]
py-modules = ["config", "server", "unity_connection"]
packages = ["tools"]

```

--------------------------------------------------------------------------------
/.github/workflows/github-repo-stats.yml:
--------------------------------------------------------------------------------

```yaml
name: github-repo-stats

on:
  schedule:
    # Run this once per day, towards the end of the day for keeping the most
    # recent data point most meaningful (hours are interpreted in UTC).
    - cron: "0 23 * * *"
  workflow_dispatch: # Allow for running this manually.

jobs:
  j1:
    name: github-repo-stats
    runs-on: ubuntu-latest
    steps:
      - name: run-ghrs
        # Use latest release.
        uses: jgehrcke/github-repo-stats@RELEASE
        with:
          ghtoken: ${{ secrets.ghrs_github_api_token }}

```

--------------------------------------------------------------------------------
/UnityMcpBridge/UnityMcpServer~/src/Dockerfile:
--------------------------------------------------------------------------------

```dockerfile
FROM python:3.12-slim

# Install required system dependencies
RUN apt-get update && apt-get install -y --no-install-recommends \
    git \
    && rm -rf /var/lib/apt/lists/*

# Set working directory
WORKDIR /app

# Install uv package manager
RUN pip install uv

# Copy required files
COPY config.py /app/
COPY server.py /app/
COPY unity_connection.py /app/
COPY pyproject.toml /app/
COPY __init__.py /app/
COPY tools/ /app/tools/

# Install dependencies using uv
RUN uv pip install --system -e .


# Command to run the server
CMD ["uv", "run", "server.py"]

```

--------------------------------------------------------------------------------
/MCPForUnity/Editor/Data/DefaultServerConfig.cs:
--------------------------------------------------------------------------------

```csharp
using MCPForUnity.Editor.Models;

namespace MCPForUnity.Editor.Data
{
    public class DefaultServerConfig : ServerConfig
    {
        public new string unityHost = "localhost";
        public new int unityPort = 6400;
        public new int mcpPort = 6500;
        public new float connectionTimeout = 15.0f;
        public new int bufferSize = 32768;
        public new string logLevel = "INFO";
        public new string logFormat = "%(asctime)s - %(name)s - %(levelname)s - %(message)s";
        public new int maxRetries = 3;
        public new float retryDelay = 1.0f;
    }
}

```

--------------------------------------------------------------------------------
/UnityMcpBridge/Editor/Data/DefaultServerConfig.cs:
--------------------------------------------------------------------------------

```csharp
using MCPForUnity.Editor.Models;

namespace MCPForUnity.Editor.Data
{
    public class DefaultServerConfig : ServerConfig
    {
        public new string unityHost = "localhost";
        public new int unityPort = 6400;
        public new int mcpPort = 6500;
        public new float connectionTimeout = 15.0f;
        public new int bufferSize = 32768;
        public new string logLevel = "INFO";
        public new string logFormat = "%(asctime)s - %(name)s - %(levelname)s - %(message)s";
        public new int maxRetries = 3;
        public new float retryDelay = 1.0f;
    }
}

```

--------------------------------------------------------------------------------
/MCPForUnity/Editor/Models/McpStatus.cs:
--------------------------------------------------------------------------------

```csharp
namespace MCPForUnity.Editor.Models
{
    // Enum representing the various status states for MCP clients
    public enum McpStatus
    {
        NotConfigured, // Not set up yet
        Configured, // Successfully configured
        Running, // Service is running
        Connected, // Successfully connected
        IncorrectPath, // Configuration has incorrect paths
        CommunicationError, // Connected but communication issues
        NoResponse, // Connected but not responding
        MissingConfig, // Config file exists but missing required elements
        UnsupportedOS, // OS is not supported
        Error, // General error state
    }
}


```

--------------------------------------------------------------------------------
/UnityMcpBridge/Editor/Models/McpStatus.cs:
--------------------------------------------------------------------------------

```csharp
namespace MCPForUnity.Editor.Models
{
    // Enum representing the various status states for MCP clients
    public enum McpStatus
    {
        NotConfigured, // Not set up yet
        Configured, // Successfully configured
        Running, // Service is running
        Connected, // Successfully connected
        IncorrectPath, // Configuration has incorrect paths
        CommunicationError, // Connected but communication issues
        NoResponse, // Connected but not responding
        MissingConfig, // Config file exists but missing required elements
        UnsupportedOS, // OS is not supported
        Error, // General error state
    }
}


```

--------------------------------------------------------------------------------
/MCPForUnity/Editor/Importers/PythonFileImporter.cs:
--------------------------------------------------------------------------------

```csharp
using UnityEngine;
using UnityEditor.AssetImporters;
using System.IO;

namespace MCPForUnity.Editor.Importers
{
    /// <summary>
    /// Custom importer that allows Unity to recognize .py files as TextAssets.
    /// This enables Python files to be selected in the Inspector and used like any other text asset.
    /// </summary>
    [ScriptedImporter(1, "py")]
    public class PythonFileImporter : ScriptedImporter
    {
        public override void OnImportAsset(AssetImportContext ctx)
        {
            var textAsset = new TextAsset(File.ReadAllText(ctx.assetPath));
            ctx.AddObjectToAsset("main obj", textAsset);
            ctx.SetMainObject(textAsset);
        }
    }
}

```

--------------------------------------------------------------------------------
/MCPForUnity/Editor/Services/ITestRunnerService.cs:
--------------------------------------------------------------------------------

```csharp
using System.Collections.Generic;
using System.Threading.Tasks;
using UnityEditor.TestTools.TestRunner.Api;

namespace MCPForUnity.Editor.Services
{
    /// <summary>
    /// Provides access to Unity Test Runner data and execution.
    /// </summary>
    public interface ITestRunnerService
    {
        /// <summary>
        /// Retrieve the list of tests for the requested mode(s).
        /// When <paramref name="mode"/> is null, tests for both EditMode and PlayMode are returned.
        /// </summary>
        Task<IReadOnlyList<Dictionary<string, string>>> GetTestsAsync(TestMode? mode);

        /// <summary>
        /// Execute tests for the supplied mode.
        /// </summary>
        Task<TestRunResult> RunTestsAsync(TestMode mode);
    }
}

```

--------------------------------------------------------------------------------
/MCPForUnity/Editor/Models/ServerConfig.cs:
--------------------------------------------------------------------------------

```csharp
using System;
using Newtonsoft.Json;

namespace MCPForUnity.Editor.Models
{
    [Serializable]
    public class ServerConfig
    {
        [JsonProperty("unity_host")]
        public string unityHost = "localhost";

        [JsonProperty("unity_port")]
        public int unityPort;

        [JsonProperty("mcp_port")]
        public int mcpPort;

        [JsonProperty("connection_timeout")]
        public float connectionTimeout;

        [JsonProperty("buffer_size")]
        public int bufferSize;

        [JsonProperty("log_level")]
        public string logLevel;

        [JsonProperty("log_format")]
        public string logFormat;

        [JsonProperty("max_retries")]
        public int maxRetries;

        [JsonProperty("retry_delay")]
        public float retryDelay;
    }
}

```

--------------------------------------------------------------------------------
/UnityMcpBridge/Editor/Models/ServerConfig.cs:
--------------------------------------------------------------------------------

```csharp
using System;
using Newtonsoft.Json;

namespace MCPForUnity.Editor.Models
{
    [Serializable]
    public class ServerConfig
    {
        [JsonProperty("unity_host")]
        public string unityHost = "localhost";

        [JsonProperty("unity_port")]
        public int unityPort;

        [JsonProperty("mcp_port")]
        public int mcpPort;

        [JsonProperty("connection_timeout")]
        public float connectionTimeout;

        [JsonProperty("buffer_size")]
        public int bufferSize;

        [JsonProperty("log_level")]
        public string logLevel;

        [JsonProperty("log_format")]
        public string logFormat;

        [JsonProperty("max_retries")]
        public int maxRetries;

        [JsonProperty("retry_delay")]
        public float retryDelay;
    }
}

```

--------------------------------------------------------------------------------
/UnityMcpBridge/Editor/Helpers/AssetPathUtility.cs:
--------------------------------------------------------------------------------

```csharp
using System;

namespace MCPForUnity.Editor.Helpers
{
    /// <summary>
    /// Provides common utility methods for working with Unity asset paths.
    /// </summary>
    public static class AssetPathUtility
    {
        /// <summary>
        /// Normalizes a Unity asset path by ensuring forward slashes are used and that it is rooted under "Assets/".
        /// </summary>
        public static string SanitizeAssetPath(string path)
        {
            if (string.IsNullOrEmpty(path))
            {
                return path;
            }

            path = path.Replace('\\', '/');
            if (!path.StartsWith("Assets/", StringComparison.OrdinalIgnoreCase))
            {
                return "Assets/" + path.TrimStart('/');
            }

            return path;
        }
    }
}

```

--------------------------------------------------------------------------------
/MCPForUnity/UnityMcpServer~/src/resources/menu_items.py:
--------------------------------------------------------------------------------

```python
from models import MCPResponse
from registry import mcp_for_unity_resource
from unity_connection import async_send_command_with_retry


class GetMenuItemsResponse(MCPResponse):
    data: list[str] = []


@mcp_for_unity_resource(
    uri="mcpforunity://menu-items",
    name="get_menu_items",
    description="Provides a list of all menu items."
)
async def get_menu_items() -> GetMenuItemsResponse:
    """Provides a list of all menu items."""
    # Later versions of FastMCP support these as query parameters
    # See: https://gofastmcp.com/servers/resources#query-parameters
    params = {
        "refresh": True,
        "search": "",
    }

    response = await async_send_command_with_retry("get_menu_items", params)
    return GetMenuItemsResponse(**response) if isinstance(response, dict) else response

```

--------------------------------------------------------------------------------
/MCPForUnity/Editor/Helpers/Vector3Helper.cs:
--------------------------------------------------------------------------------

```csharp
using Newtonsoft.Json.Linq;
using UnityEngine;

namespace MCPForUnity.Editor.Helpers
{
    /// <summary>
    /// Helper class for Vector3 operations
    /// </summary>
    public static class Vector3Helper
    {
        /// <summary>
        /// Parses a JArray into a Vector3
        /// </summary>
        /// <param name="array">The array containing x, y, z coordinates</param>
        /// <returns>A Vector3 with the parsed coordinates</returns>
        /// <exception cref="System.Exception">Thrown when array is invalid</exception>
        public static Vector3 ParseVector3(JArray array)
        {
            if (array == null || array.Count != 3)
                throw new System.Exception("Vector3 must be an array of 3 floats [x, y, z].");
            return new Vector3((float)array[0], (float)array[1], (float)array[2]);
        }
    }
}

```

--------------------------------------------------------------------------------
/UnityMcpBridge/Editor/Helpers/Vector3Helper.cs:
--------------------------------------------------------------------------------

```csharp
using Newtonsoft.Json.Linq;
using UnityEngine;

namespace MCPForUnity.Editor.Helpers
{
    /// <summary>
    /// Helper class for Vector3 operations
    /// </summary>
    public static class Vector3Helper
    {
        /// <summary>
        /// Parses a JArray into a Vector3
        /// </summary>
        /// <param name="array">The array containing x, y, z coordinates</param>
        /// <returns>A Vector3 with the parsed coordinates</returns>
        /// <exception cref="System.Exception">Thrown when array is invalid</exception>
        public static Vector3 ParseVector3(JArray array)
        {
            if (array == null || array.Count != 3)
                throw new System.Exception("Vector3 must be an array of 3 floats [x, y, z].");
            return new Vector3((float)array[0], (float)array[1], (float)array[2]);
        }
    }
}

```

--------------------------------------------------------------------------------
/UnityMcpBridge/Editor/Helpers/McpLog.cs:
--------------------------------------------------------------------------------

```csharp
using UnityEditor;
using UnityEngine;

namespace MCPForUnity.Editor.Helpers
{
    internal static class McpLog
    {
        private const string Prefix = "<b><color=#2EA3FF>MCP-FOR-UNITY</color></b>:";

        private static bool IsDebugEnabled()
        {
            try { return EditorPrefs.GetBool("MCPForUnity.DebugLogs", false); } catch { return false; }
        }

        public static void Info(string message, bool always = true)
        {
            if (!always && !IsDebugEnabled()) return;
            Debug.Log($"{Prefix} {message}");
        }

        public static void Warn(string message)
        {
            Debug.LogWarning($"<color=#cc7a00>{Prefix} {message}</color>");
        }

        public static void Error(string message)
        {
            Debug.LogError($"<color=#cc3333>{Prefix} {message}</color>");
        }
    }
}

```

--------------------------------------------------------------------------------
/MCPForUnity/UnityMcpServer~/src/tools/execute_menu_item.py:
--------------------------------------------------------------------------------

```python
"""
Defines the execute_menu_item tool for executing and reading Unity Editor menu items.
"""
from typing import Annotated, Any

from mcp.server.fastmcp import Context

from models import MCPResponse
from registry import mcp_for_unity_tool
from unity_connection import async_send_command_with_retry


@mcp_for_unity_tool(
    description="Execute a Unity menu item by path."
)
async def execute_menu_item(
    ctx: Context,
    menu_path: Annotated[str,
                         "Menu path for 'execute' or 'exists' (e.g., 'File/Save Project')"] | None = None,
) -> MCPResponse:
    await ctx.info(f"Processing execute_menu_item: {menu_path}")
    params_dict: dict[str, Any] = {"menuPath": menu_path}
    params_dict = {k: v for k, v in params_dict.items() if v is not None}
    result = await async_send_command_with_retry("execute_menu_item", params_dict)
    return MCPResponse(**result) if isinstance(result, dict) else result

```

--------------------------------------------------------------------------------
/MCPForUnity/package.json:
--------------------------------------------------------------------------------

```json
{
  "name": "com.coplaydev.unity-mcp",
  "version": "6.2.0",
  "displayName": "MCP for Unity",
  "description": "A bridge that connects AI assistants to Unity via the MCP (Model Context Protocol). Allows AI clients like Claude Code, Cursor, and VSCode to directly control your Unity Editor for enhanced development workflows.\n\nFeatures automated setup wizard, cross-platform support, and seamless integration with popular AI development tools.\n\nJoin Our Discord: https://discord.gg/y4p8KfzrN4",
  "unity": "2021.3",
  "documentationUrl": "https://github.com/CoplayDev/unity-mcp",
  "licensesUrl": "https://github.com/CoplayDev/unity-mcp/blob/main/LICENSE",
  "dependencies": {
    "com.unity.nuget.newtonsoft-json": "3.0.2"
  },
  "keywords": [
    "unity",
    "ai",
    "llm",
    "mcp",
    "model-context-protocol",
    "mcp-server",
    "mcp-client"
  ],
  "author": {
    "name": "Coplay",
    "email": "[email protected]",
    "url": "https://coplay.dev"
  }
}

```

--------------------------------------------------------------------------------
/UnityMcpBridge/package.json:
--------------------------------------------------------------------------------

```json
{
  "name": "com.coplaydev.unity-mcp",
  "version": "4.1.1",
  "displayName": "MCP for Unity",
  "description": "A bridge that connects AI assistants to Unity via the MCP (Model Context Protocol). Allows AI clients like Claude Code, Cursor, and VSCode to directly control your Unity Editor for enhanced development workflows.\n\nFeatures automated setup wizard, cross-platform support, and seamless integration with popular AI development tools.\n\nJoin Our Discord: https://discord.gg/y4p8KfzrN4",
  "unity": "2021.3",
  "documentationUrl": "https://github.com/CoplayDev/unity-mcp",
  "licensesUrl": "https://github.com/CoplayDev/unity-mcp/blob/main/LICENSE",
  "dependencies": {
    "com.unity.nuget.newtonsoft-json": "3.0.2"
  },
  "keywords": [
    "unity",
    "ai",
    "llm",
    "mcp",
    "model-context-protocol",
    "mcp-server",
    "mcp-client"
  ],
  "author": {
    "name": "Coplay",
    "email": "[email protected]",
    "url": "https://coplay.dev"
  }
}

```

--------------------------------------------------------------------------------
/MCPForUnity/Editor/Helpers/McpLog.cs:
--------------------------------------------------------------------------------

```csharp
using UnityEditor;
using UnityEngine;

namespace MCPForUnity.Editor.Helpers
{
    internal static class McpLog
    {
        private const string LogPrefix = "<b><color=#2EA3FF>MCP-FOR-UNITY</color></b>:";
        private const string WarnPrefix = "<b><color=#cc7a00>MCP-FOR-UNITY</color></b>:";
        private const string ErrorPrefix = "<b><color=#cc3333>MCP-FOR-UNITY</color></b>:";

        private static bool IsDebugEnabled()
        {
            try { return EditorPrefs.GetBool("MCPForUnity.DebugLogs", false); } catch { return false; }
        }

        public static void Info(string message, bool always = true)
        {
            if (!always && !IsDebugEnabled()) return;
            Debug.Log($"{LogPrefix} {message}");
        }

        public static void Warn(string message)
        {
            Debug.LogWarning($"{WarnPrefix} {message}");
        }

        public static void Error(string message)
        {
            Debug.LogError($"{ErrorPrefix} {message}");
        }
    }
}

```

--------------------------------------------------------------------------------
/tests/test_script_editing.py:
--------------------------------------------------------------------------------

```python
import pytest


@pytest.mark.xfail(strict=False, reason="pending: create new script, validate, apply edits, build and compile scene")
def test_script_edit_happy_path():
    pass


@pytest.mark.xfail(strict=False, reason="pending: multiple micro-edits debounce to single compilation")
def test_micro_edits_debounce():
    pass


@pytest.mark.xfail(strict=False, reason="pending: line ending variations handled correctly")
def test_line_endings_and_columns():
    pass


@pytest.mark.xfail(strict=False, reason="pending: regex_replace no-op with allow_noop honored")
def test_regex_replace_noop_allowed():
    pass


@pytest.mark.xfail(strict=False, reason="pending: large edit size boundaries and overflow protection")
def test_large_edit_size_and_overflow():
    pass


@pytest.mark.xfail(strict=False, reason="pending: symlink and junction protections on edits")
def test_symlink_and_junction_protection():
    pass


@pytest.mark.xfail(strict=False, reason="pending: atomic write guarantees")
def test_atomic_write_guarantees():
    pass

```

--------------------------------------------------------------------------------
/tests/test_find_in_file_minimal.py:
--------------------------------------------------------------------------------

```python
from tools.resource_tools import register_resource_tools  # type: ignore
import sys
import pathlib
import importlib.util
import types
import asyncio
import pytest

ROOT = pathlib.Path(__file__).resolve().parents[1]
SRC = ROOT / "MCPForUnity" / "UnityMcpServer~" / "src"
sys.path.insert(0, str(SRC))


class DummyMCP:
    def __init__(self):
        self.tools = {}

    def tool(self, *args, **kwargs):
        def deco(fn):
            self.tools[fn.__name__] = fn
            return fn
        return deco


@pytest.fixture()
def resource_tools():
    mcp = DummyMCP()
    register_resource_tools(mcp)
    return mcp.tools


def test_find_in_file_returns_positions(resource_tools, tmp_path):
    proj = tmp_path
    assets = proj / "Assets"
    assets.mkdir()
    f = assets / "A.txt"
    f.write_text("hello world", encoding="utf-8")
    find_in_file = resource_tools["find_in_file"]
    loop = asyncio.new_event_loop()
    try:
        resp = loop.run_until_complete(
            find_in_file(uri="unity://path/Assets/A.txt",
                         pattern="world", ctx=None, project_root=str(proj))
        )
    finally:
        loop.close()
    assert resp["success"] is True
    assert resp["data"]["matches"] == [
        {"startLine": 1, "startCol": 7, "endLine": 1, "endCol": 12}]

```

--------------------------------------------------------------------------------
/UnityMcpBridge/UnityMcpServer~/src/registry/tool_registry.py:
--------------------------------------------------------------------------------

```python
"""
Tool registry for auto-discovery of MCP tools.
"""
from typing import Callable, Any

# Global registry to collect decorated tools
_tool_registry: list[dict[str, Any]] = []


def mcp_for_unity_tool(
    name: str | None = None,
    description: str | None = None,
    **kwargs
) -> Callable:
    """
    Decorator for registering MCP tools in the server's tools directory.

    Tools are registered in the global tool registry.

    Args:
        name: Tool name (defaults to function name)
        description: Tool description
        **kwargs: Additional arguments passed to @mcp.tool()

    Example:
        @mcp_for_unity_tool(description="Does something cool")
        async def my_custom_tool(ctx: Context, ...):
            pass
    """
    def decorator(func: Callable) -> Callable:
        tool_name = name if name is not None else func.__name__
        _tool_registry.append({
            'func': func,
            'name': tool_name,
            'description': description,
            'kwargs': kwargs
        })

        return func

    return decorator


def get_registered_tools() -> list[dict[str, Any]]:
    """Get all registered tools"""
    return _tool_registry.copy()


def clear_registry():
    """Clear the tool registry (useful for testing)"""
    _tool_registry.clear()

```

--------------------------------------------------------------------------------
/MCPForUnity/UnityMcpServer~/src/registry/tool_registry.py:
--------------------------------------------------------------------------------

```python
"""
Tool registry for auto-discovery of MCP tools.
"""
from typing import Callable, Any

# Global registry to collect decorated tools
_tool_registry: list[dict[str, Any]] = []


def mcp_for_unity_tool(
    name: str | None = None,
    description: str | None = None,
    **kwargs
) -> Callable:
    """
    Decorator for registering MCP tools in the server's tools directory.

    Tools are registered in the global tool registry.

    Args:
        name: Tool name (defaults to function name)
        description: Tool description
        **kwargs: Additional arguments passed to @mcp.tool()

    Example:
        @mcp_for_unity_tool(description="Does something cool")
        async def my_custom_tool(ctx: Context, ...):
            pass
    """
    def decorator(func: Callable) -> Callable:
        tool_name = name if name is not None else func.__name__
        _tool_registry.append({
            'func': func,
            'name': tool_name,
            'description': description,
            'kwargs': kwargs
        })

        return func

    return decorator


def get_registered_tools() -> list[dict[str, Any]]:
    """Get all registered tools"""
    return _tool_registry.copy()


def clear_tool_registry():
    """Clear the tool registry (useful for testing)"""
    _tool_registry.clear()

```

--------------------------------------------------------------------------------
/MCPForUnity/Editor/Tools/McpForUnityToolAttribute.cs:
--------------------------------------------------------------------------------

```csharp
using System;

namespace MCPForUnity.Editor.Tools
{
    /// <summary>
    /// Marks a class as an MCP tool handler for auto-discovery.
    /// The class must have a public static HandleCommand(JObject) method.
    /// </summary>
    [AttributeUsage(AttributeTargets.Class, AllowMultiple = false)]
    public class McpForUnityToolAttribute : Attribute
    {
        /// <summary>
        /// The command name used to route requests to this tool.
        /// If not specified, defaults to the PascalCase class name converted to snake_case.
        /// </summary>
        public string CommandName { get; }

        /// <summary>
        /// Create an MCP tool attribute with auto-generated command name.
        /// The command name will be derived from the class name (PascalCase → snake_case).
        /// Example: ManageAsset → manage_asset
        /// </summary>
        public McpForUnityToolAttribute()
        {
            CommandName = null; // Will be auto-generated
        }

        /// <summary>
        /// Create an MCP tool attribute with explicit command name.
        /// </summary>
        /// <param name="commandName">The command name (e.g., "manage_asset")</param>
        public McpForUnityToolAttribute(string commandName)
        {
            CommandName = commandName;
        }
    }
}

```

--------------------------------------------------------------------------------
/UnityMcpBridge/Editor/Tools/McpForUnityToolAttribute.cs:
--------------------------------------------------------------------------------

```csharp
using System;

namespace MCPForUnity.Editor.Tools
{
    /// <summary>
    /// Marks a class as an MCP tool handler for auto-discovery.
    /// The class must have a public static HandleCommand(JObject) method.
    /// </summary>
    [AttributeUsage(AttributeTargets.Class, AllowMultiple = false)]
    public class McpForUnityToolAttribute : Attribute
    {
        /// <summary>
        /// The command name used to route requests to this tool.
        /// If not specified, defaults to the PascalCase class name converted to snake_case.
        /// </summary>
        public string CommandName { get; }

        /// <summary>
        /// Create an MCP tool attribute with auto-generated command name.
        /// The command name will be derived from the class name (PascalCase → snake_case).
        /// Example: ManageAsset → manage_asset
        /// </summary>
        public McpForUnityToolAttribute()
        {
            CommandName = null; // Will be auto-generated
        }

        /// <summary>
        /// Create an MCP tool attribute with explicit command name.
        /// </summary>
        /// <param name="commandName">The command name (e.g., "manage_asset")</param>
        public McpForUnityToolAttribute(string commandName)
        {
            CommandName = commandName;
        }
    }
}

```

--------------------------------------------------------------------------------
/MCPForUnity/Editor/Resources/McpForUnityResourceAttribute.cs:
--------------------------------------------------------------------------------

```csharp
using System;

namespace MCPForUnity.Editor.Resources
{
    /// <summary>
    /// Marks a class as an MCP resource handler for auto-discovery.
    /// The class must have a public static HandleCommand(JObject) method.
    /// </summary>
    [AttributeUsage(AttributeTargets.Class, AllowMultiple = false)]
    public class McpForUnityResourceAttribute : Attribute
    {
        /// <summary>
        /// The resource name used to route requests to this resource.
        /// If not specified, defaults to the PascalCase class name converted to snake_case.
        /// </summary>
        public string ResourceName { get; }

        /// <summary>
        /// Create an MCP resource attribute with auto-generated resource name.
        /// The resource name will be derived from the class name (PascalCase → snake_case).
        /// Example: ManageAsset → manage_asset
        /// </summary>
        public McpForUnityResourceAttribute()
        {
            ResourceName = null; // Will be auto-generated
        }

        /// <summary>
        /// Create an MCP resource attribute with explicit resource name.
        /// </summary>
        /// <param name="resourceName">The resource name (e.g., "manage_asset")</param>
        public McpForUnityResourceAttribute(string resourceName)
        {
            ResourceName = resourceName;
        }
    }
}

```

--------------------------------------------------------------------------------
/MCPForUnity/UnityMcpServer~/src/config.py:
--------------------------------------------------------------------------------

```python
"""
Configuration settings for the MCP for Unity Server.
This file contains all configurable parameters for the server.
"""

from dataclasses import dataclass


@dataclass
class ServerConfig:
    """Main configuration class for the MCP server."""

    # Network settings
    unity_host: str = "localhost"
    unity_port: int = 6400
    mcp_port: int = 6500

    # Connection settings
    connection_timeout: float = 30.0
    buffer_size: int = 16 * 1024 * 1024  # 16MB buffer
    # Framed receive behavior
    # max seconds to wait while consuming heartbeats only
    framed_receive_timeout: float = 2.0
    # cap heartbeat frames consumed before giving up
    max_heartbeat_frames: int = 16

    # Logging settings
    log_level: str = "INFO"
    log_format: str = "%(asctime)s - %(name)s - %(levelname)s - %(message)s"

    # Server settings
    max_retries: int = 5
    retry_delay: float = 0.25
    # Backoff hint returned to clients when Unity is reloading (milliseconds)
    reload_retry_ms: int = 250
    # Number of polite retries when Unity reports reloading
    # 40 × 250ms ≈ 10s default window
    reload_max_retries: int = 40

    # Telemetry settings
    telemetry_enabled: bool = True
    # Align with telemetry.py default Cloud Run endpoint
    telemetry_endpoint: str = "https://api-prod.coplay.dev/telemetry/events"


# Create a global config instance
config = ServerConfig()

```

--------------------------------------------------------------------------------
/MCPForUnity/Editor/Dependencies/PlatformDetectors/IPlatformDetector.cs:
--------------------------------------------------------------------------------

```csharp
using MCPForUnity.Editor.Dependencies.Models;

namespace MCPForUnity.Editor.Dependencies.PlatformDetectors
{
    /// <summary>
    /// Interface for platform-specific dependency detection
    /// </summary>
    public interface IPlatformDetector
    {
        /// <summary>
        /// Platform name this detector handles
        /// </summary>
        string PlatformName { get; }

        /// <summary>
        /// Whether this detector can run on the current platform
        /// </summary>
        bool CanDetect { get; }

        /// <summary>
        /// Detect Python installation on this platform
        /// </summary>
        DependencyStatus DetectPython();

        /// <summary>
        /// Detect UV package manager on this platform
        /// </summary>
        DependencyStatus DetectUV();

        /// <summary>
        /// Detect MCP server installation on this platform
        /// </summary>
        DependencyStatus DetectMCPServer();

        /// <summary>
        /// Get platform-specific installation recommendations
        /// </summary>
        string GetInstallationRecommendations();

        /// <summary>
        /// Get platform-specific Python installation URL
        /// </summary>
        string GetPythonInstallUrl();

        /// <summary>
        /// Get platform-specific UV installation URL
        /// </summary>
        string GetUVInstallUrl();
    }
}

```

--------------------------------------------------------------------------------
/UnityMcpBridge/Editor/Dependencies/PlatformDetectors/IPlatformDetector.cs:
--------------------------------------------------------------------------------

```csharp
using MCPForUnity.Editor.Dependencies.Models;

namespace MCPForUnity.Editor.Dependencies.PlatformDetectors
{
    /// <summary>
    /// Interface for platform-specific dependency detection
    /// </summary>
    public interface IPlatformDetector
    {
        /// <summary>
        /// Platform name this detector handles
        /// </summary>
        string PlatformName { get; }

        /// <summary>
        /// Whether this detector can run on the current platform
        /// </summary>
        bool CanDetect { get; }

        /// <summary>
        /// Detect Python installation on this platform
        /// </summary>
        DependencyStatus DetectPython();

        /// <summary>
        /// Detect UV package manager on this platform
        /// </summary>
        DependencyStatus DetectUV();

        /// <summary>
        /// Detect MCP server installation on this platform
        /// </summary>
        DependencyStatus DetectMCPServer();

        /// <summary>
        /// Get platform-specific installation recommendations
        /// </summary>
        string GetInstallationRecommendations();

        /// <summary>
        /// Get platform-specific Python installation URL
        /// </summary>
        string GetPythonInstallUrl();

        /// <summary>
        /// Get platform-specific UV installation URL
        /// </summary>
        string GetUVInstallUrl();
    }
}

```

--------------------------------------------------------------------------------
/UnityMcpBridge/UnityMcpServer~/src/config.py:
--------------------------------------------------------------------------------

```python
"""
Configuration settings for the MCP for Unity Server.
This file contains all configurable parameters for the server.
"""

from dataclasses import dataclass


@dataclass
class ServerConfig:
    """Main configuration class for the MCP server."""

    # Network settings
    unity_host: str = "localhost"
    unity_port: int = 6400
    mcp_port: int = 6500

    # Connection settings
    # short initial timeout; retries use shorter timeouts
    connection_timeout: float = 1.0
    buffer_size: int = 16 * 1024 * 1024  # 16MB buffer
    # Framed receive behavior
    # max seconds to wait while consuming heartbeats only
    framed_receive_timeout: float = 2.0
    # cap heartbeat frames consumed before giving up
    max_heartbeat_frames: int = 16

    # Logging settings
    log_level: str = "INFO"
    log_format: str = "%(asctime)s - %(name)s - %(levelname)s - %(message)s"

    # Server settings
    max_retries: int = 10
    retry_delay: float = 0.25
    # Backoff hint returned to clients when Unity is reloading (milliseconds)
    reload_retry_ms: int = 250
    # Number of polite retries when Unity reports reloading
    # 40 × 250ms ≈ 10s default window
    reload_max_retries: int = 40

    # Telemetry settings
    telemetry_enabled: bool = True
    # Align with telemetry.py default Cloud Run endpoint
    telemetry_endpoint: str = "https://api-prod.coplay.dev/telemetry/events"


# Create a global config instance
config = ServerConfig()

```

--------------------------------------------------------------------------------
/MCPForUnity/UnityMcpServer~/src/resources/tests.py:
--------------------------------------------------------------------------------

```python
from typing import Annotated, Literal
from pydantic import BaseModel, Field

from models import MCPResponse
from registry import mcp_for_unity_resource
from unity_connection import async_send_command_with_retry


class TestItem(BaseModel):
    name: Annotated[str, Field(description="The name of the test.")]
    full_name: Annotated[str, Field(description="The full name of the test.")]
    mode: Annotated[Literal["EditMode", "PlayMode"],
                    Field(description="The mode the test is for.")]


class GetTestsResponse(MCPResponse):
    data: list[TestItem] = []


@mcp_for_unity_resource(uri="mcpforunity://tests", name="get_tests", description="Provides a list of all tests.")
async def get_tests() -> GetTestsResponse:
    """Provides a list of all tests."""
    response = await async_send_command_with_retry("get_tests", {})
    return GetTestsResponse(**response) if isinstance(response, dict) else response


@mcp_for_unity_resource(uri="mcpforunity://tests/{mode}", name="get_tests_for_mode", description="Provides a list of tests for a specific mode.")
async def get_tests_for_mode(mode: Annotated[Literal["EditMode", "PlayMode"], Field(description="The mode to filter tests by.")]) -> GetTestsResponse:
    """Provides a list of tests for a specific mode."""
    response = await async_send_command_with_retry("get_tests_for_mode", {"mode": mode})
    return GetTestsResponse(**response) if isinstance(response, dict) else response

```

--------------------------------------------------------------------------------
/UnityMcpBridge/Editor/Tools/MenuItems/ManageMenuItem.cs:
--------------------------------------------------------------------------------

```csharp
using System;
using Newtonsoft.Json.Linq;
using MCPForUnity.Editor.Helpers;

namespace MCPForUnity.Editor.Tools.MenuItems
{
    [McpForUnityTool("manage_menu_item")]
    public static class ManageMenuItem
    {
        /// <summary>
        /// Routes actions: execute, list, exists, refresh
        /// </summary>
        public static object HandleCommand(JObject @params)
        {
            string action = @params["action"]?.ToString()?.ToLowerInvariant();
            if (string.IsNullOrEmpty(action))
            {
                return Response.Error("Action parameter is required. Valid actions are: execute, list, exists, refresh.");
            }

            try
            {
                switch (action)
                {
                    case "execute":
                        return MenuItemExecutor.Execute(@params);
                    case "list":
                        return MenuItemsReader.List(@params);
                    case "exists":
                        return MenuItemsReader.Exists(@params);
                    default:
                        return Response.Error($"Unknown action: '{action}'. Valid actions are: execute, list, exists, refresh.");
                }
            }
            catch (Exception e)
            {
                McpLog.Error($"[ManageMenuItem] Action '{action}' failed: {e}");
                return Response.Error($"Internal error: {e.Message}");
            }
        }
    }
}

```

--------------------------------------------------------------------------------
/MCPForUnity/UnityMcpServer~/src/registry/resource_registry.py:
--------------------------------------------------------------------------------

```python
"""
Resource registry for auto-discovery of MCP resources.
"""
from typing import Callable, Any

# Global registry to collect decorated resources
_resource_registry: list[dict[str, Any]] = []


def mcp_for_unity_resource(
    uri: str,
    name: str | None = None,
    description: str | None = None,
    **kwargs
) -> Callable:
    """
    Decorator for registering MCP resources in the server's resources directory.

    Resources are registered in the global resource registry.

    Args:
        name: Resource name (defaults to function name)
        description: Resource description
        **kwargs: Additional arguments passed to @mcp.resource()

    Example:
        @mcp_for_unity_resource("mcpforunity://resource", description="Gets something interesting")
        async def my_custom_resource(ctx: Context, ...):
            pass
    """
    def decorator(func: Callable) -> Callable:
        resource_name = name if name is not None else func.__name__
        _resource_registry.append({
            'func': func,
            'uri': uri,
            'name': resource_name,
            'description': description,
            'kwargs': kwargs
        })

        return func

    return decorator


def get_registered_resources() -> list[dict[str, Any]]:
    """Get all registered resources"""
    return _resource_registry.copy()


def clear_resource_registry():
    """Clear the resource registry (useful for testing)"""
    _resource_registry.clear()

```

--------------------------------------------------------------------------------
/.github/workflows/unity-tests.yml:
--------------------------------------------------------------------------------

```yaml
name: Unity Tests

on:
  push:
    branches: [main]
    paths:
      - TestProjects/UnityMCPTests/**
      - MCPForUnity/Editor/**
      - .github/workflows/unity-tests.yml

jobs:
  testAllModes:
    name: Test in ${{ matrix.testMode }}
    runs-on: ubuntu-latest
    strategy:
      fail-fast: false
      matrix:
        projectPath:
          - TestProjects/UnityMCPTests
        testMode:
          - editmode
        unityVersion:
          - 2021.3.45f2
    steps:
      # Checkout
      - name: Checkout repository
        uses: actions/checkout@v4
        with:
          lfs: true

      # Cache
      - uses: actions/cache@v4
        with:
          path: ${{ matrix.projectPath }}/Library
          key: Library-${{ matrix.projectPath }}-${{ matrix.unityVersion }}
          restore-keys: |
            Library-${{ matrix.projectPath }}-
            Library-

      # Test
      - name: Run tests
        uses: game-ci/unity-test-runner@v4
        id: tests
        env:
          UNITY_EMAIL: ${{ secrets.UNITY_EMAIL }}
          UNITY_PASSWORD: ${{ secrets.UNITY_PASSWORD }}
          UNITY_LICENSE: ${{ secrets.UNITY_LICENSE }}
        with:
          projectPath: ${{ matrix.projectPath }}
          unityVersion: ${{ matrix.unityVersion }}
          testMode: ${{ matrix.testMode }}

      # Upload test results
      - uses: actions/upload-artifact@v4
        if: always()
        with:
          name: Test results for ${{ matrix.testMode }}
          path: ${{ steps.tests.outputs.artifactsPath }}

```

--------------------------------------------------------------------------------
/MCPForUnity/Editor/Helpers/PackageInstaller.cs:
--------------------------------------------------------------------------------

```csharp
using UnityEditor;
using UnityEngine;

namespace MCPForUnity.Editor.Helpers
{
    /// <summary>
    /// Handles automatic installation of the MCP server when the package is first installed.
    /// </summary>
    [InitializeOnLoad]
    public static class PackageInstaller
    {
        private const string InstallationFlagKey = "MCPForUnity.ServerInstalled";

        static PackageInstaller()
        {
            // Check if this is the first time the package is loaded
            if (!EditorPrefs.GetBool(InstallationFlagKey, false))
            {
                // Schedule the installation for after Unity is fully loaded
                EditorApplication.delayCall += InstallServerOnFirstLoad;
            }
        }

        private static void InstallServerOnFirstLoad()
        {
            try
            {
                ServerInstaller.EnsureServerInstalled();

                // Mark as installed/checked
                EditorPrefs.SetBool(InstallationFlagKey, true);

                // Only log success if server was actually embedded and copied
                if (ServerInstaller.HasEmbeddedServer())
                {
                    McpLog.Info("MCP server installation completed successfully.");
                }
            }
            catch (System.Exception)
            {
                EditorPrefs.SetBool(InstallationFlagKey, true); // Mark as handled
                McpLog.Info("Server installation pending. Open Window > MCP For Unity to download the server.");
            }
        }
    }
}

```

--------------------------------------------------------------------------------
/TestProjects/UnityMCPTests/Assets/Tests/EditMode/Tools/ExecuteMenuItemTests.cs:
--------------------------------------------------------------------------------

```csharp
using NUnit.Framework;
using Newtonsoft.Json.Linq;
using MCPForUnity.Editor.Tools;

namespace MCPForUnityTests.Editor.Tools
{
    public class ExecuteMenuItemTests
    {
        private static JObject ToJO(object o) => JObject.FromObject(o);

        [Test]
        public void Execute_MissingParam_ReturnsError()
        {
            var res = ExecuteMenuItem.HandleCommand(new JObject());
            var jo = ToJO(res);
            Assert.IsFalse((bool)jo["success"], "Expected success false");
            StringAssert.Contains("Required parameter", (string)jo["error"]);
        }

        [Test]
        public void Execute_Blacklisted_ReturnsError()
        {
            var res = ExecuteMenuItem.HandleCommand(new JObject { ["menuPath"] = "File/Quit" });
            var jo = ToJO(res);
            Assert.IsFalse((bool)jo["success"], "Expected success false for blacklisted menu");
            StringAssert.Contains("blocked for safety", (string)jo["error"], "Expected blacklist message");
        }

        [Test]
        public void Execute_NonBlacklisted_ReturnsImmediateSuccess()
        {
            // We don't rely on the menu actually existing; execution is delayed and we only check the immediate response shape
            var res = ExecuteMenuItem.HandleCommand(new JObject { ["menuPath"] = "File/Save Project" });
            var jo = ToJO(res);
            Assert.IsTrue((bool)jo["success"], "Expected immediate success response");
            StringAssert.Contains("Attempted to execute menu item", (string)jo["message"], "Expected attempt message");
        }
    }
}

```

--------------------------------------------------------------------------------
/MCPForUnity/UnityMcpServer~/src/tools/__init__.py:
--------------------------------------------------------------------------------

```python
"""
MCP Tools package - Auto-discovers and registers all tools in this directory.
"""
import logging
from pathlib import Path

from mcp.server.fastmcp import FastMCP
from telemetry_decorator import telemetry_tool

from registry import get_registered_tools
from module_discovery import discover_modules

logger = logging.getLogger("mcp-for-unity-server")

# Export decorator for easy imports within tools
__all__ = ['register_all_tools']


def register_all_tools(mcp: FastMCP):
    """
    Auto-discover and register all tools in the tools/ directory.

    Any .py file in this directory or subdirectories with @mcp_for_unity_tool decorated
    functions will be automatically registered.
    """
    logger.info("Auto-discovering MCP for Unity Server tools...")
    # Dynamic import of all modules in this directory
    tools_dir = Path(__file__).parent

    # Discover and import all modules
    list(discover_modules(tools_dir, __package__))

    tools = get_registered_tools()

    if not tools:
        logger.warning("No MCP tools registered!")
        return

    for tool_info in tools:
        func = tool_info['func']
        tool_name = tool_info['name']
        description = tool_info['description']
        kwargs = tool_info['kwargs']

        # Apply the @mcp.tool decorator and telemetry
        wrapped = telemetry_tool(tool_name)(func)
        wrapped = mcp.tool(
            name=tool_name, description=description, **kwargs)(wrapped)
        tool_info['func'] = wrapped
        logger.debug(f"Registered tool: {tool_name} - {description}")

    logger.info(f"Registered {len(tools)} MCP tools")

```

--------------------------------------------------------------------------------
/MCPForUnity/UnityMcpServer~/src/tools/run_tests.py:
--------------------------------------------------------------------------------

```python
"""Tool for executing Unity Test Runner suites."""
from typing import Annotated, Literal, Any

from mcp.server.fastmcp import Context
from pydantic import BaseModel, Field

from models import MCPResponse
from registry import mcp_for_unity_tool
from unity_connection import async_send_command_with_retry


class RunTestsSummary(BaseModel):
    total: int
    passed: int
    failed: int
    skipped: int
    durationSeconds: float
    resultState: str


class RunTestsTestResult(BaseModel):
    name: str
    fullName: str
    state: str
    durationSeconds: float
    message: str | None = None
    stackTrace: str | None = None
    output: str | None = None


class RunTestsResult(BaseModel):
    mode: str
    summary: RunTestsSummary
    results: list[RunTestsTestResult]


class RunTestsResponse(MCPResponse):
    data: RunTestsResult | None = None


@mcp_for_unity_tool(description="Runs Unity tests for the specified mode")
async def run_tests(
    ctx: Context,
    mode: Annotated[Literal["edit", "play"], Field(
        description="Unity test mode to run")] = "edit",
    timeout_seconds: Annotated[int, Field(
        description="Optional timeout in seconds for the Unity test run")] | None = None,
) -> RunTestsResponse:
    await ctx.info(f"Processing run_tests: mode={mode}")

    params: dict[str, Any] = {"mode": mode}
    if timeout_seconds is not None:
        params["timeoutSeconds"] = timeout_seconds

    response = await async_send_command_with_retry("run_tests", params)
    await ctx.info(f'Response {response}')
    return RunTestsResponse(**response) if isinstance(response, dict) else response

```

--------------------------------------------------------------------------------
/UnityMcpBridge/Editor/Helpers/PackageInstaller.cs:
--------------------------------------------------------------------------------

```csharp
using UnityEditor;
using UnityEngine;

namespace MCPForUnity.Editor.Helpers
{
    /// <summary>
    /// Handles automatic installation of the Python server when the package is first installed.
    /// </summary>
    [InitializeOnLoad]
    public static class PackageInstaller
    {
        private const string InstallationFlagKey = "MCPForUnity.ServerInstalled";

        static PackageInstaller()
        {
            // Check if this is the first time the package is loaded
            if (!EditorPrefs.GetBool(InstallationFlagKey, false))
            {
                // Schedule the installation for after Unity is fully loaded
                EditorApplication.delayCall += InstallServerOnFirstLoad;
            }
        }

        private static void InstallServerOnFirstLoad()
        {
            try
            {
                Debug.Log("<b><color=#2EA3FF>MCP-FOR-UNITY</color></b>: Installing Python server...");
                ServerInstaller.EnsureServerInstalled();

                // Mark as installed
                EditorPrefs.SetBool(InstallationFlagKey, true);

                Debug.Log("<b><color=#2EA3FF>MCP-FOR-UNITY</color></b>: Python server installation completed successfully.");
            }
            catch (System.Exception ex)
            {
                Debug.LogError($"<b><color=#2EA3FF>MCP-FOR-UNITY</color></b>: Failed to install Python server: {ex.Message}");
                Debug.LogWarning("<b><color=#2EA3FF>MCP-FOR-UNITY</color></b>: You may need to manually install the Python server. Check the MCP For Unity Window for instructions.");
            }
        }
    }
}

```

--------------------------------------------------------------------------------
/UnityMcpBridge/UnityMcpServer~/src/tools/manage_menu_item.py:
--------------------------------------------------------------------------------

```python
"""
Defines the manage_menu_item tool for executing and reading Unity Editor menu items.
"""
import asyncio
from typing import Annotated, Any, Literal

from mcp.server.fastmcp import Context
from registry import mcp_for_unity_tool
from unity_connection import async_send_command_with_retry


@mcp_for_unity_tool(
    description="Manage Unity menu items (execute/list/exists). If you're not sure what menu item to use, use the 'list' action to find it before using 'execute'."
)
async def manage_menu_item(
    ctx: Context,
    action: Annotated[Literal["execute", "list", "exists"], "Read and execute Unity menu items."],
    menu_path: Annotated[str,
                         "Menu path for 'execute' or 'exists' (e.g., 'File/Save Project')"] | None = None,
    search: Annotated[str,
                      "Optional filter string for 'list' (e.g., 'Save')"] | None = None,
    refresh: Annotated[bool,
                       "Optional flag to force refresh of the menu cache when listing"] | None = None,
) -> dict[str, Any]:
    ctx.info(f"Processing manage_menu_item: {action}")
    # Prepare parameters for the C# handler
    params_dict: dict[str, Any] = {
        "action": action,
        "menuPath": menu_path,
        "search": search,
        "refresh": refresh,
    }
    # Remove None values
    params_dict = {k: v for k, v in params_dict.items() if v is not None}

    # Get the current asyncio event loop
    loop = asyncio.get_running_loop()

    # Use centralized async retry helper
    result = await async_send_command_with_retry("manage_menu_item", params_dict, loop=loop)
    return result if isinstance(result, dict) else {"success": False, "message": str(result)}

```

--------------------------------------------------------------------------------
/MCPForUnity/Editor/Models/McpClient.cs:
--------------------------------------------------------------------------------

```csharp
namespace MCPForUnity.Editor.Models
{
    public class McpClient
    {
        public string name;
        public string windowsConfigPath;
        public string macConfigPath;
        public string linuxConfigPath;
        public McpTypes mcpType;
        public string configStatus;
        public McpStatus status = McpStatus.NotConfigured;

        // Helper method to convert the enum to a display string
        public string GetStatusDisplayString()
        {
            return status switch
            {
                McpStatus.NotConfigured => "Not Configured",
                McpStatus.Configured => "Configured",
                McpStatus.Running => "Running",
                McpStatus.Connected => "Connected",
                McpStatus.IncorrectPath => "Incorrect Path",
                McpStatus.CommunicationError => "Communication Error",
                McpStatus.NoResponse => "No Response",
                McpStatus.UnsupportedOS => "Unsupported OS",
                McpStatus.MissingConfig => "Missing MCPForUnity Config",
                McpStatus.Error => configStatus.StartsWith("Error:") ? configStatus : "Error",
                _ => "Unknown",
            };
        }

        // Helper method to set both status enum and string for backward compatibility
        public void SetStatus(McpStatus newStatus, string errorDetails = null)
        {
            status = newStatus;

            if (newStatus == McpStatus.Error && !string.IsNullOrEmpty(errorDetails))
            {
                configStatus = $"Error: {errorDetails}";
            }
            else
            {
                configStatus = GetStatusDisplayString();
            }
        }
    }
}

```

--------------------------------------------------------------------------------
/UnityMcpBridge/Editor/Models/McpClient.cs:
--------------------------------------------------------------------------------

```csharp
namespace MCPForUnity.Editor.Models
{
    public class McpClient
    {
        public string name;
        public string windowsConfigPath;
        public string macConfigPath;
        public string linuxConfigPath;
        public McpTypes mcpType;
        public string configStatus;
        public McpStatus status = McpStatus.NotConfigured;

        // Helper method to convert the enum to a display string
        public string GetStatusDisplayString()
        {
            return status switch
            {
                McpStatus.NotConfigured => "Not Configured",
                McpStatus.Configured => "Configured",
                McpStatus.Running => "Running",
                McpStatus.Connected => "Connected",
                McpStatus.IncorrectPath => "Incorrect Path",
                McpStatus.CommunicationError => "Communication Error",
                McpStatus.NoResponse => "No Response",
                McpStatus.UnsupportedOS => "Unsupported OS",
                McpStatus.MissingConfig => "Missing MCPForUnity Config",
                McpStatus.Error => configStatus.StartsWith("Error:") ? configStatus : "Error",
                _ => "Unknown",
            };
        }

        // Helper method to set both status enum and string for backward compatibility
        public void SetStatus(McpStatus newStatus, string errorDetails = null)
        {
            status = newStatus;

            if (newStatus == McpStatus.Error && !string.IsNullOrEmpty(errorDetails))
            {
                configStatus = $"Error: {errorDetails}";
            }
            else
            {
                configStatus = GetStatusDisplayString();
            }
        }
    }
}

```

--------------------------------------------------------------------------------
/TestProjects/UnityMCPTests/Assets/Tests/EditMode/Tools/CommandRegistryTests.cs:
--------------------------------------------------------------------------------

```csharp
using System;
using System.Collections.Generic;
using System.Linq;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using NUnit.Framework;
using MCPForUnity.Editor.Tools;

namespace MCPForUnityTests.Editor.Tools
{
    public class CommandRegistryTests
    {
        [OneTimeSetUp]
        public void OneTimeSetUp()
        {
            // Ensure CommandRegistry is initialized before tests run
            CommandRegistry.Initialize();
        }

        [Test]
        public void GetHandler_ThrowsException_ForUnknownCommand()
        {
            var unknown = "nonexistent_command_that_should_not_exist";

            Assert.Throws<InvalidOperationException>(() =>
            {
                CommandRegistry.GetHandler(unknown);
            }, "Should throw InvalidOperationException for unknown handler");
        }

        [Test]
        public void AutoDiscovery_RegistersAllBuiltInTools()
        {
            // Verify that all expected built-in tools are registered by trying to get their handlers
            var expectedTools = new[]
            {
                "manage_asset",
                "manage_editor",
                "manage_gameobject",
                "manage_scene",
                "manage_script",
                "manage_shader",
                "read_console",
                "execute_menu_item",
                "manage_prefabs"
            };

            foreach (var toolName in expectedTools)
            {
                Assert.DoesNotThrow(() =>
                {
                    var handler = CommandRegistry.GetHandler(toolName);
                    Assert.IsNotNull(handler, $"Handler for '{toolName}' should not be null");
                }, $"Expected tool '{toolName}' to be auto-registered");
            }
        }
    }
}

```

--------------------------------------------------------------------------------
/MCPForUnity/Editor/Services/PythonToolRegistryService.cs:
--------------------------------------------------------------------------------

```csharp
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Security.Cryptography;
using UnityEditor;
using UnityEngine;
using MCPForUnity.Editor.Data;

namespace MCPForUnity.Editor.Services
{
    public class PythonToolRegistryService : IPythonToolRegistryService
    {
        public IEnumerable<PythonToolsAsset> GetAllRegistries()
        {
            // Find all PythonToolsAsset instances in the project
            string[] guids = AssetDatabase.FindAssets("t:PythonToolsAsset");
            foreach (string guid in guids)
            {
                string path = AssetDatabase.GUIDToAssetPath(guid);
                var asset = AssetDatabase.LoadAssetAtPath<PythonToolsAsset>(path);
                if (asset != null)
                    yield return asset;
            }
        }

        public bool NeedsSync(PythonToolsAsset registry, TextAsset file)
        {
            if (!registry.useContentHashing) return true;

            string currentHash = ComputeHash(file);
            return registry.NeedsSync(file, currentHash);
        }

        public void RecordSync(PythonToolsAsset registry, TextAsset file)
        {
            string hash = ComputeHash(file);
            registry.RecordSync(file, hash);
            EditorUtility.SetDirty(registry);
        }

        public string ComputeHash(TextAsset file)
        {
            if (file == null || string.IsNullOrEmpty(file.text))
                return string.Empty;

            using (var sha256 = SHA256.Create())
            {
                byte[] bytes = System.Text.Encoding.UTF8.GetBytes(file.text);
                byte[] hash = sha256.ComputeHash(bytes);
                return BitConverter.ToString(hash).Replace("-", "").ToLower();
            }
        }
    }
}

```

--------------------------------------------------------------------------------
/MCPForUnity/UnityMcpServer~/src/resources/__init__.py:
--------------------------------------------------------------------------------

```python
"""
MCP Resources package - Auto-discovers and registers all resources in this directory.
"""
import logging
from pathlib import Path

from mcp.server.fastmcp import FastMCP
from telemetry_decorator import telemetry_resource

from registry import get_registered_resources
from module_discovery import discover_modules

logger = logging.getLogger("mcp-for-unity-server")

# Export decorator for easy imports within tools
__all__ = ['register_all_resources']


def register_all_resources(mcp: FastMCP):
    """
    Auto-discover and register all resources in the resources/ directory.

    Any .py file in this directory or subdirectories with @mcp_for_unity_resource decorated
    functions will be automatically registered.
    """
    logger.info("Auto-discovering MCP for Unity Server resources...")
    # Dynamic import of all modules in this directory
    resources_dir = Path(__file__).parent

    # Discover and import all modules
    list(discover_modules(resources_dir, __package__))

    resources = get_registered_resources()

    if not resources:
        logger.warning("No MCP resources registered!")
        return

    for resource_info in resources:
        func = resource_info['func']
        uri = resource_info['uri']
        resource_name = resource_info['name']
        description = resource_info['description']
        kwargs = resource_info['kwargs']

        # Apply the @mcp.resource decorator and telemetry
        wrapped = telemetry_resource(resource_name)(func)
        wrapped = mcp.resource(uri=uri, name=resource_name,
                               description=description, **kwargs)(wrapped)
        resource_info['func'] = wrapped
        logger.debug(f"Registered resource: {resource_name} - {description}")

    logger.info(f"Registered {len(resources)} MCP resources")

```

--------------------------------------------------------------------------------
/MCPForUnity/Editor/Dependencies/Models/DependencyStatus.cs:
--------------------------------------------------------------------------------

```csharp
using System;

namespace MCPForUnity.Editor.Dependencies.Models
{
    /// <summary>
    /// Represents the status of a dependency check
    /// </summary>
    [Serializable]
    public class DependencyStatus
    {
        /// <summary>
        /// Name of the dependency being checked
        /// </summary>
        public string Name { get; set; }

        /// <summary>
        /// Whether the dependency is available and functional
        /// </summary>
        public bool IsAvailable { get; set; }

        /// <summary>
        /// Version information if available
        /// </summary>
        public string Version { get; set; }

        /// <summary>
        /// Path to the dependency executable/installation
        /// </summary>
        public string Path { get; set; }

        /// <summary>
        /// Additional details about the dependency status
        /// </summary>
        public string Details { get; set; }

        /// <summary>
        /// Error message if dependency check failed
        /// </summary>
        public string ErrorMessage { get; set; }

        /// <summary>
        /// Whether this dependency is required for basic functionality
        /// </summary>
        public bool IsRequired { get; set; }

        /// <summary>
        /// Suggested installation method or URL
        /// </summary>
        public string InstallationHint { get; set; }

        public DependencyStatus(string name, bool isRequired = true)
        {
            Name = name;
            IsRequired = isRequired;
            IsAvailable = false;
        }

        public override string ToString()
        {
            var status = IsAvailable ? "✓" : "✗";
            var version = !string.IsNullOrEmpty(Version) ? $" ({Version})" : "";
            return $"{status} {Name}{version}";
        }
    }
}

```

--------------------------------------------------------------------------------
/UnityMcpBridge/Editor/Dependencies/Models/DependencyStatus.cs:
--------------------------------------------------------------------------------

```csharp
using System;

namespace MCPForUnity.Editor.Dependencies.Models
{
    /// <summary>
    /// Represents the status of a dependency check
    /// </summary>
    [Serializable]
    public class DependencyStatus
    {
        /// <summary>
        /// Name of the dependency being checked
        /// </summary>
        public string Name { get; set; }

        /// <summary>
        /// Whether the dependency is available and functional
        /// </summary>
        public bool IsAvailable { get; set; }

        /// <summary>
        /// Version information if available
        /// </summary>
        public string Version { get; set; }

        /// <summary>
        /// Path to the dependency executable/installation
        /// </summary>
        public string Path { get; set; }

        /// <summary>
        /// Additional details about the dependency status
        /// </summary>
        public string Details { get; set; }

        /// <summary>
        /// Error message if dependency check failed
        /// </summary>
        public string ErrorMessage { get; set; }

        /// <summary>
        /// Whether this dependency is required for basic functionality
        /// </summary>
        public bool IsRequired { get; set; }

        /// <summary>
        /// Suggested installation method or URL
        /// </summary>
        public string InstallationHint { get; set; }

        public DependencyStatus(string name, bool isRequired = true)
        {
            Name = name;
            IsRequired = isRequired;
            IsAvailable = false;
        }

        public override string ToString()
        {
            var status = IsAvailable ? "✓" : "✗";
            var version = !string.IsNullOrEmpty(Version) ? $" ({Version})" : "";
            return $"{status} {Name}{version}";
        }
    }
}

```

--------------------------------------------------------------------------------
/tests/test_read_resource_minimal.py:
--------------------------------------------------------------------------------

```python
from tools.resource_tools import register_resource_tools  # type: ignore
import sys
import pathlib
import asyncio
import types
import pytest

ROOT = pathlib.Path(__file__).resolve().parents[1]
SRC = ROOT / "MCPForUnity" / "UnityMcpServer~" / "src"
sys.path.insert(0, str(SRC))

# Stub mcp.server.fastmcp to satisfy imports without full package
mcp_pkg = types.ModuleType("mcp")
server_pkg = types.ModuleType("mcp.server")
fastmcp_pkg = types.ModuleType("mcp.server.fastmcp")


class _Dummy:
    pass


fastmcp_pkg.FastMCP = _Dummy
fastmcp_pkg.Context = _Dummy
server_pkg.fastmcp = fastmcp_pkg
mcp_pkg.server = server_pkg
sys.modules.setdefault("mcp", mcp_pkg)
sys.modules.setdefault("mcp.server", server_pkg)
sys.modules.setdefault("mcp.server.fastmcp", fastmcp_pkg)


class DummyMCP:
    def __init__(self):
        self.tools = {}

    def tool(self, *args, **kwargs):
        def deco(fn):
            self.tools[fn.__name__] = fn
            return fn
        return deco


@pytest.fixture()
def resource_tools():
    mcp = DummyMCP()
    register_resource_tools(mcp)
    return mcp.tools


def test_read_resource_minimal_metadata_only(resource_tools, tmp_path):
    proj = tmp_path
    assets = proj / "Assets"
    assets.mkdir()
    f = assets / "A.txt"
    content = "hello world"
    f.write_text(content, encoding="utf-8")

    read_resource = resource_tools["read_resource"]
    loop = asyncio.new_event_loop()
    try:
        resp = loop.run_until_complete(
            read_resource(uri="unity://path/Assets/A.txt",
                          ctx=None, project_root=str(proj))
        )
    finally:
        loop.close()

    assert resp["success"] is True
    data = resp["data"]
    assert "text" not in data
    meta = data["metadata"]
    assert "sha256" in meta and len(meta["sha256"]) == 64
    assert meta["lengthBytes"] == len(content.encode("utf-8"))

```

--------------------------------------------------------------------------------
/UnityMcpBridge/UnityMcpServer~/src/tools/__init__.py:
--------------------------------------------------------------------------------

```python
"""
MCP Tools package - Auto-discovers and registers all tools in this directory.
"""
import importlib
import logging
from pathlib import Path
import pkgutil

from mcp.server.fastmcp import FastMCP
from telemetry_decorator import telemetry_tool

from registry import get_registered_tools, mcp_for_unity_tool

logger = logging.getLogger("mcp-for-unity-server")

# Export decorator for easy imports within tools
__all__ = ['register_all_tools', 'mcp_for_unity_tool']


def register_all_tools(mcp: FastMCP):
    """
    Auto-discover and register all tools in the tools/ directory.

    Any .py file in this directory with @mcp_for_unity_tool decorated
    functions will be automatically registered.
    """
    logger.info("Auto-discovering MCP for Unity Server tools...")
    # Dynamic import of all modules in this directory
    tools_dir = Path(__file__).parent

    for _, module_name, _ in pkgutil.iter_modules([str(tools_dir)]):
        # Skip private modules and __init__
        if module_name.startswith('_'):
            continue

        try:
            importlib.import_module(f'.{module_name}', __package__)
        except Exception as e:
            logger.warning(f"Failed to import tool module {module_name}: {e}")

    tools = get_registered_tools()

    if not tools:
        logger.warning("No MCP tools registered!")
        return

    for tool_info in tools:
        func = tool_info['func']
        tool_name = tool_info['name']
        description = tool_info['description']
        kwargs = tool_info['kwargs']

        # Apply the @mcp.tool decorator and telemetry
        wrapped = telemetry_tool(tool_name)(func)
        wrapped = mcp.tool(
            name=tool_name, description=description, **kwargs)(wrapped)
        tool_info['func'] = wrapped
        logger.info(f"Registered tool: {tool_name} - {description}")

    logger.info(f"Registered {len(tools)} MCP tools")

```

--------------------------------------------------------------------------------
/TestProjects/UnityMCPTests/Packages/manifest.json:
--------------------------------------------------------------------------------

```json
{
  "dependencies": {
    "com.coplaydev.unity-mcp": "file:../../../MCPForUnity",
    "com.unity.collab-proxy": "2.5.2",
    "com.unity.feature.development": "1.0.1",
    "com.unity.ide.rider": "3.0.31",
    "com.unity.ide.visualstudio": "2.0.22",
    "com.unity.ide.vscode": "1.2.5",
    "com.unity.ide.windsurf": "https://github.com/Asuta/com.unity.ide.windsurf.git",
    "com.unity.test-framework": "1.1.33",
    "com.unity.textmeshpro": "3.0.6",
    "com.unity.timeline": "1.6.5",
    "com.unity.ugui": "1.0.0",
    "com.unity.visualscripting": "1.9.4",
    "com.unity.modules.ai": "1.0.0",
    "com.unity.modules.androidjni": "1.0.0",
    "com.unity.modules.animation": "1.0.0",
    "com.unity.modules.assetbundle": "1.0.0",
    "com.unity.modules.audio": "1.0.0",
    "com.unity.modules.cloth": "1.0.0",
    "com.unity.modules.director": "1.0.0",
    "com.unity.modules.imageconversion": "1.0.0",
    "com.unity.modules.imgui": "1.0.0",
    "com.unity.modules.jsonserialize": "1.0.0",
    "com.unity.modules.particlesystem": "1.0.0",
    "com.unity.modules.physics": "1.0.0",
    "com.unity.modules.physics2d": "1.0.0",
    "com.unity.modules.screencapture": "1.0.0",
    "com.unity.modules.terrain": "1.0.0",
    "com.unity.modules.terrainphysics": "1.0.0",
    "com.unity.modules.tilemap": "1.0.0",
    "com.unity.modules.ui": "1.0.0",
    "com.unity.modules.uielements": "1.0.0",
    "com.unity.modules.umbra": "1.0.0",
    "com.unity.modules.unityanalytics": "1.0.0",
    "com.unity.modules.unitywebrequest": "1.0.0",
    "com.unity.modules.unitywebrequestassetbundle": "1.0.0",
    "com.unity.modules.unitywebrequestaudio": "1.0.0",
    "com.unity.modules.unitywebrequesttexture": "1.0.0",
    "com.unity.modules.unitywebrequestwww": "1.0.0",
    "com.unity.modules.vehicles": "1.0.0",
    "com.unity.modules.video": "1.0.0",
    "com.unity.modules.vr": "1.0.0",
    "com.unity.modules.wind": "1.0.0",
    "com.unity.modules.xr": "1.0.0"
  }
}

```

--------------------------------------------------------------------------------
/tests/test_validate_script_summary.py:
--------------------------------------------------------------------------------

```python
import sys
import pathlib
import importlib.util
import types

ROOT = pathlib.Path(__file__).resolve().parents[1]
SRC = ROOT / "MCPForUnity" / "UnityMcpServer~" / "src"
sys.path.insert(0, str(SRC))

# stub mcp.server.fastmcp similar to test_get_sha
mcp_pkg = types.ModuleType("mcp")
server_pkg = types.ModuleType("mcp.server")
fastmcp_pkg = types.ModuleType("mcp.server.fastmcp")


class _Dummy:
    pass


fastmcp_pkg.FastMCP = _Dummy
fastmcp_pkg.Context = _Dummy
server_pkg.fastmcp = fastmcp_pkg
mcp_pkg.server = server_pkg
sys.modules.setdefault("mcp", mcp_pkg)
sys.modules.setdefault("mcp.server", server_pkg)
sys.modules.setdefault("mcp.server.fastmcp", fastmcp_pkg)


def _load_module(path: pathlib.Path, name: str):
    spec = importlib.util.spec_from_file_location(name, path)
    mod = importlib.util.module_from_spec(spec)
    spec.loader.exec_module(mod)
    return mod


manage_script = _load_module(
    SRC / "tools" / "manage_script.py", "manage_script_mod")


class DummyMCP:
    def __init__(self):
        self.tools = {}

    def tool(self, *args, **kwargs):
        def deco(fn):
            self.tools[fn.__name__] = fn
            return fn
        return deco


def setup_tools():
    mcp = DummyMCP()
    manage_script.register_manage_script_tools(mcp)
    return mcp.tools


def test_validate_script_returns_counts(monkeypatch):
    tools = setup_tools()
    validate_script = tools["validate_script"]

    def fake_send(cmd, params):
        return {
            "success": True,
            "data": {
                "diagnostics": [
                    {"severity": "warning"},
                    {"severity": "error"},
                    {"severity": "fatal"},
                ]
            },
        }

    monkeypatch.setattr(manage_script, "send_command_with_retry", fake_send)

    resp = validate_script(None, uri="unity://path/Assets/Scripts/A.cs")
    assert resp == {"success": True, "data": {"warnings": 1, "errors": 2}}

```

--------------------------------------------------------------------------------
/MCPForUnity/Editor/Services/IBridgeControlService.cs:
--------------------------------------------------------------------------------

```csharp
namespace MCPForUnity.Editor.Services
{
    /// <summary>
    /// Service for controlling the MCP for Unity Bridge connection
    /// </summary>
    public interface IBridgeControlService
    {
        /// <summary>
        /// Gets whether the bridge is currently running
        /// </summary>
        bool IsRunning { get; }
        
        /// <summary>
        /// Gets the current port the bridge is listening on
        /// </summary>
        int CurrentPort { get; }
        
        /// <summary>
        /// Gets whether the bridge is in auto-connect mode
        /// </summary>
        bool IsAutoConnectMode { get; }
        
        /// <summary>
        /// Starts the MCP for Unity Bridge
        /// </summary>
        void Start();
        
        /// <summary>
        /// Stops the MCP for Unity Bridge
        /// </summary>
        void Stop();
        
        /// <summary>
        /// Verifies the bridge connection by sending a ping and waiting for a pong response
        /// </summary>
        /// <param name="port">The port to verify</param>
        /// <returns>Verification result with detailed status</returns>
        BridgeVerificationResult Verify(int port);
    }
    
    /// <summary>
    /// Result of a bridge verification attempt
    /// </summary>
    public class BridgeVerificationResult
    {
        /// <summary>
        /// Whether the verification was successful
        /// </summary>
        public bool Success { get; set; }
        
        /// <summary>
        /// Human-readable message about the verification result
        /// </summary>
        public string Message { get; set; }
        
        /// <summary>
        /// Whether the handshake was valid (FRAMING=1 protocol)
        /// </summary>
        public bool HandshakeValid { get; set; }
        
        /// <summary>
        /// Whether the ping/pong exchange succeeded
        /// </summary>
        public bool PingSucceeded { get; set; }
    }
}

```

--------------------------------------------------------------------------------
/MCPForUnity/UnityMcpServer~/src/module_discovery.py:
--------------------------------------------------------------------------------

```python
"""
Shared module discovery utilities for auto-registering tools and resources.
"""
import importlib
import logging
from pathlib import Path
import pkgutil
from typing import Generator

logger = logging.getLogger("mcp-for-unity-server")


def discover_modules(base_dir: Path, package_name: str) -> Generator[str, None, None]:
    """
    Discover and import all Python modules in a directory and its subdirectories.

    Args:
        base_dir: The base directory to search for modules
        package_name: The package name to use for relative imports (e.g., 'tools' or 'resources')

    Yields:
        Full module names that were successfully imported
    """
    # Discover modules in the top level
    for _, module_name, _ in pkgutil.iter_modules([str(base_dir)]):
        # Skip private modules and __init__
        if module_name.startswith('_'):
            continue

        try:
            full_module_name = f'.{module_name}'
            importlib.import_module(full_module_name, package_name)
            yield full_module_name
        except Exception as e:
            logger.warning(f"Failed to import module {module_name}: {e}")

    # Discover modules in subdirectories (one level deep)
    for subdir in base_dir.iterdir():
        if not subdir.is_dir() or subdir.name.startswith('_') or subdir.name.startswith('.'):
            continue

        # Check if subdirectory contains Python modules
        for _, module_name, _ in pkgutil.iter_modules([str(subdir)]):
            # Skip private modules and __init__
            if module_name.startswith('_'):
                continue

            try:
                # Import as package.subdirname.modulename
                full_module_name = f'.{subdir.name}.{module_name}'
                importlib.import_module(full_module_name, package_name)
                yield full_module_name
            except Exception as e:
                logger.warning(
                    f"Failed to import module {subdir.name}.{module_name}: {e}")

```

--------------------------------------------------------------------------------
/docs/v5_MIGRATION.md:
--------------------------------------------------------------------------------

```markdown
# MCP for Unity v5 Migration Guide

This guide will help you migrate from the legacy UnityMcpBridge installation to the new MCPForUnity package structure in version 5.

## Overview

Version 5 introduces a new package structure. The package is now installed from the `MCPForUnity` folder instead of the legacy `UnityMcpBridge` folder.

## Migration Steps

### Step 1: Uninstall the Current Package

1. Open the Unity Package Manager (**Window > Package Manager**)
2. Select **Packages: In Project** from the dropdown
3. Find **MCP for Unity** in the list
4. Click the **Remove** button to uninstall the legacy package

![Uninstalling the legacy package](screenshots/v5_01_uninstall.png)

### Step 2: Install from the New Path

1. In the Package Manager, click the **+** button in the top-left corner
2. Select **Add package from git URL...**
3. Enter the following URL: `https://github.com/CoplayDev/unity-mcp.git?path=/MCPForUnity`
4. Click **Add** to install the package

![Installing from the new MCPForUnity path](screenshots/v5_02_install.png)

### Step 3: Rebuild MCP Server

After installing the new package, you need to rebuild the MCP server:

1. In Unity, go to **Window > MCP for Unity > Open MCP Window**
![Opening the MCP window](screenshots/v5_03_open_mcp_window.png)
2. Click the **Rebuild MCP Server** button
![Rebuilding the MCP server](screenshots/v5_04_rebuild_mcp_server.png)
3. You should see a success message confirming the rebuild
![Rebuild success](screenshots/v5_05_rebuild_success.png)

## Verification

After completing these steps, verify the migration was successful:

- Check that the package appears in the Package Manager as **MCP for Unity**
- Confirm the package location shows the new `MCPForUnity` path
- Test basic MCP functionality to ensure everything works correctly

## Troubleshooting

- Check the Unity Console for specific error messages
- Ensure Python dependencies are properly installed
- Try pressing the rebuild button again
- Try restarting Unity and repeating the installation steps

```

--------------------------------------------------------------------------------
/MCPForUnity/Editor/Tools/ExecuteMenuItem.cs:
--------------------------------------------------------------------------------

```csharp
using System;
using System.Collections.Generic;
using MCPForUnity.Editor.Helpers;
using Newtonsoft.Json.Linq;
using UnityEditor;

namespace MCPForUnity.Editor.Tools
{
    [McpForUnityTool("execute_menu_item")]
    public static class ExecuteMenuItem
    {
        // Basic blacklist to prevent execution of disruptive menu items.
        private static readonly HashSet<string> _menuPathBlacklist = new HashSet<string>(
            StringComparer.OrdinalIgnoreCase)
        {
            "File/Quit",
        };

        public static object HandleCommand(JObject @params)
        {
            McpLog.Info("[ExecuteMenuItem] Handling menu item command");
            string menuPath = @params["menu_path"]?.ToString() ?? @params["menuPath"]?.ToString();
            if (string.IsNullOrWhiteSpace(menuPath))
            {
                return Response.Error("Required parameter 'menu_path' or 'menuPath' is missing or empty.");
            }

            if (_menuPathBlacklist.Contains(menuPath))
            {
                return Response.Error($"Execution of menu item '{menuPath}' is blocked for safety reasons.");
            }

            try
            {
                bool executed = EditorApplication.ExecuteMenuItem(menuPath);
                if (!executed)
                {
                    McpLog.Error($"[MenuItemExecutor] Failed to execute menu item '{menuPath}'. It might be invalid, disabled, or context-dependent.");
                    return Response.Error($"Failed to execute menu item '{menuPath}'. It might be invalid, disabled, or context-dependent.");
                }
                return Response.Success($"Attempted to execute menu item: '{menuPath}'. Check Unity logs for confirmation or errors.");
            }
            catch (Exception e)
            {
                McpLog.Error($"[MenuItemExecutor] Failed to setup execution for '{menuPath}': {e}");
                return Response.Error($"Error setting up execution for menu item '{menuPath}': {e.Message}");
            }
        }
    }
}

```

--------------------------------------------------------------------------------
/MCPForUnity/Editor/Services/IPackageUpdateService.cs:
--------------------------------------------------------------------------------

```csharp
namespace MCPForUnity.Editor.Services
{
    /// <summary>
    /// Service for checking package updates and version information
    /// </summary>
    public interface IPackageUpdateService
    {
        /// <summary>
        /// Checks if a newer version of the package is available
        /// </summary>
        /// <param name="currentVersion">The current package version</param>
        /// <returns>Update check result containing availability and latest version info</returns>
        UpdateCheckResult CheckForUpdate(string currentVersion);
        
        /// <summary>
        /// Compares two version strings to determine if the first is newer than the second
        /// </summary>
        /// <param name="version1">First version string</param>
        /// <param name="version2">Second version string</param>
        /// <returns>True if version1 is newer than version2</returns>
        bool IsNewerVersion(string version1, string version2);
        
        /// <summary>
        /// Determines if the package was installed via Git or Asset Store
        /// </summary>
        /// <returns>True if installed via Git, false if Asset Store or unknown</returns>
        bool IsGitInstallation();
        
        /// <summary>
        /// Clears the cached update check data, forcing a fresh check on next request
        /// </summary>
        void ClearCache();
    }
    
    /// <summary>
    /// Result of an update check operation
    /// </summary>
    public class UpdateCheckResult
    {
        /// <summary>
        /// Whether an update is available
        /// </summary>
        public bool UpdateAvailable { get; set; }
        
        /// <summary>
        /// The latest version available (null if check failed or no update)
        /// </summary>
        public string LatestVersion { get; set; }
        
        /// <summary>
        /// Whether the check was successful (false if network error, etc.)
        /// </summary>
        public bool CheckSucceeded { get; set; }
        
        /// <summary>
        /// Optional message about the check result
        /// </summary>
        public string Message { get; set; }
    }
}

```

--------------------------------------------------------------------------------
/UnityMcpBridge/Editor/Tools/MenuItems/MenuItemExecutor.cs:
--------------------------------------------------------------------------------

```csharp
using System;
using System.Collections.Generic;
using Newtonsoft.Json.Linq;
using UnityEditor;
using MCPForUnity.Editor.Helpers;

namespace MCPForUnity.Editor.Tools.MenuItems
{
    /// <summary>
    /// Executes Unity Editor menu items by path with safety checks.
    /// </summary>
    public static class MenuItemExecutor
    {
        // Basic blacklist to prevent execution of disruptive menu items.
        private static readonly HashSet<string> _menuPathBlacklist = new HashSet<string>(
            StringComparer.OrdinalIgnoreCase)
        {
            "File/Quit",
        };

        /// <summary>
        /// Execute a specific menu item. Expects 'menu_path' or 'menuPath' in params.
        /// </summary>
        public static object Execute(JObject @params)
        {
            string menuPath = @params["menu_path"]?.ToString() ?? @params["menuPath"]?.ToString();
            if (string.IsNullOrWhiteSpace(menuPath))
            {
                return Response.Error("Required parameter 'menu_path' or 'menuPath' is missing or empty.");
            }

            if (_menuPathBlacklist.Contains(menuPath))
            {
                return Response.Error($"Execution of menu item '{menuPath}' is blocked for safety reasons.");
            }

            try
            {
                bool executed = EditorApplication.ExecuteMenuItem(menuPath);
                if (!executed)
                {
                    McpLog.Error($"[MenuItemExecutor] Failed to execute menu item '{menuPath}'. It might be invalid, disabled, or context-dependent.");
                    return Response.Error($"Failed to execute menu item '{menuPath}'. It might be invalid, disabled, or context-dependent.");
                }
                return Response.Success($"Attempted to execute menu item: '{menuPath}'. Check Unity logs for confirmation or errors.");
            }
            catch (Exception e)
            {
                McpLog.Error($"[MenuItemExecutor] Failed to setup execution for '{menuPath}': {e}");
                return Response.Error($"Error setting up execution for menu item '{menuPath}': {e.Message}");
            }
        }
    }
}

```

--------------------------------------------------------------------------------
/TestProjects/UnityMCPTests/Assets/Tests/EditMode/Services/ToolSyncServiceTests.cs:
--------------------------------------------------------------------------------

```csharp
using System.IO;
using NUnit.Framework;
using UnityEngine;
using MCPForUnity.Editor.Data;
using MCPForUnity.Editor.Services;

namespace MCPForUnityTests.Editor.Services
{
    public class ToolSyncServiceTests
    {
        private ToolSyncService _service;
        private string _testToolsDir;

        [SetUp]
        public void SetUp()
        {
            _service = new ToolSyncService();
            _testToolsDir = Path.Combine(Path.GetTempPath(), "UnityMCPTests", "tools");

            // Clean up any existing test directory
            if (Directory.Exists(_testToolsDir))
            {
                Directory.Delete(_testToolsDir, true);
            }
        }

        [TearDown]
        public void TearDown()
        {
            // Clean up test directory
            if (Directory.Exists(_testToolsDir))
            {
                try
                {
                    Directory.Delete(_testToolsDir, true);
                }
                catch
                {
                    // Ignore cleanup errors
                }
            }
        }

        [Test]
        public void SyncProjectTools_CreatesDestinationDirectory()
        {
            _service.SyncProjectTools(_testToolsDir);

            Assert.IsTrue(Directory.Exists(_testToolsDir), "Should create destination directory");
        }

        [Test]
        public void SyncProjectTools_ReturnsSuccess_WhenNoPythonToolsAssets()
        {
            var result = _service.SyncProjectTools(_testToolsDir);

            Assert.IsNotNull(result, "Should return a result");
            Assert.AreEqual(0, result.CopiedCount, "Should not copy any files");
            Assert.AreEqual(0, result.ErrorCount, "Should not have errors");
        }

        [Test]
        public void SyncProjectTools_ReportsCorrectCounts()
        {
            var result = _service.SyncProjectTools(_testToolsDir);

            Assert.IsTrue(result.CopiedCount >= 0, "Copied count should be non-negative");
            Assert.IsTrue(result.SkippedCount >= 0, "Skipped count should be non-negative");
            Assert.IsTrue(result.ErrorCount >= 0, "Error count should be non-negative");
        }
    }
}

```

--------------------------------------------------------------------------------
/tests/test_get_sha.py:
--------------------------------------------------------------------------------

```python
import sys
import pathlib
import importlib.util
import types


ROOT = pathlib.Path(__file__).resolve().parents[1]
SRC = ROOT / "MCPForUnity" / "UnityMcpServer~" / "src"
sys.path.insert(0, str(SRC))

# stub mcp.server.fastmcp to satisfy imports without full dependency
mcp_pkg = types.ModuleType("mcp")
server_pkg = types.ModuleType("mcp.server")
fastmcp_pkg = types.ModuleType("mcp.server.fastmcp")


class _Dummy:
    pass


fastmcp_pkg.FastMCP = _Dummy
fastmcp_pkg.Context = _Dummy
server_pkg.fastmcp = fastmcp_pkg
mcp_pkg.server = server_pkg
sys.modules.setdefault("mcp", mcp_pkg)
sys.modules.setdefault("mcp.server", server_pkg)
sys.modules.setdefault("mcp.server.fastmcp", fastmcp_pkg)


def _load_module(path: pathlib.Path, name: str):
    spec = importlib.util.spec_from_file_location(name, path)
    mod = importlib.util.module_from_spec(spec)
    spec.loader.exec_module(mod)
    return mod


manage_script = _load_module(
    SRC / "tools" / "manage_script.py", "manage_script_mod")


class DummyMCP:
    def __init__(self):
        self.tools = {}

    def tool(self, *args, **kwargs):
        def deco(fn):
            self.tools[fn.__name__] = fn
            return fn
        return deco


def setup_tools():
    mcp = DummyMCP()
    manage_script.register_manage_script_tools(mcp)
    return mcp.tools


def test_get_sha_param_shape_and_routing(monkeypatch):
    tools = setup_tools()
    get_sha = tools["get_sha"]

    captured = {}

    def fake_send(cmd, params):
        captured["cmd"] = cmd
        captured["params"] = params
        return {"success": True, "data": {"sha256": "abc", "lengthBytes": 1, "lastModifiedUtc": "2020-01-01T00:00:00Z", "uri": "unity://path/Assets/Scripts/A.cs", "path": "Assets/Scripts/A.cs"}}

    monkeypatch.setattr(manage_script, "send_command_with_retry", fake_send)

    resp = get_sha(None, uri="unity://path/Assets/Scripts/A.cs")
    assert captured["cmd"] == "manage_script"
    assert captured["params"]["action"] == "get_sha"
    assert captured["params"]["name"] == "A"
    assert captured["params"]["path"].endswith("Assets/Scripts")
    assert resp["success"] is True
    assert resp["data"] == {"sha256": "abc", "lengthBytes": 1}

```

--------------------------------------------------------------------------------
/MCPForUnity/Editor/Resources/MenuItems/GetMenuItems.cs:
--------------------------------------------------------------------------------

```csharp
using System;
using System.Collections.Generic;
using System.Linq;
using MCPForUnity.Editor.Helpers;
using Newtonsoft.Json.Linq;
using UnityEditor;

namespace MCPForUnity.Editor.Resources.MenuItems
{
    /// <summary>
    /// Provides a simple read-only resource that returns Unity menu items.
    /// </summary>
    [McpForUnityResource("get_menu_items")]
    public static class GetMenuItems
    {
        private static List<string> _cached;

        [InitializeOnLoadMethod]
        private static void BuildCache() => Refresh();

        public static object HandleCommand(JObject @params)
        {
            bool forceRefresh = @params?["refresh"]?.ToObject<bool>() ?? false;
            string search = @params?["search"]?.ToString();

            var items = GetMenuItemsInternal(forceRefresh);

            if (!string.IsNullOrEmpty(search))
            {
                items = items
                    .Where(item => item.IndexOf(search, StringComparison.OrdinalIgnoreCase) >= 0)
                    .ToList();
            }

            string message = $"Retrieved {items.Count} menu items";
            return Response.Success(message, items);
        }

        internal static List<string> GetMenuItemsInternal(bool forceRefresh)
        {
            if (forceRefresh || _cached == null)
            {
                Refresh();
            }

            return (_cached ?? new List<string>()).ToList();
        }

        private static void Refresh()
        {
            try
            {
                var methods = TypeCache.GetMethodsWithAttribute<MenuItem>();
                _cached = methods
                    .SelectMany(m => m
                        .GetCustomAttributes(typeof(MenuItem), false)
                        .OfType<MenuItem>()
                        .Select(attr => attr.menuItem))
                    .Where(s => !string.IsNullOrEmpty(s))
                    .Distinct(StringComparer.Ordinal)
                    .OrderBy(s => s, StringComparer.Ordinal)
                    .ToList();
            }
            catch (Exception ex)
            {
                McpLog.Error($"[GetMenuItems] Failed to scan menu items: {ex}");
                _cached ??= new List<string>();
            }
        }
    }
}

```

--------------------------------------------------------------------------------
/prune_tool_results.py:
--------------------------------------------------------------------------------

```python
#!/usr/bin/env python3
import sys, json

def summarize(txt):
    try:
        obj = json.loads(txt)
    except Exception:
        return f"tool_result: {len(txt)} bytes"
    data = obj.get("data", {}) or {}
    msg  = obj.get("message") or obj.get("status") or ""
    # Common tool shapes
    if "sha256" in str(data):
        ln  = data.get("lengthBytes") or data.get("length") or ""
        return f"len={ln}".strip()
    if "diagnostics" in data:
        diags = data["diagnostics"] or []
        w = sum(d.get("severity","" ).lower()=="warning" for d in diags)
        e = sum(d.get("severity","" ).lower() in ("error","fatal") for d in diags)
        ok = "OK" if not e else "FAIL"
        return f"validate: {ok} (warnings={w}, errors={e})"
    if "matches" in data:
        m = data["matches"] or []
        if m:
            first = m[0]
            return f"find_in_file: {len(m)} match(es) first@{first.get('line',0)}:{first.get('col',0)}"
        return "find_in_file: 0 matches"
    if "lines" in data:  # console
        lines = data["lines"] or []
        lvls = {"info":0,"warning":0,"error":0}
        for L in lines:
            lvls[L.get("level","" ).lower()] = lvls.get(L.get("level","" ).lower(),0)+1
        return f"console: {len(lines)} lines (info={lvls.get('info',0)},warn={lvls.get('warning',0)},err={lvls.get('error',0)})"
    # Fallback: short status
    return (msg or "tool_result")[:80]

def prune_message(msg):
    if "content" not in msg: return msg
    newc=[]
    for c in msg["content"]:
        if c.get("type")=="tool_result" and c.get("content"):
            out=[]
            for chunk in c["content"]:
                if chunk.get("type")=="text":
                    out.append({"type":"text","text":summarize(chunk.get("text","" ))})
            newc.append({"type":"tool_result","tool_use_id":c.get("tool_use_id"),"content":out})
        else:
            newc.append(c)
    msg["content"]=newc
    return msg

def main():
    convo=json.load(sys.stdin)
    if isinstance(convo, dict) and "messages" in convo:
        convo["messages"]=[prune_message(m) for m in convo["messages"]]
    elif isinstance(convo, list):
        convo=[prune_message(m) for m in convo]
    json.dump(convo, sys.stdout, ensure_ascii=False)
main()

```

--------------------------------------------------------------------------------
/MCPForUnity/Editor/Helpers/Response.cs:
--------------------------------------------------------------------------------

```csharp
using System;
using System.Collections.Generic;

namespace MCPForUnity.Editor.Helpers
{
    /// <summary>
    /// Provides static methods for creating standardized success and error response objects.
    /// Ensures consistent JSON structure for communication back to the Python server.
    /// </summary>
    public static class Response
    {
        /// <summary>
        /// Creates a standardized success response object.
        /// </summary>
        /// <param name="message">A message describing the successful operation.</param>
        /// <param name="data">Optional additional data to include in the response.</param>
        /// <returns>An object representing the success response.</returns>
        public static object Success(string message, object data = null)
        {
            if (data != null)
            {
                return new
                {
                    success = true,
                    message = message,
                    data = data,
                };
            }
            else
            {
                return new { success = true, message = message };
            }
        }

        /// <summary>
        /// Creates a standardized error response object.
        /// </summary>
        /// <param name="errorCodeOrMessage">A message describing the error.</param>
        /// <param name="data">Optional additional data (e.g., error details) to include.</param>
        /// <returns>An object representing the error response.</returns>
        public static object Error(string errorCodeOrMessage, object data = null)
        {
            if (data != null)
            {
                // Note: The key is "error" for error messages, not "message"
                return new
                {
                    success = false,
                    // Preserve original behavior while adding a machine-parsable code field.
                    // If callers pass a code string, it will be echoed in both code and error.
                    code = errorCodeOrMessage,
                    error = errorCodeOrMessage,
                    data = data,
                };
            }
            else
            {
                return new { success = false, code = errorCodeOrMessage, error = errorCodeOrMessage };
            }
        }
    }
}

```

--------------------------------------------------------------------------------
/UnityMcpBridge/Editor/Helpers/Response.cs:
--------------------------------------------------------------------------------

```csharp
using System;
using System.Collections.Generic;

namespace MCPForUnity.Editor.Helpers
{
    /// <summary>
    /// Provides static methods for creating standardized success and error response objects.
    /// Ensures consistent JSON structure for communication back to the Python server.
    /// </summary>
    public static class Response
    {
        /// <summary>
        /// Creates a standardized success response object.
        /// </summary>
        /// <param name="message">A message describing the successful operation.</param>
        /// <param name="data">Optional additional data to include in the response.</param>
        /// <returns>An object representing the success response.</returns>
        public static object Success(string message, object data = null)
        {
            if (data != null)
            {
                return new
                {
                    success = true,
                    message = message,
                    data = data,
                };
            }
            else
            {
                return new { success = true, message = message };
            }
        }

        /// <summary>
        /// Creates a standardized error response object.
        /// </summary>
        /// <param name="errorCodeOrMessage">A message describing the error.</param>
        /// <param name="data">Optional additional data (e.g., error details) to include.</param>
        /// <returns>An object representing the error response.</returns>
        public static object Error(string errorCodeOrMessage, object data = null)
        {
            if (data != null)
            {
                // Note: The key is "error" for error messages, not "message"
                return new
                {
                    success = false,
                    // Preserve original behavior while adding a machine-parsable code field.
                    // If callers pass a code string, it will be echoed in both code and error.
                    code = errorCodeOrMessage,
                    error = errorCodeOrMessage,
                    data = data,
                };
            }
            else
            {
                return new { success = false, code = errorCodeOrMessage, error = errorCodeOrMessage };
            }
        }
    }
}

```

--------------------------------------------------------------------------------
/MCPForUnity/UnityMcpServer~/src/tools/manage_scene.py:
--------------------------------------------------------------------------------

```python
from typing import Annotated, Literal, Any

from mcp.server.fastmcp import Context
from registry import mcp_for_unity_tool
from unity_connection import send_command_with_retry


@mcp_for_unity_tool(description="Manage Unity scenes")
def manage_scene(
    ctx: Context,
    action: Annotated[Literal["create", "load", "save", "get_hierarchy", "get_active", "get_build_settings"], "Perform CRUD operations on Unity scenes."],
    name: Annotated[str,
                    "Scene name. Not required get_active/get_build_settings"] | None = None,
    path: Annotated[str,
                    "Asset path for scene operations (default: 'Assets/')"] | None = None,
    build_index: Annotated[int,
                           "Build index for load/build settings actions"] | None = None,
) -> dict[str, Any]:
    ctx.info(f"Processing manage_scene: {action}")
    try:
        # Coerce numeric inputs defensively
        def _coerce_int(value, default=None):
            if value is None:
                return default
            try:
                if isinstance(value, bool):
                    return default
                if isinstance(value, int):
                    return int(value)
                s = str(value).strip()
                if s.lower() in ("", "none", "null"):
                    return default
                return int(float(s))
            except Exception:
                return default

        coerced_build_index = _coerce_int(build_index, default=None)

        params = {"action": action}
        if name:
            params["name"] = name
        if path:
            params["path"] = path
        if coerced_build_index is not None:
            params["buildIndex"] = coerced_build_index

        # Use centralized retry helper
        response = send_command_with_retry("manage_scene", params)

        # Preserve structured failure data; unwrap success into a friendlier shape
        if isinstance(response, dict) and response.get("success"):
            return {"success": True, "message": response.get("message", "Scene operation successful."), "data": response.get("data")}
        return response if isinstance(response, dict) else {"success": False, "message": str(response)}

    except Exception as e:
        return {"success": False, "message": f"Python error managing scene: {str(e)}"}

```

--------------------------------------------------------------------------------
/UnityMcpBridge/UnityMcpServer~/src/tools/manage_scene.py:
--------------------------------------------------------------------------------

```python
from typing import Annotated, Literal, Any

from mcp.server.fastmcp import Context
from registry import mcp_for_unity_tool
from unity_connection import send_command_with_retry


@mcp_for_unity_tool(description="Manage Unity scenes")
def manage_scene(
    ctx: Context,
    action: Annotated[Literal["create", "load", "save", "get_hierarchy", "get_active", "get_build_settings"], "Perform CRUD operations on Unity scenes."],
    name: Annotated[str,
                    "Scene name. Not required get_active/get_build_settings"] | None = None,
    path: Annotated[str,
                    "Asset path for scene operations (default: 'Assets/')"] | None = None,
    build_index: Annotated[int,
                           "Build index for load/build settings actions"] | None = None,
) -> dict[str, Any]:
    ctx.info(f"Processing manage_scene: {action}")
    try:
        # Coerce numeric inputs defensively
        def _coerce_int(value, default=None):
            if value is None:
                return default
            try:
                if isinstance(value, bool):
                    return default
                if isinstance(value, int):
                    return int(value)
                s = str(value).strip()
                if s.lower() in ("", "none", "null"):
                    return default
                return int(float(s))
            except Exception:
                return default

        coerced_build_index = _coerce_int(build_index, default=None)

        params = {"action": action}
        if name:
            params["name"] = name
        if path:
            params["path"] = path
        if coerced_build_index is not None:
            params["buildIndex"] = coerced_build_index

        # Use centralized retry helper
        response = send_command_with_retry("manage_scene", params)

        # Preserve structured failure data; unwrap success into a friendlier shape
        if isinstance(response, dict) and response.get("success"):
            return {"success": True, "message": response.get("message", "Scene operation successful."), "data": response.get("data")}
        return response if isinstance(response, dict) else {"success": False, "message": str(response)}

    except Exception as e:
        return {"success": False, "message": f"Python error managing scene: {str(e)}"}

```

--------------------------------------------------------------------------------
/tests/test_telemetry_endpoint_validation.py:
--------------------------------------------------------------------------------

```python
import os
import importlib


def test_endpoint_rejects_non_http(tmp_path, monkeypatch):
    # Point data dir to temp to avoid touching real files
    monkeypatch.setenv("XDG_DATA_HOME", str(tmp_path))
    monkeypatch.setenv("UNITY_MCP_TELEMETRY_ENDPOINT", "file:///etc/passwd")

    telemetry = importlib.import_module(
        "MCPForUnity.UnityMcpServer~.src.telemetry")
    importlib.reload(telemetry)

    tc = telemetry.TelemetryCollector()
    # Should have fallen back to default endpoint
    assert tc.config.endpoint == tc.config.default_endpoint


def test_config_preferred_then_env_override(tmp_path, monkeypatch):
    # Simulate config telemetry endpoint
    monkeypatch.setenv("XDG_DATA_HOME", str(tmp_path))
    monkeypatch.delenv("UNITY_MCP_TELEMETRY_ENDPOINT", raising=False)

    # Patch config.telemetry_endpoint via import mocking
    import importlib
    cfg_mod = importlib.import_module(
        "MCPForUnity.UnityMcpServer~.src.config")
    old_endpoint = cfg_mod.config.telemetry_endpoint
    cfg_mod.config.telemetry_endpoint = "https://example.com/telemetry"
    try:
        telemetry = importlib.import_module(
            "MCPForUnity.UnityMcpServer~.src.telemetry")
        importlib.reload(telemetry)
        tc = telemetry.TelemetryCollector()
        assert tc.config.endpoint == "https://example.com/telemetry"

        # Env should override config
        monkeypatch.setenv("UNITY_MCP_TELEMETRY_ENDPOINT",
                           "https://override.example/ep")
        importlib.reload(telemetry)
        tc2 = telemetry.TelemetryCollector()
        assert tc2.config.endpoint == "https://override.example/ep"
    finally:
        cfg_mod.config.telemetry_endpoint = old_endpoint


def test_uuid_preserved_on_malformed_milestones(tmp_path, monkeypatch):
    monkeypatch.setenv("XDG_DATA_HOME", str(tmp_path))

    telemetry = importlib.import_module(
        "MCPForUnity.UnityMcpServer~.src.telemetry")
    importlib.reload(telemetry)

    tc1 = telemetry.TelemetryCollector()
    first_uuid = tc1._customer_uuid

    # Write malformed milestones
    tc1.config.milestones_file.write_text("{not-json}", encoding="utf-8")

    # Reload collector; UUID should remain same despite bad milestones
    importlib.reload(telemetry)
    tc2 = telemetry.TelemetryCollector()
    assert tc2._customer_uuid == first_uuid

```

--------------------------------------------------------------------------------
/MCPForUnity/Editor/Tools/RunTests.cs:
--------------------------------------------------------------------------------

```csharp
using System;
using System.Threading.Tasks;
using MCPForUnity.Editor.Helpers;
using MCPForUnity.Editor.Resources.Tests;
using MCPForUnity.Editor.Services;
using Newtonsoft.Json.Linq;

namespace MCPForUnity.Editor.Tools
{
    /// <summary>
    /// Executes Unity tests for a specified mode and returns detailed results.
    /// </summary>
    [McpForUnityTool("run_tests")]
    public static class RunTests
    {
        private const int DefaultTimeoutSeconds = 600; // 10 minutes

        public static async Task<object> HandleCommand(JObject @params)
        {
            string modeStr = @params?["mode"]?.ToString();
            if (string.IsNullOrWhiteSpace(modeStr))
            {
                modeStr = "edit";
            }

            if (!ModeParser.TryParse(modeStr, out var parsedMode, out var parseError))
            {
                return Response.Error(parseError);
            }

            int timeoutSeconds = DefaultTimeoutSeconds;
            try
            {
                var timeoutToken = @params?["timeoutSeconds"];
                if (timeoutToken != null && int.TryParse(timeoutToken.ToString(), out var parsedTimeout) && parsedTimeout > 0)
                {
                    timeoutSeconds = parsedTimeout;
                }
            }
            catch
            {
                // Preserve default timeout if parsing fails
            }

            var testService = MCPServiceLocator.Tests;
            Task<TestRunResult> runTask;
            try
            {
                runTask = testService.RunTestsAsync(parsedMode.Value);
            }
            catch (Exception ex)
            {
                return Response.Error($"Failed to start test run: {ex.Message}");
            }

            var timeoutTask = Task.Delay(TimeSpan.FromSeconds(timeoutSeconds));
            var completed = await Task.WhenAny(runTask, timeoutTask).ConfigureAwait(true);

            if (completed != runTask)
            {
                return Response.Error($"Test run timed out after {timeoutSeconds} seconds");
            }

            var result = await runTask.ConfigureAwait(true);

            string message =
                $"{parsedMode.Value} tests completed: {result.Passed}/{result.Total} passed, {result.Failed} failed, {result.Skipped} skipped";

            var data = result.ToSerializable(parsedMode.Value.ToString());
            return Response.Success(message, data);
        }
    }
}

```

--------------------------------------------------------------------------------
/MCPForUnity/UnityMcpServer~/src/tools/manage_prefabs.py:
--------------------------------------------------------------------------------

```python
from typing import Annotated, Any, Literal

from mcp.server.fastmcp import Context
from registry import mcp_for_unity_tool
from unity_connection import send_command_with_retry


@mcp_for_unity_tool(
    description="Bridge for prefab management commands (stage control and creation)."
)
def manage_prefabs(
    ctx: Context,
    action: Annotated[Literal[
        "open_stage",
        "close_stage",
        "save_open_stage",
        "create_from_gameobject",
    ], "Manage prefabs (stage control and creation)."],
    prefab_path: Annotated[str,
                           "Prefab asset path relative to Assets e.g. Assets/Prefabs/favorite.prefab"] | None = None,
    mode: Annotated[str,
                    "Optional prefab stage mode (only 'InIsolation' is currently supported)"] | None = None,
    save_before_close: Annotated[bool,
                                 "When true, `close_stage` will save the prefab before exiting the stage."] | None = None,
    target: Annotated[str,
                      "Scene GameObject name required for create_from_gameobject"] | None = None,
    allow_overwrite: Annotated[bool,
                               "Allow replacing an existing prefab at the same path"] | None = None,
    search_inactive: Annotated[bool,
                               "Include inactive objects when resolving the target name"] | None = None,
) -> dict[str, Any]:
    ctx.info(f"Processing manage_prefabs: {action}")
    try:
        params: dict[str, Any] = {"action": action}

        if prefab_path:
            params["prefabPath"] = prefab_path
        if mode:
            params["mode"] = mode
        if save_before_close is not None:
            params["saveBeforeClose"] = bool(save_before_close)
        if target:
            params["target"] = target
        if allow_overwrite is not None:
            params["allowOverwrite"] = bool(allow_overwrite)
        if search_inactive is not None:
            params["searchInactive"] = bool(search_inactive)
        response = send_command_with_retry("manage_prefabs", params)

        if isinstance(response, dict) and response.get("success"):
            return {
                "success": True,
                "message": response.get("message", "Prefab operation successful."),
                "data": response.get("data"),
            }
        return response if isinstance(response, dict) else {"success": False, "message": str(response)}
    except Exception as exc:
        return {"success": False, "message": f"Python error managing prefabs: {exc}"}

```

--------------------------------------------------------------------------------
/UnityMcpBridge/UnityMcpServer~/src/tools/manage_prefabs.py:
--------------------------------------------------------------------------------

```python
from typing import Annotated, Any, Literal

from mcp.server.fastmcp import Context
from registry import mcp_for_unity_tool
from unity_connection import send_command_with_retry


@mcp_for_unity_tool(
    description="Bridge for prefab management commands (stage control and creation)."
)
def manage_prefabs(
    ctx: Context,
    action: Annotated[Literal[
        "open_stage",
        "close_stage",
        "save_open_stage",
        "create_from_gameobject",
    ], "Manage prefabs (stage control and creation)."],
    prefab_path: Annotated[str,
                           "Prefab asset path relative to Assets e.g. Assets/Prefabs/favorite.prefab"] | None = None,
    mode: Annotated[str,
                    "Optional prefab stage mode (only 'InIsolation' is currently supported)"] | None = None,
    save_before_close: Annotated[bool,
                                 "When true, `close_stage` will save the prefab before exiting the stage."] | None = None,
    target: Annotated[str,
                      "Scene GameObject name required for create_from_gameobject"] | None = None,
    allow_overwrite: Annotated[bool,
                               "Allow replacing an existing prefab at the same path"] | None = None,
    search_inactive: Annotated[bool,
                               "Include inactive objects when resolving the target name"] | None = None,
) -> dict[str, Any]:
    ctx.info(f"Processing manage_prefabs: {action}")
    try:
        params: dict[str, Any] = {"action": action}

        if prefab_path:
            params["prefabPath"] = prefab_path
        if mode:
            params["mode"] = mode
        if save_before_close is not None:
            params["saveBeforeClose"] = bool(save_before_close)
        if target:
            params["target"] = target
        if allow_overwrite is not None:
            params["allowOverwrite"] = bool(allow_overwrite)
        if search_inactive is not None:
            params["searchInactive"] = bool(search_inactive)
        response = send_command_with_retry("manage_prefabs", params)

        if isinstance(response, dict) and response.get("success"):
            return {
                "success": True,
                "message": response.get("message", "Prefab operation successful."),
                "data": response.get("data"),
            }
        return response if isinstance(response, dict) else {"success": False, "message": str(response)}
    except Exception as exc:
        return {"success": False, "message": f"Python error managing prefabs: {exc}"}

```

--------------------------------------------------------------------------------
/MCPForUnity/UnityMcpServer~/src/tools/manage_shader.py:
--------------------------------------------------------------------------------

```python
import base64
from typing import Annotated, Any, Literal

from mcp.server.fastmcp import Context
from registry import mcp_for_unity_tool
from unity_connection import send_command_with_retry


@mcp_for_unity_tool(
    description="Manages shader scripts in Unity (create, read, update, delete)."
)
def manage_shader(
    ctx: Context,
    action: Annotated[Literal['create', 'read', 'update', 'delete'], "Perform CRUD operations on shader scripts."],
    name: Annotated[str, "Shader name (no .cs extension)"],
    path: Annotated[str, "Asset path (default: \"Assets/\")"],
    contents: Annotated[str,
                        "Shader code for 'create'/'update'"] | None = None,
) -> dict[str, Any]:
    ctx.info(f"Processing manage_shader: {action}")
    try:
        # Prepare parameters for Unity
        params = {
            "action": action,
            "name": name,
            "path": path,
        }

        # Base64 encode the contents if they exist to avoid JSON escaping issues
        if contents is not None:
            if action in ['create', 'update']:
                # Encode content for safer transmission
                params["encodedContents"] = base64.b64encode(
                    contents.encode('utf-8')).decode('utf-8')
                params["contentsEncoded"] = True
            else:
                params["contents"] = contents

        # Remove None values so they don't get sent as null
        params = {k: v for k, v in params.items() if v is not None}

        # Send command via centralized retry helper
        response = send_command_with_retry("manage_shader", params)

        # Process response from Unity
        if isinstance(response, dict) and response.get("success"):
            # If the response contains base64 encoded content, decode it
            if response.get("data", {}).get("contentsEncoded"):
                decoded_contents = base64.b64decode(
                    response["data"]["encodedContents"]).decode('utf-8')
                response["data"]["contents"] = decoded_contents
                del response["data"]["encodedContents"]
                del response["data"]["contentsEncoded"]

            return {"success": True, "message": response.get("message", "Operation successful."), "data": response.get("data")}
        return response if isinstance(response, dict) else {"success": False, "message": str(response)}

    except Exception as e:
        # Handle Python-side errors (e.g., connection issues)
        return {"success": False, "message": f"Python error managing shader: {str(e)}"}

```

--------------------------------------------------------------------------------
/UnityMcpBridge/UnityMcpServer~/src/tools/manage_shader.py:
--------------------------------------------------------------------------------

```python
import base64
from typing import Annotated, Any, Literal

from mcp.server.fastmcp import Context
from registry import mcp_for_unity_tool
from unity_connection import send_command_with_retry


@mcp_for_unity_tool(
    description="Manages shader scripts in Unity (create, read, update, delete)."
)
def manage_shader(
    ctx: Context,
    action: Annotated[Literal['create', 'read', 'update', 'delete'], "Perform CRUD operations on shader scripts."],
    name: Annotated[str, "Shader name (no .cs extension)"],
    path: Annotated[str, "Asset path (default: \"Assets/\")"],
    contents: Annotated[str,
                        "Shader code for 'create'/'update'"] | None = None,
) -> dict[str, Any]:
    ctx.info(f"Processing manage_shader: {action}")
    try:
        # Prepare parameters for Unity
        params = {
            "action": action,
            "name": name,
            "path": path,
        }

        # Base64 encode the contents if they exist to avoid JSON escaping issues
        if contents is not None:
            if action in ['create', 'update']:
                # Encode content for safer transmission
                params["encodedContents"] = base64.b64encode(
                    contents.encode('utf-8')).decode('utf-8')
                params["contentsEncoded"] = True
            else:
                params["contents"] = contents

        # Remove None values so they don't get sent as null
        params = {k: v for k, v in params.items() if v is not None}

        # Send command via centralized retry helper
        response = send_command_with_retry("manage_shader", params)

        # Process response from Unity
        if isinstance(response, dict) and response.get("success"):
            # If the response contains base64 encoded content, decode it
            if response.get("data", {}).get("contentsEncoded"):
                decoded_contents = base64.b64decode(
                    response["data"]["encodedContents"]).decode('utf-8')
                response["data"]["contents"] = decoded_contents
                del response["data"]["encodedContents"]
                del response["data"]["contentsEncoded"]

            return {"success": True, "message": response.get("message", "Operation successful."), "data": response.get("data")}
        return response if isinstance(response, dict) else {"success": False, "message": str(response)}

    except Exception as e:
        # Handle Python-side errors (e.g., connection issues)
        return {"success": False, "message": f"Python error managing shader: {str(e)}"}

```

--------------------------------------------------------------------------------
/tests/test_logging_stdout.py:
--------------------------------------------------------------------------------

```python
import ast
from pathlib import Path

import pytest


# locate server src dynamically to avoid hardcoded layout assumptions
ROOT = Path(__file__).resolve().parents[1]
candidates = [
    ROOT / "MCPForUnity" / "UnityMcpServer~" / "src",
    ROOT / "UnityMcpServer~" / "src",
]
SRC = next((p for p in candidates if p.exists()), None)
if SRC is None:
    searched = "\n".join(str(p) for p in candidates)
    pytest.skip(
        "MCP for Unity server source not found. Tried:\n" + searched,
        allow_module_level=True,
    )


@pytest.mark.skip(reason="TODO: ensure server logs only to stderr and rotating file")
def test_no_stdout_output_from_tools():
    pass


def test_no_print_statements_in_codebase():
    """Ensure no stray print/sys.stdout writes remain in server source."""
    offenders = []
    syntax_errors = []
    for py_file in SRC.rglob("*.py"):
        # Skip virtual envs and third-party packages if they exist under SRC
        parts = set(py_file.parts)
        if ".venv" in parts or "site-packages" in parts:
            continue
        try:
            text = py_file.read_text(encoding="utf-8", errors="strict")
        except UnicodeDecodeError:
            # Be tolerant of encoding edge cases in source tree without silently dropping bytes
            text = py_file.read_text(encoding="utf-8", errors="replace")
        try:
            tree = ast.parse(text, filename=str(py_file))
        except SyntaxError:
            syntax_errors.append(py_file.relative_to(SRC))
            continue

        class StdoutVisitor(ast.NodeVisitor):
            def __init__(self):
                self.hit = False

            def visit_Call(self, node: ast.Call):
                # print(...)
                if isinstance(node.func, ast.Name) and node.func.id == "print":
                    self.hit = True
                # sys.stdout.write(...)
                if isinstance(node.func, ast.Attribute) and node.func.attr == "write":
                    val = node.func.value
                    if isinstance(val, ast.Attribute) and val.attr == "stdout":
                        if isinstance(val.value, ast.Name) and val.value.id == "sys":
                            self.hit = True
                self.generic_visit(node)

        v = StdoutVisitor()
        v.visit(tree)
        if v.hit:
            offenders.append(py_file.relative_to(SRC))
    assert not syntax_errors, "syntax errors in: " + \
        ", ".join(str(e) for e in syntax_errors)
    assert not offenders, "stdout writes found in: " + \
        ", ".join(str(o) for o in offenders)

```

--------------------------------------------------------------------------------
/tests/test_telemetry_queue_worker.py:
--------------------------------------------------------------------------------

```python
import sys
import pathlib
import importlib.util
import types
import threading
import time
import queue as q


ROOT = pathlib.Path(__file__).resolve().parents[1]
SRC = ROOT / "MCPForUnity" / "UnityMcpServer~" / "src"
sys.path.insert(0, str(SRC))

# Stub mcp.server.fastmcp to satisfy imports without the full dependency
mcp_pkg = types.ModuleType("mcp")
server_pkg = types.ModuleType("mcp.server")
fastmcp_pkg = types.ModuleType("mcp.server.fastmcp")


class _Dummy:
    pass


fastmcp_pkg.FastMCP = _Dummy
fastmcp_pkg.Context = _Dummy
server_pkg.fastmcp = fastmcp_pkg
mcp_pkg.server = server_pkg
sys.modules.setdefault("mcp", mcp_pkg)
sys.modules.setdefault("mcp.server", server_pkg)
sys.modules.setdefault("mcp.server.fastmcp", fastmcp_pkg)


def _load_module(path: pathlib.Path, name: str):
    spec = importlib.util.spec_from_file_location(name, path)
    mod = importlib.util.module_from_spec(spec)
    spec.loader.exec_module(mod)
    return mod


telemetry = _load_module(SRC / "telemetry.py", "telemetry_mod")


def test_telemetry_queue_backpressure_and_single_worker(monkeypatch, caplog):
    caplog.set_level("DEBUG")

    collector = telemetry.TelemetryCollector()
    # Force-enable telemetry regardless of env settings from conftest
    collector.config.enabled = True

    # Wake existing worker once so it observes the new queue on the next loop
    collector.record(telemetry.RecordType.TOOL_EXECUTION, {"i": -1})
    # Replace queue with tiny one to trigger backpressure quickly
    small_q = q.Queue(maxsize=2)
    collector._queue = small_q
    # Give the worker a moment to switch queues
    time.sleep(0.02)

    # Make sends slow to build backlog and exercise worker
    def slow_send(self, rec):
        time.sleep(0.05)

    collector._send_telemetry = types.MethodType(slow_send, collector)

    # Fire many events quickly; record() should not block even when queue fills
    start = time.perf_counter()
    for i in range(50):
        collector.record(telemetry.RecordType.TOOL_EXECUTION, {"i": i})
    elapsed_ms = (time.perf_counter() - start) * 1000.0

    # Should be fast despite backpressure (non-blocking enqueue or drop)
    assert elapsed_ms < 80.0

    # Allow worker to process some
    time.sleep(0.3)

    # Verify drops were logged (queue full backpressure)
    dropped_logs = [
        m for m in caplog.messages if "Telemetry queue full; dropping" in m]
    assert len(dropped_logs) >= 1

    # Ensure only one worker thread exists and is alive
    assert collector._worker.is_alive()
    worker_threads = [
        t for t in threading.enumerate() if t is collector._worker]
    assert len(worker_threads) == 1

```

--------------------------------------------------------------------------------
/TestProjects/UnityMCPTests/Assets/Tests/EditMode/Windows/ManualConfigJsonBuilderTests.cs:
--------------------------------------------------------------------------------

```csharp
using Newtonsoft.Json.Linq;
using NUnit.Framework;
using MCPForUnity.Editor.Helpers;
using MCPForUnity.Editor.Models;

namespace MCPForUnityTests.Editor.Windows
{
    public class ManualConfigJsonBuilderTests
    {
        [Test]
        public void VSCode_ManualJson_HasServers_NoEnv_NoDisabled()
        {
            var client = new McpClient { name = "VSCode", mcpType = McpTypes.VSCode };
            string json = ConfigJsonBuilder.BuildManualConfigJson("/usr/bin/uv", "/path/to/server", client);

            var root = JObject.Parse(json);
            var unity = (JObject)root.SelectToken("servers.unityMCP");
            Assert.NotNull(unity, "Expected servers.unityMCP node");
            Assert.AreEqual("/usr/bin/uv", (string)unity["command"]);
            CollectionAssert.AreEqual(new[] { "run", "--directory", "/path/to/server", "server.py" }, unity["args"].ToObject<string[]>());
            Assert.AreEqual("stdio", (string)unity["type"], "VSCode should include type=stdio");
            Assert.IsNull(unity["env"], "env should not be added for VSCode");
            Assert.IsNull(unity["disabled"], "disabled should not be added for VSCode");
        }

        [Test]
        public void Windsurf_ManualJson_HasMcpServersEnv_DisabledFalse()
        {
            var client = new McpClient { name = "Windsurf", mcpType = McpTypes.Windsurf };
            string json = ConfigJsonBuilder.BuildManualConfigJson("/usr/bin/uv", "/path/to/server", client);

            var root = JObject.Parse(json);
            var unity = (JObject)root.SelectToken("mcpServers.unityMCP");
            Assert.NotNull(unity, "Expected mcpServers.unityMCP node");
            Assert.NotNull(unity["env"], "env should be included");
            Assert.AreEqual(false, (bool)unity["disabled"], "disabled:false should be added for Windsurf");
            Assert.IsNull(unity["type"], "type should not be added for non-VSCode clients");
        }

        [Test]
        public void Cursor_ManualJson_HasMcpServers_NoEnv_NoDisabled()
        {
            var client = new McpClient { name = "Cursor", mcpType = McpTypes.Cursor };
            string json = ConfigJsonBuilder.BuildManualConfigJson("/usr/bin/uv", "/path/to/server", client);

            var root = JObject.Parse(json);
            var unity = (JObject)root.SelectToken("mcpServers.unityMCP");
            Assert.NotNull(unity, "Expected mcpServers.unityMCP node");
            Assert.IsNull(unity["env"], "env should not be added for Cursor");
            Assert.IsNull(unity["disabled"], "disabled should not be added for Cursor");
            Assert.IsNull(unity["type"], "type should not be added for non-VSCode clients");
        }
    }
}

```

--------------------------------------------------------------------------------
/tests/test_resources_api.py:
--------------------------------------------------------------------------------

```python
from tools.resource_tools import register_resource_tools  # type: ignore
import pytest


import sys
from pathlib import Path
import pytest
import types

# locate server src dynamically to avoid hardcoded layout assumptions
ROOT = Path(__file__).resolve().parents[1]
candidates = [
    ROOT / "MCPForUnity" / "UnityMcpServer~" / "src",
    ROOT / "UnityMcpServer~" / "src",
]
SRC = next((p for p in candidates if p.exists()), None)
if SRC is None:
    searched = "\n".join(str(p) for p in candidates)
    pytest.skip(
        "MCP for Unity server source not found. Tried:\n" + searched,
        allow_module_level=True,
    )
sys.path.insert(0, str(SRC))


class DummyMCP:
    def __init__(self):
        self._tools = {}

    def tool(self, *args, **kwargs):  # accept kwargs like description
        def deco(fn):
            self._tools[fn.__name__] = fn
            return fn
        return deco


@pytest.fixture()
def resource_tools():
    mcp = DummyMCP()
    register_resource_tools(mcp)
    return mcp._tools


def test_resource_list_filters_and_rejects_traversal(resource_tools, tmp_path, monkeypatch):
    # Create fake project structure
    proj = tmp_path
    assets = proj / "Assets" / "Scripts"
    assets.mkdir(parents=True)
    (assets / "A.cs").write_text("// a", encoding="utf-8")
    (assets / "B.txt").write_text("b", encoding="utf-8")
    outside = tmp_path / "Outside.cs"
    outside.write_text("// outside", encoding="utf-8")
    # Symlink attempting to escape
    sneaky_link = assets / "link_out"
    try:
        sneaky_link.symlink_to(outside)
    except Exception:
        # Some platforms may not allow symlinks in tests; ignore
        pass

    list_resources = resource_tools["list_resources"]
    # Only .cs under Assets should be listed
    import asyncio
    resp = asyncio.get_event_loop().run_until_complete(
        list_resources(ctx=None, pattern="*.cs", under="Assets",
                       limit=50, project_root=str(proj))
    )
    assert resp["success"] is True
    uris = resp["data"]["uris"]
    assert any(u.endswith("Assets/Scripts/A.cs") for u in uris)
    assert not any(u.endswith("B.txt") for u in uris)
    assert not any(u.endswith("Outside.cs") for u in uris)


def test_resource_list_rejects_outside_paths(resource_tools, tmp_path):
    proj = tmp_path
    # under points outside Assets
    list_resources = resource_tools["list_resources"]
    import asyncio
    resp = asyncio.get_event_loop().run_until_complete(
        list_resources(ctx=None, pattern="*.cs", under="..",
                       limit=10, project_root=str(proj))
    )
    assert resp["success"] is False
    assert "Assets" in resp.get(
        "error", "") or "under project root" in resp.get("error", "")

```

--------------------------------------------------------------------------------
/tests/test_telemetry_subaction.py:
--------------------------------------------------------------------------------

```python
import importlib


def _get_decorator_module():
    # Import the telemetry_decorator module from the MCP for Unity server src
    mod = importlib.import_module(
        "MCPForUnity.UnityMcpServer~.src.telemetry_decorator")
    return mod


def test_subaction_extracted_from_keyword(monkeypatch):
    td = _get_decorator_module()

    captured = {}

    def fake_record_tool_usage(tool_name, success, duration_ms, error, sub_action=None):
        captured["tool_name"] = tool_name
        captured["success"] = success
        captured["error"] = error
        captured["sub_action"] = sub_action

    # Silence milestones/logging in test
    monkeypatch.setattr(td, "record_tool_usage", fake_record_tool_usage)
    monkeypatch.setattr(td, "record_milestone", lambda *a, **k: None)
    monkeypatch.setattr(td, "_decorator_log_count", 999)

    def dummy_tool(ctx, action: str, name: str = ""):
        return {"success": True, "name": name}

    wrapped = td.telemetry_tool("manage_scene")(dummy_tool)

    resp = wrapped(None, action="get_hierarchy", name="Sample")
    assert resp["success"] is True
    assert captured["tool_name"] == "manage_scene"
    assert captured["success"] is True
    assert captured["error"] is None
    assert captured["sub_action"] == "get_hierarchy"


def test_subaction_extracted_from_positionals(monkeypatch):
    td = _get_decorator_module()

    captured = {}

    def fake_record_tool_usage(tool_name, success, duration_ms, error, sub_action=None):
        captured["tool_name"] = tool_name
        captured["sub_action"] = sub_action

    monkeypatch.setattr(td, "record_tool_usage", fake_record_tool_usage)
    monkeypatch.setattr(td, "record_milestone", lambda *a, **k: None)
    monkeypatch.setattr(td, "_decorator_log_count", 999)

    def dummy_tool(ctx, action: str, name: str = ""):
        return True

    wrapped = td.telemetry_tool("manage_scene")(dummy_tool)

    _ = wrapped(None, "save", "MyScene")
    assert captured["tool_name"] == "manage_scene"
    assert captured["sub_action"] == "save"


def test_subaction_none_when_not_present(monkeypatch):
    td = _get_decorator_module()

    captured = {}

    def fake_record_tool_usage(tool_name, success, duration_ms, error, sub_action=None):
        captured["tool_name"] = tool_name
        captured["sub_action"] = sub_action

    monkeypatch.setattr(td, "record_tool_usage", fake_record_tool_usage)
    monkeypatch.setattr(td, "record_milestone", lambda *a, **k: None)
    monkeypatch.setattr(td, "_decorator_log_count", 999)

    def dummy_tool_without_action(ctx, name: str):
        return 123

    wrapped = td.telemetry_tool("apply_text_edits")(dummy_tool_without_action)
    _ = wrapped(None, name="X")
    assert captured["tool_name"] == "apply_text_edits"
    assert captured["sub_action"] is None

```

--------------------------------------------------------------------------------
/tests/test_edit_strict_and_warnings.py:
--------------------------------------------------------------------------------

```python
import sys
import pathlib
import importlib.util
import types


ROOT = pathlib.Path(__file__).resolve().parents[1]
SRC = ROOT / "MCPForUnity" / "UnityMcpServer~" / "src"
sys.path.insert(0, str(SRC))

# stub mcp.server.fastmcp
mcp_pkg = types.ModuleType("mcp")
server_pkg = types.ModuleType("mcp.server")
fastmcp_pkg = types.ModuleType("mcp.server.fastmcp")


class _Dummy:
    pass


fastmcp_pkg.FastMCP = _Dummy
fastmcp_pkg.Context = _Dummy
server_pkg.fastmcp = fastmcp_pkg
mcp_pkg.server = server_pkg
sys.modules.setdefault("mcp", mcp_pkg)
sys.modules.setdefault("mcp.server", server_pkg)
sys.modules.setdefault("mcp.server.fastmcp", fastmcp_pkg)


def _load(path: pathlib.Path, name: str):
    spec = importlib.util.spec_from_file_location(name, path)
    mod = importlib.util.module_from_spec(spec)
    spec.loader.exec_module(mod)
    return mod


manage_script = _load(SRC / "tools" / "manage_script.py", "manage_script_mod3")


class DummyMCP:
    def __init__(self): self.tools = {}

    def tool(self, *args, **kwargs):
        def deco(fn): self.tools[fn.__name__] = fn; return fn
        return deco


def setup_tools():
    mcp = DummyMCP()
    manage_script.register_manage_script_tools(mcp)
    return mcp.tools


def test_explicit_zero_based_normalized_warning(monkeypatch):
    tools = setup_tools()
    apply_edits = tools["apply_text_edits"]

    def fake_send(cmd, params):
        # Simulate Unity path returning minimal success
        return {"success": True}

    monkeypatch.setattr(manage_script, "send_command_with_retry", fake_send)

    # Explicit fields given as 0-based (invalid); SDK should normalize and warn
    edits = [{"startLine": 0, "startCol": 0,
              "endLine": 0, "endCol": 0, "newText": "//x"}]
    resp = apply_edits(None, uri="unity://path/Assets/Scripts/F.cs",
                       edits=edits, precondition_sha256="sha")

    assert resp["success"] is True
    data = resp.get("data", {})
    assert "normalizedEdits" in data
    assert any(
        w == "zero_based_explicit_fields_normalized" for w in data.get("warnings", []))
    ne = data["normalizedEdits"][0]
    assert ne["startLine"] == 1 and ne["startCol"] == 1 and ne["endLine"] == 1 and ne["endCol"] == 1


def test_strict_zero_based_error(monkeypatch):
    tools = setup_tools()
    apply_edits = tools["apply_text_edits"]

    def fake_send(cmd, params):
        return {"success": True}

    monkeypatch.setattr(manage_script, "send_command_with_retry", fake_send)

    edits = [{"startLine": 0, "startCol": 0,
              "endLine": 0, "endCol": 0, "newText": "//x"}]
    resp = apply_edits(None, uri="unity://path/Assets/Scripts/F.cs",
                       edits=edits, precondition_sha256="sha", strict=True)
    assert resp["success"] is False
    assert resp.get("code") == "zero_based_explicit_fields"

```

--------------------------------------------------------------------------------
/MCPForUnity/UnityMcpServer~/src/tools/manage_editor.py:
--------------------------------------------------------------------------------

```python
from typing import Annotated, Any, Literal

from mcp.server.fastmcp import Context
from registry import mcp_for_unity_tool
from telemetry import is_telemetry_enabled, record_tool_usage
from unity_connection import send_command_with_retry


@mcp_for_unity_tool(
    description="Controls and queries the Unity editor's state and settings"
)
def manage_editor(
    ctx: Context,
    action: Annotated[Literal["telemetry_status", "telemetry_ping", "play", "pause", "stop", "get_state", "get_project_root", "get_windows",
                              "get_active_tool", "get_selection", "get_prefab_stage", "set_active_tool", "add_tag", "remove_tag", "get_tags", "add_layer", "remove_layer", "get_layers"], "Get and update the Unity Editor state."],
    wait_for_completion: Annotated[bool,
                                   "Optional. If True, waits for certain actions"] | None = None,
    tool_name: Annotated[str,
                         "Tool name when setting active tool"] | None = None,
    tag_name: Annotated[str,
                        "Tag name when adding and removing tags"] | None = None,
    layer_name: Annotated[str,
                          "Layer name when adding and removing layers"] | None = None,
) -> dict[str, Any]:
    ctx.info(f"Processing manage_editor: {action}")
    try:
        # Diagnostics: quick telemetry checks
        if action == "telemetry_status":
            return {"success": True, "telemetry_enabled": is_telemetry_enabled()}

        if action == "telemetry_ping":
            record_tool_usage("diagnostic_ping", True, 1.0, None)
            return {"success": True, "message": "telemetry ping queued"}
        # Prepare parameters, removing None values
        params = {
            "action": action,
            "waitForCompletion": wait_for_completion,
            "toolName": tool_name,  # Corrected parameter name to match C#
            "tagName": tag_name,   # Pass tag name
            "layerName": layer_name,  # Pass layer name
            # Add other parameters based on the action being performed
            # "width": width,
            # "height": height,
            # etc.
        }
        params = {k: v for k, v in params.items() if v is not None}

        # Send command using centralized retry helper
        response = send_command_with_retry("manage_editor", params)

        # Preserve structured failure data; unwrap success into a friendlier shape
        if isinstance(response, dict) and response.get("success"):
            return {"success": True, "message": response.get("message", "Editor operation successful."), "data": response.get("data")}
        return response if isinstance(response, dict) else {"success": False, "message": str(response)}

    except Exception as e:
        return {"success": False, "message": f"Python error managing editor: {str(e)}"}

```

--------------------------------------------------------------------------------
/UnityMcpBridge/UnityMcpServer~/src/tools/manage_editor.py:
--------------------------------------------------------------------------------

```python
from typing import Annotated, Any, Literal

from mcp.server.fastmcp import Context
from registry import mcp_for_unity_tool
from telemetry import is_telemetry_enabled, record_tool_usage
from unity_connection import send_command_with_retry


@mcp_for_unity_tool(
    description="Controls and queries the Unity editor's state and settings"
)
def manage_editor(
    ctx: Context,
    action: Annotated[Literal["telemetry_status", "telemetry_ping", "play", "pause", "stop", "get_state", "get_project_root", "get_windows",
                              "get_active_tool", "get_selection", "get_prefab_stage", "set_active_tool", "add_tag", "remove_tag", "get_tags", "add_layer", "remove_layer", "get_layers"], "Get and update the Unity Editor state."],
    wait_for_completion: Annotated[bool,
                                   "Optional. If True, waits for certain actions"] | None = None,
    tool_name: Annotated[str,
                         "Tool name when setting active tool"] | None = None,
    tag_name: Annotated[str,
                        "Tag name when adding and removing tags"] | None = None,
    layer_name: Annotated[str,
                          "Layer name when adding and removing layers"] | None = None,
) -> dict[str, Any]:
    ctx.info(f"Processing manage_editor: {action}")
    try:
        # Diagnostics: quick telemetry checks
        if action == "telemetry_status":
            return {"success": True, "telemetry_enabled": is_telemetry_enabled()}

        if action == "telemetry_ping":
            record_tool_usage("diagnostic_ping", True, 1.0, None)
            return {"success": True, "message": "telemetry ping queued"}
        # Prepare parameters, removing None values
        params = {
            "action": action,
            "waitForCompletion": wait_for_completion,
            "toolName": tool_name,  # Corrected parameter name to match C#
            "tagName": tag_name,   # Pass tag name
            "layerName": layer_name,  # Pass layer name
            # Add other parameters based on the action being performed
            # "width": width,
            # "height": height,
            # etc.
        }
        params = {k: v for k, v in params.items() if v is not None}

        # Send command using centralized retry helper
        response = send_command_with_retry("manage_editor", params)

        # Preserve structured failure data; unwrap success into a friendlier shape
        if isinstance(response, dict) and response.get("success"):
            return {"success": True, "message": response.get("message", "Editor operation successful."), "data": response.get("data")}
        return response if isinstance(response, dict) else {"success": False, "message": str(response)}

    except Exception as e:
        return {"success": False, "message": f"Python error managing editor: {str(e)}"}

```

--------------------------------------------------------------------------------
/test_unity_socket_framing.py:
--------------------------------------------------------------------------------

```python
#!/usr/bin/env python3
import socket
import struct
import json
import sys

HOST = "127.0.0.1"
PORT = 6400
try:
    SIZE_MB = int(sys.argv[1])
except (IndexError, ValueError):
    SIZE_MB = 5  # e.g., 5 or 10
FILL = "R"
MAX_FRAME = 64 * 1024 * 1024


def recv_exact(sock, n):
    buf = bytearray(n)
    view = memoryview(buf)
    off = 0
    while off < n:
        r = sock.recv_into(view[off:])
        if r == 0:
            raise RuntimeError("socket closed")
        off += r
    return bytes(buf)


def is_valid_json(b):
    try:
        json.loads(b.decode("utf-8"))
        return True
    except Exception:
        return False


def recv_legacy_json(sock, timeout=60):
    sock.settimeout(timeout)
    chunks = []
    while True:
        chunk = sock.recv(65536)
        if not chunk:
            data = b"".join(chunks)
            if not data:
                raise RuntimeError("no data, socket closed")
            return data
        chunks.append(chunk)
        data = b"".join(chunks)
        if data.strip() == b"ping":
            return data
        if is_valid_json(data):
            return data


def main():
    # Cap filler to stay within framing limit (reserve small overhead for JSON)
    safe_max = max(1, MAX_FRAME - 4096)
    filler_len = min(SIZE_MB * 1024 * 1024, safe_max)
    body = {
        "type": "read_console",
        "params": {
            "action": "get",
            "types": ["all"],
            "count": 1000,
            "format": "detailed",
            "includeStacktrace": True,
            "filterText": FILL * filler_len
        }
    }
    body_bytes = json.dumps(body, ensure_ascii=False).encode("utf-8")

    with socket.create_connection((HOST, PORT), timeout=5) as s:
        s.settimeout(2)
        # Read optional greeting
        try:
            greeting = s.recv(256)
        except Exception:
            greeting = b""
        greeting_text = greeting.decode("ascii", errors="ignore").strip()
        print(f"Greeting: {greeting_text or '(none)'}")

        framing = "FRAMING=1" in greeting_text
        print(f"Using framing? {framing}")

        s.settimeout(120)
        if framing:
            header = struct.pack(">Q", len(body_bytes))
            s.sendall(header + body_bytes)
            resp_len = struct.unpack(">Q", recv_exact(s, 8))[0]
            print(f"Response framed length: {resp_len}")
            MAX_RESP = MAX_FRAME
            if resp_len <= 0 or resp_len > MAX_RESP:
                raise RuntimeError(
                    f"invalid framed length: {resp_len} (max {MAX_RESP})")
            resp = recv_exact(s, resp_len)
        else:
            s.sendall(body_bytes)
            resp = recv_legacy_json(s)

        print(f"Response bytes: {len(resp)}")
        print(f"Response head: {resp[:120].decode('utf-8', 'ignore')}")


if __name__ == "__main__":
    main()

```

--------------------------------------------------------------------------------
/tests/test_read_console_truncate.py:
--------------------------------------------------------------------------------

```python
import sys
import pathlib
import importlib.util
import types

ROOT = pathlib.Path(__file__).resolve().parents[1]
SRC = ROOT / "MCPForUnity" / "UnityMcpServer~" / "src"
sys.path.insert(0, str(SRC))

# stub mcp.server.fastmcp
mcp_pkg = types.ModuleType("mcp")
server_pkg = types.ModuleType("mcp.server")
fastmcp_pkg = types.ModuleType("mcp.server.fastmcp")


class _Dummy:
    pass


fastmcp_pkg.FastMCP = _Dummy
fastmcp_pkg.Context = _Dummy
server_pkg.fastmcp = fastmcp_pkg
mcp_pkg.server = server_pkg
sys.modules.setdefault("mcp", mcp_pkg)
sys.modules.setdefault("mcp.server", server_pkg)
sys.modules.setdefault("mcp.server.fastmcp", fastmcp_pkg)


def _load_module(path: pathlib.Path, name: str):
    spec = importlib.util.spec_from_file_location(name, path)
    mod = importlib.util.module_from_spec(spec)
    spec.loader.exec_module(mod)
    return mod


read_console_mod = _load_module(
    SRC / "tools" / "read_console.py", "read_console_mod")


class DummyMCP:
    def __init__(self):
        self.tools = {}

    def tool(self, *args, **kwargs):
        def deco(fn):
            self.tools[fn.__name__] = fn
            return fn
        return deco


def setup_tools():
    mcp = DummyMCP()
    read_console_mod.register_read_console_tools(mcp)
    return mcp.tools


def test_read_console_full_default(monkeypatch):
    tools = setup_tools()
    read_console = tools["read_console"]

    captured = {}

    def fake_send(cmd, params):
        captured["params"] = params
        return {
            "success": True,
            "data": {"lines": [{"level": "error", "message": "oops", "stacktrace": "trace", "time": "t"}]},
        }

    monkeypatch.setattr(read_console_mod, "send_command_with_retry", fake_send)
    monkeypatch.setattr(
        read_console_mod, "get_unity_connection", lambda: object())

    resp = read_console(ctx=None, count=10)
    assert resp == {
        "success": True,
        "data": {"lines": [{"level": "error", "message": "oops", "stacktrace": "trace", "time": "t"}]},
    }
    assert captured["params"]["count"] == 10
    assert captured["params"]["includeStacktrace"] is True


def test_read_console_truncated(monkeypatch):
    tools = setup_tools()
    read_console = tools["read_console"]

    captured = {}

    def fake_send(cmd, params):
        captured["params"] = params
        return {
            "success": True,
            "data": {"lines": [{"level": "error", "message": "oops", "stacktrace": "trace"}]},
        }

    monkeypatch.setattr(read_console_mod, "send_command_with_retry", fake_send)
    monkeypatch.setattr(
        read_console_mod, "get_unity_connection", lambda: object())

    resp = read_console(ctx=None, count=10, include_stacktrace=False)
    assert resp == {"success": True, "data": {
        "lines": [{"level": "error", "message": "oops"}]}}
    assert captured["params"]["includeStacktrace"] is False

```

--------------------------------------------------------------------------------
/MCPForUnity/Editor/Services/IPathResolverService.cs:
--------------------------------------------------------------------------------

```csharp
namespace MCPForUnity.Editor.Services
{
    /// <summary>
    /// Service for resolving paths to required tools and supporting user overrides
    /// </summary>
    public interface IPathResolverService
    {
        /// <summary>
        /// Gets the MCP server path (respects override if set)
        /// </summary>
        /// <returns>Path to the MCP server directory containing server.py, or null if not found</returns>
        string GetMcpServerPath();
        
        /// <summary>
        /// Gets the UV package manager path (respects override if set)
        /// </summary>
        /// <returns>Path to the uv executable, or null if not found</returns>
        string GetUvPath();
        
        /// <summary>
        /// Gets the Claude CLI path (respects override if set)
        /// </summary>
        /// <returns>Path to the claude executable, or null if not found</returns>
        string GetClaudeCliPath();
        
        /// <summary>
        /// Checks if Python is detected on the system
        /// </summary>
        /// <returns>True if Python is found</returns>
        bool IsPythonDetected();
        
        /// <summary>
        /// Checks if UV is detected on the system
        /// </summary>
        /// <returns>True if UV is found</returns>
        bool IsUvDetected();
        
        /// <summary>
        /// Checks if Claude CLI is detected on the system
        /// </summary>
        /// <returns>True if Claude CLI is found</returns>
        bool IsClaudeCliDetected();
        
        /// <summary>
        /// Sets an override for the MCP server path
        /// </summary>
        /// <param name="path">Path to override with</param>
        void SetMcpServerOverride(string path);
        
        /// <summary>
        /// Sets an override for the UV path
        /// </summary>
        /// <param name="path">Path to override with</param>
        void SetUvPathOverride(string path);
        
        /// <summary>
        /// Sets an override for the Claude CLI path
        /// </summary>
        /// <param name="path">Path to override with</param>
        void SetClaudeCliPathOverride(string path);
        
        /// <summary>
        /// Clears the MCP server path override
        /// </summary>
        void ClearMcpServerOverride();
        
        /// <summary>
        /// Clears the UV path override
        /// </summary>
        void ClearUvPathOverride();
        
        /// <summary>
        /// Clears the Claude CLI path override
        /// </summary>
        void ClearClaudeCliPathOverride();
        
        /// <summary>
        /// Gets whether a MCP server path override is active
        /// </summary>
        bool HasMcpServerOverride { get; }
        
        /// <summary>
        /// Gets whether a UV path override is active
        /// </summary>
        bool HasUvPathOverride { get; }
        
        /// <summary>
        /// Gets whether a Claude CLI path override is active
        /// </summary>
        bool HasClaudeCliPathOverride { get; }
    }
}

```
Page 1/13FirstPrevNextLast