#
tokens: 49120/50000 30/263 files (page 3/13)
lines: off (toggle) GitHub
raw markdown copy
This is page 3 of 13. Use http://codebase.md/justinpbarnett/unity-mcp?lines=false&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

--------------------------------------------------------------------------------
/tests/test_script_tools.py:
--------------------------------------------------------------------------------

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

# add server src to path and load modules without triggering package imports
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, name):
    spec = importlib.util.spec_from_file_location(name, path)
    module = importlib.util.module_from_spec(spec)
    spec.loader.exec_module(module)
    return module


manage_script_module = load_module(
    SRC / "tools" / "manage_script.py", "manage_script_module")
manage_asset_module = load_module(
    SRC / "tools" / "manage_asset.py", "manage_asset_module")


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

    def tool(self, *args, **kwargs):  # accept decorator kwargs like description
        def decorator(func):
            self.tools[func.__name__] = func
            return func
        return decorator


def setup_manage_script():
    mcp = DummyMCP()
    manage_script_module.register_manage_script_tools(mcp)
    return mcp.tools


def setup_manage_asset():
    mcp = DummyMCP()
    manage_asset_module.register_manage_asset_tools(mcp)
    return mcp.tools


def test_apply_text_edits_long_file(monkeypatch):
    tools = setup_manage_script()
    apply_edits = tools["apply_text_edits"]
    captured = {}

    def fake_send(cmd, params):
        captured["cmd"] = cmd
        captured["params"] = params
        return {"success": True}

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

    edit = {"startLine": 1005, "startCol": 0,
            "endLine": 1005, "endCol": 5, "newText": "Hello"}
    resp = apply_edits(None, "unity://path/Assets/Scripts/LongFile.cs", [edit])
    assert captured["cmd"] == "manage_script"
    assert captured["params"]["action"] == "apply_text_edits"
    assert captured["params"]["edits"][0]["startLine"] == 1005
    assert resp["success"] is True


def test_sequential_edits_use_precondition(monkeypatch):
    tools = setup_manage_script()
    apply_edits = tools["apply_text_edits"]
    calls = []

    def fake_send(cmd, params):
        calls.append(params)
        return {"success": True, "sha256": f"hash{len(calls)}"}

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

    edit1 = {"startLine": 1, "startCol": 0, "endLine": 1,
             "endCol": 0, "newText": "//header\n"}
    resp1 = apply_edits(None, "unity://path/Assets/Scripts/File.cs", [edit1])
    edit2 = {"startLine": 2, "startCol": 0, "endLine": 2,
             "endCol": 0, "newText": "//second\n"}
    resp2 = apply_edits(None, "unity://path/Assets/Scripts/File.cs",
                        [edit2], precondition_sha256=resp1["sha256"])

    assert calls[1]["precondition_sha256"] == resp1["sha256"]
    assert resp2["sha256"] == "hash2"


def test_apply_text_edits_forwards_options(monkeypatch):
    tools = setup_manage_script()
    apply_edits = tools["apply_text_edits"]
    captured = {}

    def fake_send(cmd, params):
        captured["params"] = params
        return {"success": True}

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

    opts = {"validate": "relaxed", "applyMode": "atomic", "refresh": "immediate"}
    apply_edits(None, "unity://path/Assets/Scripts/File.cs",
                [{"startLine": 1, "startCol": 1, "endLine": 1, "endCol": 1, "newText": "x"}], options=opts)
    assert captured["params"].get("options") == opts


def test_apply_text_edits_defaults_atomic_for_multi_span(monkeypatch):
    tools = setup_manage_script()
    apply_edits = tools["apply_text_edits"]
    captured = {}

    def fake_send(cmd, params):
        captured["params"] = params
        return {"success": True}

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

    edits = [
        {"startLine": 2, "startCol": 2, "endLine": 2, "endCol": 3, "newText": "A"},
        {"startLine": 3, "startCol": 2, "endLine": 3,
            "endCol": 2, "newText": "// tail\n"},
    ]
    apply_edits(None, "unity://path/Assets/Scripts/File.cs",
                edits, precondition_sha256="x")
    opts = captured["params"].get("options", {})
    assert opts.get("applyMode") == "atomic"


def test_manage_asset_prefab_modify_request(monkeypatch):
    tools = setup_manage_asset()
    manage_asset = tools["manage_asset"]
    captured = {}

    async def fake_async(cmd, params, loop=None):
        captured["cmd"] = cmd
        captured["params"] = params
        return {"success": True}

    monkeypatch.setattr(manage_asset_module,
                        "async_send_command_with_retry", fake_async)
    monkeypatch.setattr(manage_asset_module,
                        "get_unity_connection", lambda: object())

    async def run():
        resp = await manage_asset(
            None,
            action="modify",
            path="Assets/Prefabs/Player.prefab",
            properties={"hp": 100},
        )
        assert captured["cmd"] == "manage_asset"
        assert captured["params"]["action"] == "modify"
        assert captured["params"]["path"] == "Assets/Prefabs/Player.prefab"
        assert captured["params"]["properties"] == {"hp": 100}
        assert resp["success"] is True

    asyncio.run(run())

```

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

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

namespace MCPForUnity.Editor.Services
{
    /// <summary>
    /// Service for checking package updates from GitHub
    /// </summary>
    public class PackageUpdateService : IPackageUpdateService
    {
        private const string LastCheckDateKey = "MCPForUnity.LastUpdateCheck";
        private const string CachedVersionKey = "MCPForUnity.LatestKnownVersion";
        private const string PackageJsonUrl = "https://raw.githubusercontent.com/CoplayDev/unity-mcp/main/MCPForUnity/package.json";

        /// <inheritdoc/>
        public UpdateCheckResult CheckForUpdate(string currentVersion)
        {
            // Check cache first - only check once per day
            string lastCheckDate = EditorPrefs.GetString(LastCheckDateKey, "");
            string cachedLatestVersion = EditorPrefs.GetString(CachedVersionKey, "");

            if (lastCheckDate == DateTime.Now.ToString("yyyy-MM-dd") && !string.IsNullOrEmpty(cachedLatestVersion))
            {
                return new UpdateCheckResult
                {
                    CheckSucceeded = true,
                    LatestVersion = cachedLatestVersion,
                    UpdateAvailable = IsNewerVersion(cachedLatestVersion, currentVersion),
                    Message = "Using cached version check"
                };
            }

            // Don't check for Asset Store installations
            if (!IsGitInstallation())
            {
                return new UpdateCheckResult
                {
                    CheckSucceeded = false,
                    UpdateAvailable = false,
                    Message = "Asset Store installations are updated via Unity Asset Store"
                };
            }

            // Fetch latest version from GitHub
            string latestVersion = FetchLatestVersionFromGitHub();

            if (!string.IsNullOrEmpty(latestVersion))
            {
                // Cache the result
                EditorPrefs.SetString(LastCheckDateKey, DateTime.Now.ToString("yyyy-MM-dd"));
                EditorPrefs.SetString(CachedVersionKey, latestVersion);

                return new UpdateCheckResult
                {
                    CheckSucceeded = true,
                    LatestVersion = latestVersion,
                    UpdateAvailable = IsNewerVersion(latestVersion, currentVersion),
                    Message = "Successfully checked for updates"
                };
            }

            return new UpdateCheckResult
            {
                CheckSucceeded = false,
                UpdateAvailable = false,
                Message = "Failed to check for updates (network issue or offline)"
            };
        }

        /// <inheritdoc/>
        public bool IsNewerVersion(string version1, string version2)
        {
            try
            {
                // Remove any "v" prefix
                version1 = version1.TrimStart('v', 'V');
                version2 = version2.TrimStart('v', 'V');

                var version1Parts = version1.Split('.');
                var version2Parts = version2.Split('.');

                for (int i = 0; i < Math.Min(version1Parts.Length, version2Parts.Length); i++)
                {
                    if (int.TryParse(version1Parts[i], out int v1Num) &&
                        int.TryParse(version2Parts[i], out int v2Num))
                    {
                        if (v1Num > v2Num) return true;
                        if (v1Num < v2Num) return false;
                    }
                }
                return false;
            }
            catch
            {
                return false;
            }
        }

        /// <inheritdoc/>
        public bool IsGitInstallation()
        {
            // Git packages are installed via Package Manager and have a package.json in Packages/
            // Asset Store packages are in Assets/
            string packageRoot = AssetPathUtility.GetMcpPackageRootPath();

            if (string.IsNullOrEmpty(packageRoot))
            {
                return false;
            }

            // If the package is in Packages/ it's a PM install (likely Git)
            // If it's in Assets/ it's an Asset Store install
            return packageRoot.StartsWith("Packages/", StringComparison.OrdinalIgnoreCase);
        }

        /// <inheritdoc/>
        public void ClearCache()
        {
            EditorPrefs.DeleteKey(LastCheckDateKey);
            EditorPrefs.DeleteKey(CachedVersionKey);
        }

        /// <summary>
        /// Fetches the latest version from GitHub's main branch package.json
        /// </summary>
        private string FetchLatestVersionFromGitHub()
        {
            try
            {
                // GitHub API endpoint (Option 1 - has rate limits):
                // https://api.github.com/repos/CoplayDev/unity-mcp/releases/latest
                //
                // We use Option 2 (package.json directly) because:
                // - No API rate limits (GitHub serves raw files freely)
                // - Simpler - just parse JSON for version field
                // - More reliable - doesn't require releases to be published
                // - Direct source of truth from the main branch

                using (var client = new WebClient())
                {
                    client.Headers.Add("User-Agent", "Unity-MCPForUnity-UpdateChecker");
                    string jsonContent = client.DownloadString(PackageJsonUrl);

                    var packageJson = JObject.Parse(jsonContent);
                    string version = packageJson["version"]?.ToString();

                    return string.IsNullOrEmpty(version) ? null : version;
                }
            }
            catch (Exception ex)
            {
                // Silent fail - don't interrupt the user if network is unavailable
                McpLog.Info($"Update check failed (this is normal if offline): {ex.Message}");
                return null;
            }
        }
    }
}

```

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

```csharp
using System;
using System.IO;
using Newtonsoft.Json.Linq;
using UnityEditor;
using UnityEngine;
using PackageInfo = UnityEditor.PackageManager.PackageInfo;

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;
        }

        /// <summary>
        /// Gets the MCP for Unity package root path.
        /// Works for registry Package Manager, local Package Manager, and Asset Store installations.
        /// </summary>
        /// <returns>The package root path (virtual for PM, absolute for Asset Store), or null if not found</returns>
        public static string GetMcpPackageRootPath()
        {
            try
            {
                // Try Package Manager first (registry and local installs)
                var packageInfo = PackageInfo.FindForAssembly(typeof(AssetPathUtility).Assembly);
                if (packageInfo != null && !string.IsNullOrEmpty(packageInfo.assetPath))
                {
                    return packageInfo.assetPath;
                }

                // Fallback to AssetDatabase for Asset Store installs (Assets/MCPForUnity)
                string[] guids = AssetDatabase.FindAssets($"t:Script {nameof(AssetPathUtility)}");
                
                if (guids.Length == 0)
                {
                    McpLog.Warn("Could not find AssetPathUtility script in AssetDatabase");
                    return null;
                }

                string scriptPath = AssetDatabase.GUIDToAssetPath(guids[0]);
                
                // Script is at: {packageRoot}/Editor/Helpers/AssetPathUtility.cs
                // Extract {packageRoot}
                int editorIndex = scriptPath.IndexOf("/Editor/", StringComparison.Ordinal);
                
                if (editorIndex >= 0)
                {
                    return scriptPath.Substring(0, editorIndex);
                }

                McpLog.Warn($"Could not determine package root from script path: {scriptPath}");
                return null;
            }
            catch (Exception ex)
            {
                McpLog.Error($"Failed to get package root path: {ex.Message}");
                return null;
            }
        }

        /// <summary>
        /// Reads and parses the package.json file for MCP for Unity.
        /// Handles both Package Manager (registry/local) and Asset Store installations.
        /// </summary>
        /// <returns>JObject containing package.json data, or null if not found or parse failed</returns>
        public static JObject GetPackageJson()
        {
            try
            {
                string packageRoot = GetMcpPackageRootPath();
                if (string.IsNullOrEmpty(packageRoot))
                {
                    return null;
                }

                string packageJsonPath = Path.Combine(packageRoot, "package.json");

                // Convert virtual asset path to file system path
                if (packageRoot.StartsWith("Packages/", StringComparison.OrdinalIgnoreCase))
                {
                    // Package Manager install - must use PackageInfo.resolvedPath
                    // Virtual paths like "Packages/..." don't work with File.Exists()
                    // Registry packages live in Library/PackageCache/package@version/
                    var packageInfo = PackageInfo.FindForAssembly(typeof(AssetPathUtility).Assembly);
                    if (packageInfo != null && !string.IsNullOrEmpty(packageInfo.resolvedPath))
                    {
                        packageJsonPath = Path.Combine(packageInfo.resolvedPath, "package.json");
                    }
                    else
                    {
                        McpLog.Warn("Could not resolve Package Manager path for package.json");
                        return null;
                    }
                }
                else if (packageRoot.StartsWith("Assets/", StringComparison.OrdinalIgnoreCase))
                {
                    // Asset Store install - convert to absolute file system path
                    // Application.dataPath is the absolute path to the Assets folder
                    string relativePath = packageRoot.Substring("Assets/".Length);
                    packageJsonPath = Path.Combine(Application.dataPath, relativePath, "package.json");
                }

                if (!File.Exists(packageJsonPath))
                {
                    McpLog.Warn($"package.json not found at: {packageJsonPath}");
                    return null;
                }

                string json = File.ReadAllText(packageJsonPath);
                return JObject.Parse(json);
            }
            catch (Exception ex)
            {
                McpLog.Warn($"Failed to read or parse package.json: {ex.Message}");
                return null;
            }
        }

        /// <summary>
        /// Gets the version string from the package.json file.
        /// </summary>
        /// <returns>Version string, or "unknown" if not found</returns>
        public static string GetPackageVersion()
        {
            try
            {
                var packageJson = GetPackageJson();
                if (packageJson == null)
                {
                    return "unknown";
                }

                string version = packageJson["version"]?.ToString();
                return string.IsNullOrEmpty(version) ? "unknown" : version;
            }
            catch (Exception ex)
            {
                McpLog.Warn($"Failed to get package version: {ex.Message}");
                return "unknown";
            }
        }
    }
}

```

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

```csharp
using System.IO;
using System.Linq;
using MCPForUnity.Editor.Data;
using MCPForUnity.Editor.Services;
using UnityEditor;
using UnityEngine;

namespace MCPForUnity.Editor.Helpers
{
    /// <summary>
    /// Automatically syncs Python tools to the MCP server when:
    /// - PythonToolsAsset is modified
    /// - Python files are imported/reimported
    /// - Unity starts up
    /// </summary>
    [InitializeOnLoad]
    public class PythonToolSyncProcessor : AssetPostprocessor
    {
        private const string SyncEnabledKey = "MCPForUnity.AutoSyncEnabled";
        private static bool _isSyncing = false;

        static PythonToolSyncProcessor()
        {
            // Sync on Unity startup
            EditorApplication.delayCall += () =>
            {
                if (IsAutoSyncEnabled())
                {
                    SyncAllTools();
                }
            };
        }

        /// <summary>
        /// Called after any assets are imported, deleted, or moved
        /// </summary>
        private static void OnPostprocessAllAssets(
            string[] importedAssets,
            string[] deletedAssets,
            string[] movedAssets,
            string[] movedFromAssetPaths)
        {
            // Prevent infinite loop - don't process if we're currently syncing
            if (_isSyncing || !IsAutoSyncEnabled())
                return;

            bool needsSync = false;

            // Only check for .py file changes, not PythonToolsAsset changes
            // (PythonToolsAsset changes are internal state updates from syncing)
            foreach (string path in importedAssets.Concat(movedAssets))
            {
                // Check if any .py files were modified
                if (path.EndsWith(".py"))
                {
                    needsSync = true;
                    break;
                }
            }

            // Check if any .py files were deleted
            if (!needsSync && deletedAssets.Any(path => path.EndsWith(".py")))
            {
                needsSync = true;
            }

            if (needsSync)
            {
                SyncAllTools();
            }
        }

        /// <summary>
        /// Syncs all Python tools from all PythonToolsAsset instances to the MCP server
        /// </summary>
        public static void SyncAllTools()
        {
            // Prevent re-entrant calls
            if (_isSyncing)
            {
                McpLog.Warn("Sync already in progress, skipping...");
                return;
            }

            _isSyncing = true;
            try
            {
                if (!ServerPathResolver.TryFindEmbeddedServerSource(out string srcPath))
                {
                    McpLog.Warn("Cannot sync Python tools: MCP server source not found");
                    return;
                }

                string toolsDir = Path.Combine(srcPath, "tools", "custom");

                var result = MCPServiceLocator.ToolSync.SyncProjectTools(toolsDir);

                if (result.Success)
                {
                    if (result.CopiedCount > 0 || result.SkippedCount > 0)
                    {
                        McpLog.Info($"Python tools synced: {result.CopiedCount} copied, {result.SkippedCount} skipped");
                    }
                }
                else
                {
                    McpLog.Error($"Python tool sync failed with {result.ErrorCount} errors");
                    foreach (var msg in result.Messages)
                    {
                        McpLog.Error($"  - {msg}");
                    }
                }
            }
            catch (System.Exception ex)
            {
                McpLog.Error($"Python tool sync exception: {ex.Message}");
            }
            finally
            {
                _isSyncing = false;
            }
        }

        /// <summary>
        /// Checks if auto-sync is enabled (default: true)
        /// </summary>
        public static bool IsAutoSyncEnabled()
        {
            return EditorPrefs.GetBool(SyncEnabledKey, true);
        }

        /// <summary>
        /// Enables or disables auto-sync
        /// </summary>
        public static void SetAutoSyncEnabled(bool enabled)
        {
            EditorPrefs.SetBool(SyncEnabledKey, enabled);
            McpLog.Info($"Python tool auto-sync {(enabled ? "enabled" : "disabled")}");
        }

        /// <summary>
        /// Menu item to reimport all Python files in the project
        /// </summary>
        [MenuItem("Window/MCP For Unity/Tool Sync/Reimport Python Files", priority = 99)]
        public static void ReimportPythonFiles()
        {
            // Find all Python files (imported as TextAssets by PythonFileImporter)
            var pythonGuids = AssetDatabase.FindAssets("t:TextAsset", new[] { "Assets" })
                .Select(AssetDatabase.GUIDToAssetPath)
                .Where(path => path.EndsWith(".py", System.StringComparison.OrdinalIgnoreCase))
                .ToArray();

            foreach (string path in pythonGuids)
            {
                AssetDatabase.ImportAsset(path, ImportAssetOptions.ForceUpdate);
            }

            int count = pythonGuids.Length;
            McpLog.Info($"Reimported {count} Python files");
            AssetDatabase.Refresh();
        }

        /// <summary>
        /// Menu item to manually trigger sync
        /// </summary>
        [MenuItem("Window/MCP For Unity/Tool Sync/Sync Python Tools", priority = 100)]
        public static void ManualSync()
        {
            McpLog.Info("Manually syncing Python tools...");
            SyncAllTools();
        }

        /// <summary>
        /// Menu item to toggle auto-sync
        /// </summary>
        [MenuItem("Window/MCP For Unity/Tool Sync/Auto-Sync Python Tools", priority = 101)]
        public static void ToggleAutoSync()
        {
            SetAutoSyncEnabled(!IsAutoSyncEnabled());
        }

        /// <summary>
        /// Validate menu item (shows checkmark when enabled)
        /// </summary>
        [MenuItem("Window/MCP For Unity/Tool Sync/Auto-Sync Python Tools", true, priority = 101)]
        public static bool ToggleAutoSyncValidate()
        {
            Menu.SetChecked("Window/MCP For Unity/Tool Sync/Auto-Sync Python Tools", IsAutoSyncEnabled());
            return true;
        }
    }
}

```

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

```csharp
using System;
using System.IO;
using System.Runtime.InteropServices;
using System.Text;
using UnityEditor;

namespace MCPForUnity.Editor.Helpers
{
    /// <summary>
    /// Shared helpers for reading and writing MCP client configuration files.
    /// Consolidates file atomics and server directory resolution so the editor
    /// window can focus on UI concerns only.
    /// </summary>
    public static class McpConfigFileHelper
    {
        public static string ExtractDirectoryArg(string[] args)
        {
            if (args == null) return null;
            for (int i = 0; i < args.Length - 1; i++)
            {
                if (string.Equals(args[i], "--directory", StringComparison.OrdinalIgnoreCase))
                {
                    return args[i + 1];
                }
            }
            return null;
        }

        public static bool PathsEqual(string a, string b)
        {
            if (string.IsNullOrEmpty(a) || string.IsNullOrEmpty(b)) return false;
            try
            {
                string na = Path.GetFullPath(a.Trim());
                string nb = Path.GetFullPath(b.Trim());
                if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
                {
                    return string.Equals(na, nb, StringComparison.OrdinalIgnoreCase);
                }
                return string.Equals(na, nb, StringComparison.Ordinal);
            }
            catch
            {
                return false;
            }
        }

        /// <summary>
        /// Resolves the server directory to use for MCP tools, preferring
        /// existing config values and falling back to installed/embedded copies.
        /// </summary>
        public static string ResolveServerDirectory(string pythonDir, string[] existingArgs)
        {
            string serverSrc = ExtractDirectoryArg(existingArgs);
            bool serverValid = !string.IsNullOrEmpty(serverSrc)
                && File.Exists(Path.Combine(serverSrc, "server.py"));
            if (!serverValid)
            {
                if (!string.IsNullOrEmpty(pythonDir)
                    && File.Exists(Path.Combine(pythonDir, "server.py")))
                {
                    serverSrc = pythonDir;
                }
                else
                {
                    serverSrc = ResolveServerSource();
                }
            }

            try
            {
                if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX) && !string.IsNullOrEmpty(serverSrc))
                {
                    string norm = serverSrc.Replace('\\', '/');
                    int idx = norm.IndexOf("/.local/share/UnityMCP/", StringComparison.Ordinal);
                    if (idx >= 0)
                    {
                        string home = Environment.GetFolderPath(Environment.SpecialFolder.Personal) ?? string.Empty;
                        string suffix = norm.Substring(idx + "/.local/share/".Length);
                        serverSrc = Path.Combine(home, "Library", "Application Support", suffix);
                    }
                }
            }
            catch
            {
                // Ignore failures and fall back to the original path.
            }

            if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)
                && !string.IsNullOrEmpty(serverSrc)
                && serverSrc.IndexOf(@"\Library\PackageCache\", StringComparison.OrdinalIgnoreCase) >= 0
                && !EditorPrefs.GetBool("MCPForUnity.UseEmbeddedServer", false))
            {
                serverSrc = ServerInstaller.GetServerPath();
            }

            return serverSrc;
        }

        public static void WriteAtomicFile(string path, string contents)
        {
            string tmp = path + ".tmp";
            string backup = path + ".backup";
            bool writeDone = false;
            try
            {
                File.WriteAllText(tmp, contents, new UTF8Encoding(false));
                try
                {
                    File.Replace(tmp, path, backup);
                    writeDone = true;
                }
                catch (FileNotFoundException)
                {
                    File.Move(tmp, path);
                    writeDone = true;
                }
                catch (PlatformNotSupportedException)
                {
                    if (File.Exists(path))
                    {
                        try
                        {
                            if (File.Exists(backup)) File.Delete(backup);
                        }
                        catch { }
                        File.Move(path, backup);
                    }
                    File.Move(tmp, path);
                    writeDone = true;
                }
            }
            catch (Exception ex)
            {
                try
                {
                    if (!writeDone && File.Exists(backup))
                    {
                        try { File.Copy(backup, path, true); } catch { }
                    }
                }
                catch { }
                throw new Exception($"Failed to write config file '{path}': {ex.Message}", ex);
            }
            finally
            {
                try { if (File.Exists(tmp)) File.Delete(tmp); } catch { }
                try { if (writeDone && File.Exists(backup)) File.Delete(backup); } catch { }
            }
        }

        public static string ResolveServerSource()
        {
            try
            {
                string remembered = EditorPrefs.GetString("MCPForUnity.ServerSrc", string.Empty);
                if (!string.IsNullOrEmpty(remembered)
                    && File.Exists(Path.Combine(remembered, "server.py")))
                {
                    return remembered;
                }

                ServerInstaller.EnsureServerInstalled();
                string installed = ServerInstaller.GetServerPath();
                if (File.Exists(Path.Combine(installed, "server.py")))
                {
                    return installed;
                }

                bool useEmbedded = EditorPrefs.GetBool("MCPForUnity.UseEmbeddedServer", false);
                if (useEmbedded
                    && ServerPathResolver.TryFindEmbeddedServerSource(out string embedded)
                    && File.Exists(Path.Combine(embedded, "server.py")))
                {
                    return embedded;
                }

                return installed;
            }
            catch
            {
                return ServerInstaller.GetServerPath();
            }
        }
    }
}

```

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

```csharp
using System;
using System.IO;
using System.Runtime.InteropServices;
using System.Text;
using UnityEditor;

namespace MCPForUnity.Editor.Helpers
{
    /// <summary>
    /// Shared helpers for reading and writing MCP client configuration files.
    /// Consolidates file atomics and server directory resolution so the editor
    /// window can focus on UI concerns only.
    /// </summary>
    public static class McpConfigFileHelper
    {
        public static string ExtractDirectoryArg(string[] args)
        {
            if (args == null) return null;
            for (int i = 0; i < args.Length - 1; i++)
            {
                if (string.Equals(args[i], "--directory", StringComparison.OrdinalIgnoreCase))
                {
                    return args[i + 1];
                }
            }
            return null;
        }

        public static bool PathsEqual(string a, string b)
        {
            if (string.IsNullOrEmpty(a) || string.IsNullOrEmpty(b)) return false;
            try
            {
                string na = Path.GetFullPath(a.Trim());
                string nb = Path.GetFullPath(b.Trim());
                if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
                {
                    return string.Equals(na, nb, StringComparison.OrdinalIgnoreCase);
                }
                return string.Equals(na, nb, StringComparison.Ordinal);
            }
            catch
            {
                return false;
            }
        }

        /// <summary>
        /// Resolves the server directory to use for MCP tools, preferring
        /// existing config values and falling back to installed/embedded copies.
        /// </summary>
        public static string ResolveServerDirectory(string pythonDir, string[] existingArgs)
        {
            string serverSrc = ExtractDirectoryArg(existingArgs);
            bool serverValid = !string.IsNullOrEmpty(serverSrc)
                && File.Exists(Path.Combine(serverSrc, "server.py"));
            if (!serverValid)
            {
                if (!string.IsNullOrEmpty(pythonDir)
                    && File.Exists(Path.Combine(pythonDir, "server.py")))
                {
                    serverSrc = pythonDir;
                }
                else
                {
                    serverSrc = ResolveServerSource();
                }
            }

            try
            {
                if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX) && !string.IsNullOrEmpty(serverSrc))
                {
                    string norm = serverSrc.Replace('\\', '/');
                    int idx = norm.IndexOf("/.local/share/UnityMCP/", StringComparison.Ordinal);
                    if (idx >= 0)
                    {
                        string home = Environment.GetFolderPath(Environment.SpecialFolder.Personal) ?? string.Empty;
                        string suffix = norm.Substring(idx + "/.local/share/".Length);
                        serverSrc = Path.Combine(home, "Library", "Application Support", suffix);
                    }
                }
            }
            catch
            {
                // Ignore failures and fall back to the original path.
            }

            if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)
                && !string.IsNullOrEmpty(serverSrc)
                && serverSrc.IndexOf(@"\Library\PackageCache\", StringComparison.OrdinalIgnoreCase) >= 0
                && !EditorPrefs.GetBool("MCPForUnity.UseEmbeddedServer", false))
            {
                serverSrc = ServerInstaller.GetServerPath();
            }

            return serverSrc;
        }

        public static void WriteAtomicFile(string path, string contents)
        {
            string tmp = path + ".tmp";
            string backup = path + ".backup";
            bool writeDone = false;
            try
            {
                File.WriteAllText(tmp, contents, new UTF8Encoding(false));
                try
                {
                    File.Replace(tmp, path, backup);
                    writeDone = true;
                }
                catch (FileNotFoundException)
                {
                    File.Move(tmp, path);
                    writeDone = true;
                }
                catch (PlatformNotSupportedException)
                {
                    if (File.Exists(path))
                    {
                        try
                        {
                            if (File.Exists(backup)) File.Delete(backup);
                        }
                        catch { }
                        File.Move(path, backup);
                    }
                    File.Move(tmp, path);
                    writeDone = true;
                }
            }
            catch (Exception ex)
            {
                try
                {
                    if (!writeDone && File.Exists(backup))
                    {
                        try { File.Copy(backup, path, true); } catch { }
                    }
                }
                catch { }
                throw new Exception($"Failed to write config file '{path}': {ex.Message}", ex);
            }
            finally
            {
                try { if (File.Exists(tmp)) File.Delete(tmp); } catch { }
                try { if (writeDone && File.Exists(backup)) File.Delete(backup); } catch { }
            }
        }

        public static string ResolveServerSource()
        {
            try
            {
                string remembered = EditorPrefs.GetString("MCPForUnity.ServerSrc", string.Empty);
                if (!string.IsNullOrEmpty(remembered)
                    && File.Exists(Path.Combine(remembered, "server.py")))
                {
                    return remembered;
                }

                ServerInstaller.EnsureServerInstalled();
                string installed = ServerInstaller.GetServerPath();
                if (File.Exists(Path.Combine(installed, "server.py")))
                {
                    return installed;
                }

                bool useEmbedded = EditorPrefs.GetBool("MCPForUnity.UseEmbeddedServer", false);
                if (useEmbedded
                    && ServerPathResolver.TryFindEmbeddedServerSource(out string embedded)
                    && File.Exists(Path.Combine(embedded, "server.py")))
                {
                    return embedded;
                }

                return installed;
            }
            catch
            {
                return ServerInstaller.GetServerPath();
            }
        }
    }
}

```

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

```csharp
using System;
using System.IO;
using System.Net;
using System.Net.Sockets;
using System.Text;

namespace MCPForUnity.Editor.Services
{
    /// <summary>
    /// Implementation of bridge control service
    /// </summary>
    public class BridgeControlService : IBridgeControlService
    {
        public bool IsRunning => MCPForUnityBridge.IsRunning;
        public int CurrentPort => MCPForUnityBridge.GetCurrentPort();
        public bool IsAutoConnectMode => MCPForUnityBridge.IsAutoConnectMode();

        public void Start()
        {
            // If server is installed, use auto-connect mode
            // Otherwise use standard mode
            string serverPath = MCPServiceLocator.Paths.GetMcpServerPath();
            if (!string.IsNullOrEmpty(serverPath) && File.Exists(Path.Combine(serverPath, "server.py")))
            {
                MCPForUnityBridge.StartAutoConnect();
            }
            else
            {
                MCPForUnityBridge.Start();
            }
        }

        public void Stop()
        {
            MCPForUnityBridge.Stop();
        }

        public BridgeVerificationResult Verify(int port)
        {
            var result = new BridgeVerificationResult
            {
                Success = false,
                HandshakeValid = false,
                PingSucceeded = false,
                Message = "Verification not started"
            };

            const int ConnectTimeoutMs = 1000;
            const int FrameTimeoutMs = 30000; // Match bridge frame I/O timeout

            try
            {
                using (var client = new TcpClient())
                {
                    // Attempt connection
                    var connectTask = client.ConnectAsync(IPAddress.Loopback, port);
                    if (!connectTask.Wait(ConnectTimeoutMs))
                    {
                        result.Message = "Connection timeout";
                        return result;
                    }

                    using (var stream = client.GetStream())
                    {
                        try { client.NoDelay = true; } catch { }

                        // 1) Read handshake line (ASCII, newline-terminated)
                        string handshake = ReadLineAscii(stream, 2000);
                        if (string.IsNullOrEmpty(handshake) || handshake.IndexOf("FRAMING=1", StringComparison.OrdinalIgnoreCase) < 0)
                        {
                            result.Message = "Bridge handshake missing FRAMING=1";
                            return result;
                        }

                        result.HandshakeValid = true;

                        // 2) Send framed "ping"
                        byte[] payload = Encoding.UTF8.GetBytes("ping");
                        WriteFrame(stream, payload, FrameTimeoutMs);

                        // 3) Read framed response and check for pong
                        string response = ReadFrameUtf8(stream, FrameTimeoutMs);
                        if (!string.IsNullOrEmpty(response) && response.IndexOf("pong", StringComparison.OrdinalIgnoreCase) >= 0)
                        {
                            result.PingSucceeded = true;
                            result.Success = true;
                            result.Message = "Bridge verified successfully";
                        }
                        else
                        {
                            result.Message = $"Ping failed; response='{response}'";
                        }
                    }
                }
            }
            catch (Exception ex)
            {
                result.Message = $"Verification error: {ex.Message}";
            }

            return result;
        }

        // Minimal framing helpers (8-byte big-endian length prefix), blocking with timeouts
        private static void WriteFrame(NetworkStream stream, byte[] payload, int timeoutMs)
        {
            if (payload == null) throw new ArgumentNullException(nameof(payload));
            if (payload.LongLength < 1) throw new IOException("Zero-length frames are not allowed");
            
            byte[] header = new byte[8];
            ulong len = (ulong)payload.LongLength;
            header[0] = (byte)(len >> 56);
            header[1] = (byte)(len >> 48);
            header[2] = (byte)(len >> 40);
            header[3] = (byte)(len >> 32);
            header[4] = (byte)(len >> 24);
            header[5] = (byte)(len >> 16);
            header[6] = (byte)(len >> 8);
            header[7] = (byte)(len);

            stream.WriteTimeout = timeoutMs;
            stream.Write(header, 0, header.Length);
            stream.Write(payload, 0, payload.Length);
        }

        private static string ReadFrameUtf8(NetworkStream stream, int timeoutMs)
        {
            byte[] header = ReadExact(stream, 8, timeoutMs);
            ulong len = ((ulong)header[0] << 56)
                      | ((ulong)header[1] << 48)
                      | ((ulong)header[2] << 40)
                      | ((ulong)header[3] << 32)
                      | ((ulong)header[4] << 24)
                      | ((ulong)header[5] << 16)
                      | ((ulong)header[6] << 8)
                      | header[7];
            if (len == 0UL) throw new IOException("Zero-length frames are not allowed");
            if (len > int.MaxValue) throw new IOException("Frame too large");
            byte[] payload = ReadExact(stream, (int)len, timeoutMs);
            return Encoding.UTF8.GetString(payload);
        }

        private static byte[] ReadExact(NetworkStream stream, int count, int timeoutMs)
        {
            byte[] buffer = new byte[count];
            int offset = 0;
            stream.ReadTimeout = timeoutMs;
            while (offset < count)
            {
                int read = stream.Read(buffer, offset, count - offset);
                if (read <= 0) throw new IOException("Connection closed before reading expected bytes");
                offset += read;
            }
            return buffer;
        }

        private static string ReadLineAscii(NetworkStream stream, int timeoutMs, int maxLen = 512)
        {
            stream.ReadTimeout = timeoutMs;
            using (var ms = new MemoryStream())
            {
                byte[] one = new byte[1];
                while (ms.Length < maxLen)
                {
                    int n = stream.Read(one, 0, 1);
                    if (n <= 0) break;
                    if (one[0] == (byte)'\n') break;
                    ms.WriteByte(one[0]);
                }
                return Encoding.ASCII.GetString(ms.ToArray());
            }
        }
    }
}

```

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

```python
"""
Telemetry decorator for MCP for Unity tools
"""

import functools
import inspect
import logging
import time
from typing import Callable, Any

from telemetry import record_resource_usage, record_tool_usage, record_milestone, MilestoneType

_log = logging.getLogger("unity-mcp-telemetry")
_decorator_log_count = 0


def telemetry_tool(tool_name: str):
    """Decorator to add telemetry tracking to MCP tools"""
    def decorator(func: Callable) -> Callable:
        @functools.wraps(func)
        def _sync_wrapper(*args, **kwargs) -> Any:
            start_time = time.time()
            success = False
            error = None
            # Extract sub-action (e.g., 'get_hierarchy') from bound args when available
            sub_action = None
            try:
                sig = inspect.signature(func)
                bound = sig.bind_partial(*args, **kwargs)
                bound.apply_defaults()
                sub_action = bound.arguments.get("action")
            except Exception:
                sub_action = None
            try:
                global _decorator_log_count
                if _decorator_log_count < 10:
                    _log.info(f"telemetry_decorator sync: tool={tool_name}")
                    _decorator_log_count += 1
                result = func(*args, **kwargs)
                success = True
                action_val = sub_action or kwargs.get("action")
                try:
                    if tool_name == "manage_script" and action_val == "create":
                        record_milestone(MilestoneType.FIRST_SCRIPT_CREATION)
                    elif tool_name.startswith("manage_scene"):
                        record_milestone(
                            MilestoneType.FIRST_SCENE_MODIFICATION)
                    record_milestone(MilestoneType.FIRST_TOOL_USAGE)
                except Exception:
                    _log.debug("milestone emit failed", exc_info=True)
                return result
            except Exception as e:
                error = str(e)
                raise
            finally:
                duration_ms = (time.time() - start_time) * 1000
                try:
                    record_tool_usage(tool_name, success,
                                      duration_ms, error, sub_action=sub_action)
                except Exception:
                    _log.debug("record_tool_usage failed", exc_info=True)

        @functools.wraps(func)
        async def _async_wrapper(*args, **kwargs) -> Any:
            start_time = time.time()
            success = False
            error = None
            # Extract sub-action (e.g., 'get_hierarchy') from bound args when available
            sub_action = None
            try:
                sig = inspect.signature(func)
                bound = sig.bind_partial(*args, **kwargs)
                bound.apply_defaults()
                sub_action = bound.arguments.get("action")
            except Exception:
                sub_action = None
            try:
                global _decorator_log_count
                if _decorator_log_count < 10:
                    _log.info(f"telemetry_decorator async: tool={tool_name}")
                    _decorator_log_count += 1
                result = await func(*args, **kwargs)
                success = True
                action_val = sub_action or kwargs.get("action")
                try:
                    if tool_name == "manage_script" and action_val == "create":
                        record_milestone(MilestoneType.FIRST_SCRIPT_CREATION)
                    elif tool_name.startswith("manage_scene"):
                        record_milestone(
                            MilestoneType.FIRST_SCENE_MODIFICATION)
                    record_milestone(MilestoneType.FIRST_TOOL_USAGE)
                except Exception:
                    _log.debug("milestone emit failed", exc_info=True)
                return result
            except Exception as e:
                error = str(e)
                raise
            finally:
                duration_ms = (time.time() - start_time) * 1000
                try:
                    record_tool_usage(tool_name, success,
                                      duration_ms, error, sub_action=sub_action)
                except Exception:
                    _log.debug("record_tool_usage failed", exc_info=True)

        return _async_wrapper if inspect.iscoroutinefunction(func) else _sync_wrapper
    return decorator


def telemetry_resource(resource_name: str):
    """Decorator to add telemetry tracking to MCP resources"""
    def decorator(func: Callable) -> Callable:
        @functools.wraps(func)
        def _sync_wrapper(*args, **kwargs) -> Any:
            start_time = time.time()
            success = False
            error = None
            try:
                global _decorator_log_count
                if _decorator_log_count < 10:
                    _log.info(
                        f"telemetry_decorator sync: resource={resource_name}")
                    _decorator_log_count += 1
                result = func(*args, **kwargs)
                success = True
                return result
            except Exception as e:
                error = str(e)
                raise
            finally:
                duration_ms = (time.time() - start_time) * 1000
                try:
                    record_resource_usage(resource_name, success,
                                          duration_ms, error)
                except Exception:
                    _log.debug("record_resource_usage failed", exc_info=True)

        @functools.wraps(func)
        async def _async_wrapper(*args, **kwargs) -> Any:
            start_time = time.time()
            success = False
            error = None
            try:
                global _decorator_log_count
                if _decorator_log_count < 10:
                    _log.info(
                        f"telemetry_decorator async: resource={resource_name}")
                    _decorator_log_count += 1
                result = await func(*args, **kwargs)
                success = True
                return result
            except Exception as e:
                error = str(e)
                raise
            finally:
                duration_ms = (time.time() - start_time) * 1000
                try:
                    record_resource_usage(resource_name, success,
                                          duration_ms, error)
                except Exception:
                    _log.debug("record_resource_usage failed", exc_info=True)

        return _async_wrapper if inspect.iscoroutinefunction(func) else _sync_wrapper
    return decorator

```

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

```csharp
using System;
using NUnit.Framework;
using UnityEngine;
using MCPForUnity.Editor.Tools;
using static MCPForUnity.Editor.Tools.ManageGameObject;

namespace MCPForUnityTests.Editor.Tools
{
    public class ComponentResolverTests
    {
        [Test]
        public void TryResolve_ReturnsTrue_ForBuiltInComponentShortName()
        {
            bool result = ComponentResolver.TryResolve("Transform", out Type type, out string error);

            Assert.IsTrue(result, "Should resolve Transform component");
            Assert.AreEqual(typeof(Transform), type, "Should return correct Transform type");
            Assert.IsEmpty(error, "Should have no error message");
        }

        [Test]
        public void TryResolve_ReturnsTrue_ForBuiltInComponentFullyQualifiedName()
        {
            bool result = ComponentResolver.TryResolve("UnityEngine.Rigidbody", out Type type, out string error);

            Assert.IsTrue(result, "Should resolve UnityEngine.Rigidbody component");
            Assert.AreEqual(typeof(Rigidbody), type, "Should return correct Rigidbody type");
            Assert.IsEmpty(error, "Should have no error message");
        }

        [Test]
        public void TryResolve_ReturnsTrue_ForCustomComponentShortName()
        {
            bool result = ComponentResolver.TryResolve("CustomComponent", out Type type, out string error);

            Assert.IsTrue(result, "Should resolve CustomComponent");
            Assert.IsNotNull(type, "Should return valid type");
            Assert.AreEqual("CustomComponent", type.Name, "Should have correct type name");
            Assert.IsTrue(typeof(Component).IsAssignableFrom(type), "Should be a Component type");
            Assert.IsEmpty(error, "Should have no error message");
        }

        [Test]
        public void TryResolve_ReturnsTrue_ForCustomComponentFullyQualifiedName()
        {
            bool result = ComponentResolver.TryResolve("TestNamespace.CustomComponent", out Type type, out string error);

            Assert.IsTrue(result, "Should resolve TestNamespace.CustomComponent");
            Assert.IsNotNull(type, "Should return valid type");
            Assert.AreEqual("CustomComponent", type.Name, "Should have correct type name");
            Assert.AreEqual("TestNamespace.CustomComponent", type.FullName, "Should have correct full name");
            Assert.IsTrue(typeof(Component).IsAssignableFrom(type), "Should be a Component type");
            Assert.IsEmpty(error, "Should have no error message");
        }

        [Test]
        public void TryResolve_ReturnsFalse_ForNonExistentComponent()
        {
            bool result = ComponentResolver.TryResolve("NonExistentComponent", out Type type, out string error);

            Assert.IsFalse(result, "Should not resolve non-existent component");
            Assert.IsNull(type, "Should return null type");
            Assert.IsNotEmpty(error, "Should have error message");
            Assert.That(error, Does.Contain("not found"), "Error should mention component not found");
        }

        [Test]
        public void TryResolve_ReturnsFalse_ForEmptyString()
        {
            bool result = ComponentResolver.TryResolve("", out Type type, out string error);

            Assert.IsFalse(result, "Should not resolve empty string");
            Assert.IsNull(type, "Should return null type");
            Assert.IsNotEmpty(error, "Should have error message");
        }

        [Test]
        public void TryResolve_ReturnsFalse_ForNullString()
        {
            bool result = ComponentResolver.TryResolve(null, out Type type, out string error);

            Assert.IsFalse(result, "Should not resolve null string");
            Assert.IsNull(type, "Should return null type");
            Assert.IsNotEmpty(error, "Should have error message");
            Assert.That(error, Does.Contain("null or empty"), "Error should mention null or empty");
        }

        [Test]
        public void TryResolve_CachesResolvedTypes()
        {
            // First call
            bool result1 = ComponentResolver.TryResolve("Transform", out Type type1, out string error1);

            // Second call should use cache
            bool result2 = ComponentResolver.TryResolve("Transform", out Type type2, out string error2);

            Assert.IsTrue(result1, "First call should succeed");
            Assert.IsTrue(result2, "Second call should succeed");
            Assert.AreSame(type1, type2, "Should return same type instance (cached)");
            Assert.IsEmpty(error1, "First call should have no error");
            Assert.IsEmpty(error2, "Second call should have no error");
        }

        [Test]
        public void TryResolve_PrefersPlayerAssemblies()
        {
            // Test that custom user scripts (in Player assemblies) are found
            bool result = ComponentResolver.TryResolve("CustomComponent", out Type type, out string error);

            Assert.IsTrue(result, "Should resolve user script from Player assembly");
            Assert.IsNotNull(type, "Should return valid type");

            // Verify it's not from an Editor assembly by checking the assembly name
            string assemblyName = type.Assembly.GetName().Name;
            Assert.That(assemblyName, Does.Not.Contain("Editor"),
                "User script should come from Player assembly, not Editor assembly");

            // Verify it's from the TestAsmdef assembly (which is a Player assembly)
            Assert.AreEqual("TestAsmdef", assemblyName,
                "CustomComponent should be resolved from TestAsmdef assembly");
        }

        [Test]
        public void TryResolve_HandlesDuplicateNames_WithAmbiguityError()
        {
            // This test would need duplicate component names to be meaningful
            // For now, test with a built-in component that should not have duplicates
            bool result = ComponentResolver.TryResolve("Transform", out Type type, out string error);

            Assert.IsTrue(result, "Transform should resolve uniquely");
            Assert.AreEqual(typeof(Transform), type, "Should return correct type");
            Assert.IsEmpty(error, "Should have no ambiguity error");
        }

        [Test]
        public void ResolvedType_IsValidComponent()
        {
            bool result = ComponentResolver.TryResolve("Rigidbody", out Type type, out string error);

            Assert.IsTrue(result, "Should resolve Rigidbody");
            Assert.IsTrue(typeof(Component).IsAssignableFrom(type), "Resolved type should be assignable from Component");
            Assert.IsTrue(typeof(MonoBehaviour).IsAssignableFrom(type) ||
                         typeof(Component).IsAssignableFrom(type), "Should be a valid Unity component");
        }
    }
}

```

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

```csharp
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using MCPForUnity.External.Tommy;

namespace MCPForUnity.Editor.Helpers
{
    /// <summary>
    /// Codex CLI specific configuration helpers. Handles TOML snippet
    /// generation and lightweight parsing so Codex can join the auto-setup
    /// flow alongside JSON-based clients.
    /// </summary>
    public static class CodexConfigHelper
    {
        public static bool IsCodexConfigured(string pythonDir)
        {
            try
            {
                string basePath = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile);
                if (string.IsNullOrEmpty(basePath)) return false;

                string configPath = Path.Combine(basePath, ".codex", "config.toml");
                if (!File.Exists(configPath)) return false;

                string toml = File.ReadAllText(configPath);
                if (!TryParseCodexServer(toml, out _, out var args)) return false;

                string dir = McpConfigFileHelper.ExtractDirectoryArg(args);
                if (string.IsNullOrEmpty(dir)) return false;

                return McpConfigFileHelper.PathsEqual(dir, pythonDir);
            }
            catch
            {
                return false;
            }
        }

        public static string BuildCodexServerBlock(string uvPath, string serverSrc)
        {
            var table = new TomlTable();
            var mcpServers = new TomlTable();

            mcpServers["unityMCP"] = CreateUnityMcpTable(uvPath, serverSrc);
            table["mcp_servers"] = mcpServers;

            using var writer = new StringWriter();
            table.WriteTo(writer);
            return writer.ToString();
        }

        public static string UpsertCodexServerBlock(string existingToml, string uvPath, string serverSrc)
        {
            // Parse existing TOML or create new root table
            var root = TryParseToml(existingToml) ?? new TomlTable();

            // Ensure mcp_servers table exists
            if (!root.TryGetNode("mcp_servers", out var mcpServersNode) || !(mcpServersNode is TomlTable))
            {
                root["mcp_servers"] = new TomlTable();
            }
            var mcpServers = root["mcp_servers"] as TomlTable;

            // Create or update unityMCP table
            mcpServers["unityMCP"] = CreateUnityMcpTable(uvPath, serverSrc);

            // Serialize back to TOML
            using var writer = new StringWriter();
            root.WriteTo(writer);
            return writer.ToString();
        }

        public static bool TryParseCodexServer(string toml, out string command, out string[] args)
        {
            command = null;
            args = null;

            var root = TryParseToml(toml);
            if (root == null) return false;

            if (!TryGetTable(root, "mcp_servers", out var servers)
                && !TryGetTable(root, "mcpServers", out servers))
            {
                return false;
            }

            if (!TryGetTable(servers, "unityMCP", out var unity))
            {
                return false;
            }

            command = GetTomlString(unity, "command");
            args = GetTomlStringArray(unity, "args");

            return !string.IsNullOrEmpty(command) && args != null;
        }

        /// <summary>
        /// Safely parses TOML string, returning null on failure
        /// </summary>
        private static TomlTable TryParseToml(string toml)
        {
            if (string.IsNullOrWhiteSpace(toml)) return null;

            try
            {
                using var reader = new StringReader(toml);
                return TOML.Parse(reader);
            }
            catch (TomlParseException)
            {
                return null;
            }
            catch (TomlSyntaxException)
            {
                return null;
            }
            catch (FormatException)
            {
                return null;
            }
        }

        /// <summary>
        /// Creates a TomlTable for the unityMCP server configuration
        /// </summary>
        private static TomlTable CreateUnityMcpTable(string uvPath, string serverSrc)
        {
            var unityMCP = new TomlTable();
            unityMCP["command"] = new TomlString { Value = uvPath };

            var argsArray = new TomlArray();
            argsArray.Add(new TomlString { Value = "run" });
            argsArray.Add(new TomlString { Value = "--directory" });
            argsArray.Add(new TomlString { Value = serverSrc });
            argsArray.Add(new TomlString { Value = "server.py" });
            unityMCP["args"] = argsArray;

            return unityMCP;
        }

        private static bool TryGetTable(TomlTable parent, string key, out TomlTable table)
        {
            table = null;
            if (parent == null) return false;

            if (parent.TryGetNode(key, out var node))
            {
                if (node is TomlTable tbl)
                {
                    table = tbl;
                    return true;
                }

                if (node is TomlArray array)
                {
                    var firstTable = array.Children.OfType<TomlTable>().FirstOrDefault();
                    if (firstTable != null)
                    {
                        table = firstTable;
                        return true;
                    }
                }
            }

            return false;
        }

        private static string GetTomlString(TomlTable table, string key)
        {
            if (table != null && table.TryGetNode(key, out var node))
            {
                if (node is TomlString str) return str.Value;
                if (node.HasValue) return node.ToString();
            }
            return null;
        }

        private static string[] GetTomlStringArray(TomlTable table, string key)
        {
            if (table == null) return null;
            if (!table.TryGetNode(key, out var node)) return null;

            if (node is TomlArray array)
            {
                List<string> values = new List<string>();
                foreach (TomlNode element in array.Children)
                {
                    if (element is TomlString str)
                    {
                        values.Add(str.Value);
                    }
                    else if (element.HasValue)
                    {
                        values.Add(element.ToString());
                    }
                }

                return values.Count > 0 ? values.ToArray() : Array.Empty<string>();
            }

            if (node is TomlString single)
            {
                return new[] { single.Value };
            }

            return null;
        }
    }
}

```

--------------------------------------------------------------------------------
/tests/test_transport_framing.py:
--------------------------------------------------------------------------------

```python
from unity_connection import UnityConnection
import sys
import json
import struct
import socket
import threading
import time
import select
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,
    )
sys.path.insert(0, str(SRC))


def start_dummy_server(greeting: bytes, respond_ping: bool = False):
    """Start a minimal TCP server for handshake tests."""
    sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    sock.bind(("127.0.0.1", 0))
    sock.listen(1)
    port = sock.getsockname()[1]
    ready = threading.Event()

    def _run():
        ready.set()
        conn, _ = sock.accept()
        conn.settimeout(1.0)
        if greeting:
            conn.sendall(greeting)
        if respond_ping:
            try:
                # Read exactly n bytes helper
                def _read_exact(n: int) -> bytes:
                    buf = b""
                    while len(buf) < n:
                        chunk = conn.recv(n - len(buf))
                        if not chunk:
                            break
                        buf += chunk
                    return buf

                header = _read_exact(8)
                if len(header) == 8:
                    length = struct.unpack(">Q", header)[0]
                    payload = _read_exact(length)
                    if payload == b'{"type":"ping"}':
                        resp = b'{"type":"pong"}'
                        conn.sendall(struct.pack(">Q", len(resp)) + resp)
            except Exception:
                pass
        time.sleep(0.1)
        try:
            conn.close()
        except Exception:
            pass
        finally:
            sock.close()

    threading.Thread(target=_run, daemon=True).start()
    ready.wait()
    return port


def start_handshake_enforcing_server():
    """Server that drops connection if client sends data before handshake."""
    sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    sock.bind(("127.0.0.1", 0))
    sock.listen(1)
    port = sock.getsockname()[1]
    ready = threading.Event()

    def _run():
        ready.set()
        conn, _ = sock.accept()
        # If client sends any data before greeting, disconnect (poll briefly)
        try:
            conn.setblocking(False)
            deadline = time.time() + 0.15  # short, reduces race with legitimate clients
            while time.time() < deadline:
                r, _, _ = select.select([conn], [], [], 0.01)
                if r:
                    try:
                        peek = conn.recv(1, socket.MSG_PEEK)
                    except BlockingIOError:
                        peek = b""
                    except Exception:
                        peek = b"\x00"
                    if peek:
                        conn.close()
                        sock.close()
                        return
            # No pre-handshake data observed; send greeting
            conn.setblocking(True)
            conn.sendall(b"MCP/0.1 FRAMING=1\n")
            time.sleep(0.1)
        finally:
            try:
                conn.close()
            finally:
                sock.close()

    threading.Thread(target=_run, daemon=True).start()
    ready.wait()
    return port


def test_handshake_requires_framing():
    port = start_dummy_server(b"MCP/0.1\n")
    conn = UnityConnection(host="127.0.0.1", port=port)
    assert conn.connect() is False
    assert conn.sock is None


def test_small_frame_ping_pong():
    port = start_dummy_server(b"MCP/0.1 FRAMING=1\n", respond_ping=True)
    conn = UnityConnection(host="127.0.0.1", port=port)
    try:
        assert conn.connect() is True
        assert conn.use_framing is True
        payload = b'{"type":"ping"}'
        conn.sock.sendall(struct.pack(">Q", len(payload)) + payload)
        resp = conn.receive_full_response(conn.sock)
        assert json.loads(resp.decode("utf-8"))["type"] == "pong"
    finally:
        conn.disconnect()


def test_unframed_data_disconnect():
    port = start_handshake_enforcing_server()
    sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    sock.connect(("127.0.0.1", port))
    sock.settimeout(1.0)
    sock.sendall(b"BAD")
    time.sleep(0.4)
    try:
        data = sock.recv(1024)
        assert data == b""
    except (ConnectionResetError, ConnectionAbortedError):
        # Some platforms raise instead of returning empty bytes when the
        # server closes the connection after detecting pre-handshake data.
        pass
    finally:
        sock.close()


def test_zero_length_payload_heartbeat():
    # Server that sends handshake and a zero-length heartbeat frame followed by a pong payload
    import socket
    import struct
    import threading
    import time

    sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    sock.bind(("127.0.0.1", 0))
    sock.listen(1)
    port = sock.getsockname()[1]
    ready = threading.Event()

    def _run():
        ready.set()
        conn, _ = sock.accept()
        try:
            conn.sendall(b"MCP/0.1 FRAMING=1\n")
            time.sleep(0.02)
            # Heartbeat frame (length=0)
            conn.sendall(struct.pack(">Q", 0))
            time.sleep(0.02)
            # Real payload frame
            payload = b'{"type":"pong"}'
            conn.sendall(struct.pack(">Q", len(payload)) + payload)
            time.sleep(0.02)
        finally:
            try:
                conn.close()
            except Exception:
                pass
            sock.close()

    threading.Thread(target=_run, daemon=True).start()
    ready.wait()

    conn = UnityConnection(host="127.0.0.1", port=port)
    try:
        assert conn.connect() is True
        # Receive should skip heartbeat and return the pong payload (or empty if only heartbeats seen)
        resp = conn.receive_full_response(conn.sock)
        assert resp in (b'{"type":"pong"}', b"")
    finally:
        conn.disconnect()


@pytest.mark.skip(reason="TODO: oversized payload should disconnect")
def test_oversized_payload_rejected():
    pass


@pytest.mark.skip(reason="TODO: partial header/payload triggers timeout and disconnect")
def test_partial_frame_timeout():
    pass


@pytest.mark.skip(reason="TODO: concurrency test with parallel tool invocations")
def test_parallel_invocations_no_interleaving():
    pass


@pytest.mark.skip(reason="TODO: reconnection after drop mid-command")
def test_reconnect_mid_command():
    pass

```

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

```csharp
using System;
using System.Diagnostics;
using System.IO;
using System.Runtime.InteropServices;
using MCPForUnity.Editor.Dependencies.Models;
using MCPForUnity.Editor.Helpers;

namespace MCPForUnity.Editor.Dependencies.PlatformDetectors
{
    /// <summary>
    /// Windows-specific dependency detection
    /// </summary>
    public class WindowsPlatformDetector : PlatformDetectorBase
    {
        public override string PlatformName => "Windows";

        public override bool CanDetect => RuntimeInformation.IsOSPlatform(OSPlatform.Windows);

        public override DependencyStatus DetectPython()
        {
            var status = new DependencyStatus("Python", isRequired: true)
            {
                InstallationHint = GetPythonInstallUrl()
            };

            try
            {
                // Check common Python installation paths
                var candidates = new[]
                {
                    "python.exe",
                    "python3.exe",
                    Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData),
                        "Programs", "Python", "Python313", "python.exe"),
                    Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData),
                        "Programs", "Python", "Python312", "python.exe"),
                    Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData),
                        "Programs", "Python", "Python311", "python.exe"),
                    Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles),
                        "Python313", "python.exe"),
                    Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles),
                        "Python312", "python.exe")
                };

                foreach (var candidate in candidates)
                {
                    if (TryValidatePython(candidate, out string version, out string fullPath))
                    {
                        status.IsAvailable = true;
                        status.Version = version;
                        status.Path = fullPath;
                        status.Details = $"Found Python {version} at {fullPath}";
                        return status;
                    }
                }

                // Try PATH resolution using 'where' command
                if (TryFindInPath("python.exe", out string pathResult) ||
                    TryFindInPath("python3.exe", out pathResult))
                {
                    if (TryValidatePython(pathResult, out string version, out string fullPath))
                    {
                        status.IsAvailable = true;
                        status.Version = version;
                        status.Path = fullPath;
                        status.Details = $"Found Python {version} in PATH at {fullPath}";
                        return status;
                    }
                }

                status.ErrorMessage = "Python not found. Please install Python 3.10 or later.";
                status.Details = "Checked common installation paths and PATH environment variable.";
            }
            catch (Exception ex)
            {
                status.ErrorMessage = $"Error detecting Python: {ex.Message}";
            }

            return status;
        }

        public override string GetPythonInstallUrl()
        {
            return "https://apps.microsoft.com/store/detail/python-313/9NCVDN91XZQP";
        }

        public override string GetUVInstallUrl()
        {
            return "https://docs.astral.sh/uv/getting-started/installation/#windows";
        }

        public override string GetInstallationRecommendations()
        {
            return @"Windows Installation Recommendations:

1. Python: Install from Microsoft Store or python.org
   - Microsoft Store: Search for 'Python 3.12' or 'Python 3.13'
   - Direct download: https://python.org/downloads/windows/

2. UV Package Manager: Install via PowerShell
   - Run: powershell -ExecutionPolicy ByPass -c ""irm https://astral.sh/uv/install.ps1 | iex""
   - Or download from: https://github.com/astral-sh/uv/releases

3. MCP Server: Will be installed automatically by Unity MCP Bridge";
        }

        private bool TryValidatePython(string pythonPath, out string version, out string fullPath)
        {
            version = null;
            fullPath = null;

            try
            {
                var psi = new ProcessStartInfo
                {
                    FileName = pythonPath,
                    Arguments = "--version",
                    UseShellExecute = false,
                    RedirectStandardOutput = true,
                    RedirectStandardError = true,
                    CreateNoWindow = true
                };

                using var process = Process.Start(psi);
                if (process == null) return false;

                string output = process.StandardOutput.ReadToEnd().Trim();
                process.WaitForExit(5000);

                if (process.ExitCode == 0 && output.StartsWith("Python "))
                {
                    version = output.Substring(7); // Remove "Python " prefix
                    fullPath = pythonPath;

                    // Validate minimum version (Python 4+ or Python 3.10+)
                    if (TryParseVersion(version, out var major, out var minor))
                    {
                        return major > 3 || (major >= 3 && minor >= 10);
                    }
                }
            }
            catch
            {
                // Ignore validation errors
            }

            return false;
        }

        private bool TryFindInPath(string executable, out string fullPath)
        {
            fullPath = null;

            try
            {
                var psi = new ProcessStartInfo
                {
                    FileName = "where",
                    Arguments = executable,
                    UseShellExecute = false,
                    RedirectStandardOutput = true,
                    RedirectStandardError = true,
                    CreateNoWindow = true
                };

                using var process = Process.Start(psi);
                if (process == null) return false;

                string output = process.StandardOutput.ReadToEnd().Trim();
                process.WaitForExit(3000);

                if (process.ExitCode == 0 && !string.IsNullOrEmpty(output))
                {
                    // Take the first result
                    var lines = output.Split(new[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries);
                    if (lines.Length > 0)
                    {
                        fullPath = lines[0].Trim();
                        return File.Exists(fullPath);
                    }
                }
            }
            catch
            {
                // Ignore errors
            }

            return false;
        }
    }
}

```

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

```csharp
using System;
using System.Diagnostics;
using System.IO;
using System.Runtime.InteropServices;
using MCPForUnity.Editor.Dependencies.Models;
using MCPForUnity.Editor.Helpers;

namespace MCPForUnity.Editor.Dependencies.PlatformDetectors
{
    /// <summary>
    /// Windows-specific dependency detection
    /// </summary>
    public class WindowsPlatformDetector : PlatformDetectorBase
    {
        public override string PlatformName => "Windows";

        public override bool CanDetect => RuntimeInformation.IsOSPlatform(OSPlatform.Windows);

        public override DependencyStatus DetectPython()
        {
            var status = new DependencyStatus("Python", isRequired: true)
            {
                InstallationHint = GetPythonInstallUrl()
            };

            try
            {
                // Check common Python installation paths
                var candidates = new[]
                {
                    "python.exe",
                    "python3.exe",
                    Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData),
                        "Programs", "Python", "Python313", "python.exe"),
                    Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData),
                        "Programs", "Python", "Python312", "python.exe"),
                    Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData),
                        "Programs", "Python", "Python311", "python.exe"),
                    Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles),
                        "Python313", "python.exe"),
                    Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles),
                        "Python312", "python.exe")
                };

                foreach (var candidate in candidates)
                {
                    if (TryValidatePython(candidate, out string version, out string fullPath))
                    {
                        status.IsAvailable = true;
                        status.Version = version;
                        status.Path = fullPath;
                        status.Details = $"Found Python {version} at {fullPath}";
                        return status;
                    }
                }

                // Try PATH resolution using 'where' command
                if (TryFindInPath("python.exe", out string pathResult) ||
                    TryFindInPath("python3.exe", out pathResult))
                {
                    if (TryValidatePython(pathResult, out string version, out string fullPath))
                    {
                        status.IsAvailable = true;
                        status.Version = version;
                        status.Path = fullPath;
                        status.Details = $"Found Python {version} in PATH at {fullPath}";
                        return status;
                    }
                }

                status.ErrorMessage = "Python not found. Please install Python 3.10 or later.";
                status.Details = "Checked common installation paths and PATH environment variable.";
            }
            catch (Exception ex)
            {
                status.ErrorMessage = $"Error detecting Python: {ex.Message}";
            }

            return status;
        }

        public override string GetPythonInstallUrl()
        {
            return "https://apps.microsoft.com/store/detail/python-313/9NCVDN91XZQP";
        }

        public override string GetUVInstallUrl()
        {
            return "https://docs.astral.sh/uv/getting-started/installation/#windows";
        }

        public override string GetInstallationRecommendations()
        {
            return @"Windows Installation Recommendations:

1. Python: Install from Microsoft Store or python.org
   - Microsoft Store: Search for 'Python 3.12' or 'Python 3.13'
   - Direct download: https://python.org/downloads/windows/

2. UV Package Manager: Install via PowerShell
   - Run: powershell -ExecutionPolicy ByPass -c ""irm https://astral.sh/uv/install.ps1 | iex""
   - Or download from: https://github.com/astral-sh/uv/releases

3. MCP Server: Will be installed automatically by MCP for Unity Bridge";
        }

        private bool TryValidatePython(string pythonPath, out string version, out string fullPath)
        {
            version = null;
            fullPath = null;

            try
            {
                var psi = new ProcessStartInfo
                {
                    FileName = pythonPath,
                    Arguments = "--version",
                    UseShellExecute = false,
                    RedirectStandardOutput = true,
                    RedirectStandardError = true,
                    CreateNoWindow = true
                };

                using var process = Process.Start(psi);
                if (process == null) return false;

                string output = process.StandardOutput.ReadToEnd().Trim();
                process.WaitForExit(5000);

                if (process.ExitCode == 0 && output.StartsWith("Python "))
                {
                    version = output.Substring(7); // Remove "Python " prefix
                    fullPath = pythonPath;

                    // Validate minimum version (Python 4+ or Python 3.10+)
                    if (TryParseVersion(version, out var major, out var minor))
                    {
                        return major > 3 || (major >= 3 && minor >= 10);
                    }
                }
            }
            catch
            {
                // Ignore validation errors
            }

            return false;
        }

        private bool TryFindInPath(string executable, out string fullPath)
        {
            fullPath = null;

            try
            {
                var psi = new ProcessStartInfo
                {
                    FileName = "where",
                    Arguments = executable,
                    UseShellExecute = false,
                    RedirectStandardOutput = true,
                    RedirectStandardError = true,
                    CreateNoWindow = true
                };

                using var process = Process.Start(psi);
                if (process == null) return false;

                string output = process.StandardOutput.ReadToEnd().Trim();
                process.WaitForExit(3000);

                if (process.ExitCode == 0 && !string.IsNullOrEmpty(output))
                {
                    // Take the first result
                    var lines = output.Split(new[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries);
                    if (lines.Length > 0)
                    {
                        fullPath = lines[0].Trim();
                        return File.Exists(fullPath);
                    }
                }
            }
            catch
            {
                // Ignore errors
            }

            return false;
        }
    }
}

```

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

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

namespace MCPForUnityTests.Editor.Tools
{
    /// <summary>
    /// In-memory tests for ManageScript validation logic.
    /// These tests focus on the validation methods directly without creating files.
    /// </summary>
    public class ManageScriptValidationTests
    {
        [Test]
        public void HandleCommand_NullParams_ReturnsError()
        {
            var result = ManageScript.HandleCommand(null);
            Assert.IsNotNull(result, "Should handle null parameters gracefully");
        }

        [Test]
        public void HandleCommand_InvalidAction_ReturnsError()
        {
            var paramsObj = new JObject
            {
                ["action"] = "invalid_action",
                ["name"] = "TestScript",
                ["path"] = "Assets/Scripts"
            };

            var result = ManageScript.HandleCommand(paramsObj);
            Assert.IsNotNull(result, "Should return error result for invalid action");
        }

        [Test]
        public void CheckBalancedDelimiters_ValidCode_ReturnsTrue()
        {
            string validCode = "using UnityEngine;\n\npublic class TestClass : MonoBehaviour\n{\n    void Start()\n    {\n        Debug.Log(\"test\");\n    }\n}";

            bool result = CallCheckBalancedDelimiters(validCode, out int line, out char expected);
            Assert.IsTrue(result, "Valid C# code should pass balance check");
        }

        [Test]
        public void CheckBalancedDelimiters_UnbalancedBraces_ReturnsFalse()
        {
            string unbalancedCode = "using UnityEngine;\n\npublic class TestClass : MonoBehaviour\n{\n    void Start()\n    {\n        Debug.Log(\"test\");\n    // Missing closing brace";

            bool result = CallCheckBalancedDelimiters(unbalancedCode, out int line, out char expected);
            Assert.IsFalse(result, "Unbalanced code should fail balance check");
        }

        [Test]
        public void CheckBalancedDelimiters_StringWithBraces_ReturnsTrue()
        {
            string codeWithStringBraces = "using UnityEngine;\n\npublic class TestClass : MonoBehaviour\n{\n    public string json = \"{key: value}\";\n    void Start() { Debug.Log(json); }\n}";

            bool result = CallCheckBalancedDelimiters(codeWithStringBraces, out int line, out char expected);
            Assert.IsTrue(result, "Code with braces in strings should pass balance check");
        }

        [Test]
        public void CheckScopedBalance_ValidCode_ReturnsTrue()
        {
            string validCode = "{ Debug.Log(\"test\"); }";

            bool result = CallCheckScopedBalance(validCode, 0, validCode.Length);
            Assert.IsTrue(result, "Valid scoped code should pass balance check");
        }

        [Test]
        public void CheckScopedBalance_ShouldTolerateOuterContext_ReturnsTrue()
        {
            // This simulates a snippet extracted from a larger context
            string contextSnippet = "    Debug.Log(\"inside method\");\n}  // This closing brace is from outer context";

            bool result = CallCheckScopedBalance(contextSnippet, 0, contextSnippet.Length);

            // Scoped balance should tolerate some imbalance from outer context
            Assert.IsTrue(result, "Scoped balance should tolerate outer context imbalance");
        }

        [Test]
        public void TicTacToe3D_ValidationScenario_DoesNotCrash()
        {
            // Test the scenario that was causing issues without file I/O
            string ticTacToeCode = "using UnityEngine;\n\npublic class TicTacToe3D : MonoBehaviour\n{\n    public string gameState = \"active\";\n    void Start() { Debug.Log(\"Game started\"); }\n    public void MakeMove(int position) { if (gameState == \"active\") Debug.Log($\"Move {position}\"); }\n}";

            // Test that the validation methods don't crash on this code
            bool balanceResult = CallCheckBalancedDelimiters(ticTacToeCode, out int line, out char expected);
            bool scopedResult = CallCheckScopedBalance(ticTacToeCode, 0, ticTacToeCode.Length);

            Assert.IsTrue(balanceResult, "TicTacToe3D code should pass balance validation");
            Assert.IsTrue(scopedResult, "TicTacToe3D code should pass scoped balance validation");
        }

        // Helper methods to access private ManageScript methods via reflection
        private bool CallCheckBalancedDelimiters(string contents, out int line, out char expected)
        {
            line = 0;
            expected = ' ';

            try
            {
                var method = typeof(ManageScript).GetMethod("CheckBalancedDelimiters",
                    BindingFlags.NonPublic | BindingFlags.Static);

                if (method != null)
                {
                    var parameters = new object[] { contents, line, expected };
                    var result = (bool)method.Invoke(null, parameters);
                    line = (int)parameters[1];
                    expected = (char)parameters[2];
                    return result;
                }
            }
            catch (Exception ex)
            {
                Debug.LogWarning($"Could not test CheckBalancedDelimiters directly: {ex.Message}");
            }

            // Fallback: basic structural check
            return BasicBalanceCheck(contents);
        }

        private bool CallCheckScopedBalance(string text, int start, int end)
        {
            try
            {
                var method = typeof(ManageScript).GetMethod("CheckScopedBalance",
                    BindingFlags.NonPublic | BindingFlags.Static);

                if (method != null)
                {
                    return (bool)method.Invoke(null, new object[] { text, start, end });
                }
            }
            catch (Exception ex)
            {
                Debug.LogWarning($"Could not test CheckScopedBalance directly: {ex.Message}");
            }

            return true; // Default to passing if we can't test the actual method
        }

        private bool BasicBalanceCheck(string contents)
        {
            // Simple fallback balance check
            int braceCount = 0;
            bool inString = false;
            bool escaped = false;

            for (int i = 0; i < contents.Length; i++)
            {
                char c = contents[i];

                if (escaped)
                {
                    escaped = false;
                    continue;
                }

                if (inString)
                {
                    if (c == '\\') escaped = true;
                    else if (c == '"') inString = false;
                    continue;
                }

                if (c == '"') inString = true;
                else if (c == '{') braceCount++;
                else if (c == '}') braceCount--;

                if (braceCount < 0) return false;
            }

            return braceCount == 0;
        }
    }
}

```

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

```csharp
using System;
using System.Collections.Generic;
using System.IO;
using MCPForUnity.Editor.Models;

namespace MCPForUnity.Editor.Data
{
    public class McpClients
    {
        public List<McpClient> clients = new()
        {
            // 1) Cursor
            new()
            {
                name = "Cursor",
                windowsConfigPath = Path.Combine(
                    Environment.GetFolderPath(Environment.SpecialFolder.UserProfile),
                    ".cursor",
                    "mcp.json"
                ),
                macConfigPath = Path.Combine(
                    Environment.GetFolderPath(Environment.SpecialFolder.UserProfile),
                    ".cursor",
                    "mcp.json"
                ),
                linuxConfigPath = Path.Combine(
                    Environment.GetFolderPath(Environment.SpecialFolder.UserProfile),
                    ".cursor",
                    "mcp.json"
                ),
                mcpType = McpTypes.Cursor,
                configStatus = "Not Configured",
            },
            // 2) Claude Code
            new()
            {
                name = "Claude Code",
                windowsConfigPath = Path.Combine(
                    Environment.GetFolderPath(Environment.SpecialFolder.UserProfile),
                    ".claude.json"
                ),
                macConfigPath = Path.Combine(
                    Environment.GetFolderPath(Environment.SpecialFolder.UserProfile),
                    ".claude.json"
                ),
                linuxConfigPath = Path.Combine(
                    Environment.GetFolderPath(Environment.SpecialFolder.UserProfile),
                    ".claude.json"
                ),
                mcpType = McpTypes.ClaudeCode,
                configStatus = "Not Configured",
            },
            // 3) Windsurf
            new()
            {
                name = "Windsurf",
                windowsConfigPath = Path.Combine(
                    Environment.GetFolderPath(Environment.SpecialFolder.UserProfile),
                    ".codeium",
                    "windsurf",
                    "mcp_config.json"
                ),
                macConfigPath = Path.Combine(
                    Environment.GetFolderPath(Environment.SpecialFolder.UserProfile),
                    ".codeium",
                    "windsurf",
                    "mcp_config.json"
                ),
                linuxConfigPath = Path.Combine(
                    Environment.GetFolderPath(Environment.SpecialFolder.UserProfile),
                    ".codeium",
                    "windsurf",
                    "mcp_config.json"
                ),
                mcpType = McpTypes.Windsurf,
                configStatus = "Not Configured",
            },
            // 4) Claude Desktop
            new()
            {
                name = "Claude Desktop",
                windowsConfigPath = Path.Combine(
                    Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData),
                    "Claude",
                    "claude_desktop_config.json"
                ),

                macConfigPath = Path.Combine(
                    Environment.GetFolderPath(Environment.SpecialFolder.UserProfile),
                    "Library",
                    "Application Support",
                    "Claude",
                    "claude_desktop_config.json"
                ),
                linuxConfigPath = Path.Combine(
                    Environment.GetFolderPath(Environment.SpecialFolder.UserProfile),
                    ".config",
                    "Claude",
                    "claude_desktop_config.json"
                ),

                mcpType = McpTypes.ClaudeDesktop,
                configStatus = "Not Configured",
            },
            // 5) VSCode GitHub Copilot
            new()
            {
                name = "VSCode GitHub Copilot",
                // Windows path is canonical under %AppData%\Code\User
                windowsConfigPath = Path.Combine(
                    Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData),
                    "Code",
                    "User",
                    "mcp.json"
                ),
                // macOS: ~/Library/Application Support/Code/User/mcp.json
                macConfigPath = Path.Combine(
                    Environment.GetFolderPath(Environment.SpecialFolder.UserProfile),
                    "Library",
                    "Application Support",
                    "Code",
                    "User",
                    "mcp.json"
                ),
                // Linux: ~/.config/Code/User/mcp.json
                linuxConfigPath = Path.Combine(
                    Environment.GetFolderPath(Environment.SpecialFolder.UserProfile),
                    ".config",
                    "Code",
                    "User",
                    "mcp.json"
                ),
                mcpType = McpTypes.VSCode,
                configStatus = "Not Configured",
            },
            // 3) Kiro
            new()
            {
                name = "Kiro",
                windowsConfigPath = Path.Combine(
                    Environment.GetFolderPath(Environment.SpecialFolder.UserProfile),
                    ".kiro",
                    "settings",
                    "mcp.json"
                ),
                macConfigPath = Path.Combine(
                    Environment.GetFolderPath(Environment.SpecialFolder.UserProfile),
                    ".kiro",
                    "settings",
                    "mcp.json"
                ),
                linuxConfigPath = Path.Combine(
                    Environment.GetFolderPath(Environment.SpecialFolder.UserProfile),
                    ".kiro",
                    "settings",
                    "mcp.json"
                ),
                mcpType = McpTypes.Kiro,
                configStatus = "Not Configured",
            },
            // 4) Codex CLI
            new()
            {
                name = "Codex CLI",
                windowsConfigPath = Path.Combine(
                    Environment.GetFolderPath(Environment.SpecialFolder.UserProfile),
                    ".codex",
                    "config.toml"
                ),
                macConfigPath = Path.Combine(
                    Environment.GetFolderPath(Environment.SpecialFolder.UserProfile),
                    ".codex",
                    "config.toml"
                ),
                linuxConfigPath = Path.Combine(
                    Environment.GetFolderPath(Environment.SpecialFolder.UserProfile),
                    ".codex",
                    "config.toml"
                ),
                mcpType = McpTypes.Codex,
                configStatus = "Not Configured",
            },
        };

        // Initialize status enums after construction
        public McpClients()
        {
            foreach (var client in clients)
            {
                if (client.configStatus == "Not Configured")
                {
                    client.status = McpStatus.NotConfigured;
                }
            }
        }
    }
}

```

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

```csharp
using System;
using System.Collections.Generic;
using NUnit.Framework;
using UnityEngine;
using MCPForUnity.Editor.Tools;
using static MCPForUnity.Editor.Tools.ManageGameObject;

namespace MCPForUnityTests.Editor.Tools
{
    public class AIPropertyMatchingTests
    {
        private List<string> sampleProperties;

        [SetUp]
        public void SetUp()
        {
            sampleProperties = new List<string>
            {
                "maxReachDistance",
                "maxHorizontalDistance",
                "maxVerticalDistance",
                "moveSpeed",
                "healthPoints",
                "playerName",
                "isEnabled",
                "mass",
                "velocity",
                "transform"
            };
        }

        [Test]
        public void GetAllComponentProperties_ReturnsValidProperties_ForTransform()
        {
            var properties = ComponentResolver.GetAllComponentProperties(typeof(Transform));

            Assert.IsNotEmpty(properties, "Transform should have properties");
            Assert.Contains("position", properties, "Transform should have position property");
            Assert.Contains("rotation", properties, "Transform should have rotation property");
            Assert.Contains("localScale", properties, "Transform should have localScale property");
        }

        [Test]
        public void GetAllComponentProperties_ReturnsEmpty_ForNullType()
        {
            var properties = ComponentResolver.GetAllComponentProperties(null);

            Assert.IsEmpty(properties, "Null type should return empty list");
        }

        [Test]
        public void GetAIPropertySuggestions_ReturnsEmpty_ForNullInput()
        {
            var suggestions = ComponentResolver.GetAIPropertySuggestions(null, sampleProperties);

            Assert.IsEmpty(suggestions, "Null input should return no suggestions");
        }

        [Test]
        public void GetAIPropertySuggestions_ReturnsEmpty_ForEmptyInput()
        {
            var suggestions = ComponentResolver.GetAIPropertySuggestions("", sampleProperties);

            Assert.IsEmpty(suggestions, "Empty input should return no suggestions");
        }

        [Test]
        public void GetAIPropertySuggestions_ReturnsEmpty_ForEmptyPropertyList()
        {
            var suggestions = ComponentResolver.GetAIPropertySuggestions("test", new List<string>());

            Assert.IsEmpty(suggestions, "Empty property list should return no suggestions");
        }

        [Test]
        public void GetAIPropertySuggestions_FindsExactMatch_AfterCleaning()
        {
            var suggestions = ComponentResolver.GetAIPropertySuggestions("Max Reach Distance", sampleProperties);

            Assert.Contains("maxReachDistance", suggestions, "Should find exact match after cleaning spaces");
            Assert.GreaterOrEqual(suggestions.Count, 1, "Should return at least one match for exact match");
        }

        [Test]
        public void GetAIPropertySuggestions_FindsMultipleWordMatches()
        {
            var suggestions = ComponentResolver.GetAIPropertySuggestions("max distance", sampleProperties);

            Assert.Contains("maxReachDistance", suggestions, "Should match maxReachDistance");
            Assert.Contains("maxHorizontalDistance", suggestions, "Should match maxHorizontalDistance");
            Assert.Contains("maxVerticalDistance", suggestions, "Should match maxVerticalDistance");
        }

        [Test]
        public void GetAIPropertySuggestions_FindsSimilarStrings_WithTypos()
        {
            var suggestions = ComponentResolver.GetAIPropertySuggestions("movespeed", sampleProperties); // missing capital S

            Assert.Contains("moveSpeed", suggestions, "Should find moveSpeed despite missing capital");
        }

        [Test]
        public void GetAIPropertySuggestions_FindsSemanticMatches_ForCommonTerms()
        {
            var suggestions = ComponentResolver.GetAIPropertySuggestions("weight", sampleProperties);

            // Note: Current algorithm might not find "mass" but should handle it gracefully
            Assert.IsNotNull(suggestions, "Should return valid suggestions list");
        }

        [Test]
        public void GetAIPropertySuggestions_LimitsResults_ToReasonableNumber()
        {
            // Test with input that might match many properties
            var suggestions = ComponentResolver.GetAIPropertySuggestions("m", sampleProperties);

            Assert.LessOrEqual(suggestions.Count, 3, "Should limit suggestions to 3 or fewer");
        }

        [Test]
        public void GetAIPropertySuggestions_CachesResults()
        {
            var input = "Max Reach Distance";

            // First call
            var suggestions1 = ComponentResolver.GetAIPropertySuggestions(input, sampleProperties);

            // Second call should use cache (tested indirectly by ensuring consistency)
            var suggestions2 = ComponentResolver.GetAIPropertySuggestions(input, sampleProperties);

            Assert.AreEqual(suggestions1.Count, suggestions2.Count, "Cached results should be consistent");
            CollectionAssert.AreEqual(suggestions1, suggestions2, "Cached results should be identical");
        }

        [Test]
        public void GetAIPropertySuggestions_HandlesUnityNamingConventions()
        {
            var unityStyleProperties = new List<string> { "isKinematic", "useGravity", "maxLinearVelocity" };

            var suggestions1 = ComponentResolver.GetAIPropertySuggestions("is kinematic", unityStyleProperties);
            var suggestions2 = ComponentResolver.GetAIPropertySuggestions("use gravity", unityStyleProperties);
            var suggestions3 = ComponentResolver.GetAIPropertySuggestions("max linear velocity", unityStyleProperties);

            Assert.Contains("isKinematic", suggestions1, "Should handle 'is' prefix convention");
            Assert.Contains("useGravity", suggestions2, "Should handle 'use' prefix convention");
            Assert.Contains("maxLinearVelocity", suggestions3, "Should handle 'max' prefix convention");
        }

        [Test]
        public void GetAIPropertySuggestions_PrioritizesExactMatches()
        {
            var properties = new List<string> { "speed", "moveSpeed", "maxSpeed", "speedMultiplier" };
            var suggestions = ComponentResolver.GetAIPropertySuggestions("speed", properties);

            Assert.IsNotEmpty(suggestions, "Should find suggestions");
            Assert.Contains("speed", suggestions, "Exact match should be included in results");
            // Note: Implementation may or may not prioritize exact matches first
        }

        [Test]
        public void GetAIPropertySuggestions_HandlesCaseInsensitive()
        {
            var suggestions1 = ComponentResolver.GetAIPropertySuggestions("MAXREACHDISTANCE", sampleProperties);
            var suggestions2 = ComponentResolver.GetAIPropertySuggestions("maxreachdistance", sampleProperties);

            Assert.Contains("maxReachDistance", suggestions1, "Should handle uppercase input");
            Assert.Contains("maxReachDistance", suggestions2, "Should handle lowercase input");
        }
    }
}

```

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

```csharp
using System;
using System.Collections.Generic;
using System.IO;
using System.Runtime.InteropServices;
using MCPForUnity.Editor.Models;

namespace MCPForUnity.Editor.Data
{
    public class McpClients
    {
        public List<McpClient> clients = new()
        {
            // 1) Cursor
            new()
            {
                name = "Cursor",
                windowsConfigPath = Path.Combine(
                    Environment.GetFolderPath(Environment.SpecialFolder.UserProfile),
                    ".cursor",
                    "mcp.json"
                ),
                macConfigPath = Path.Combine(
                    Environment.GetFolderPath(Environment.SpecialFolder.UserProfile),
                    ".cursor",
                    "mcp.json"
                ),
                linuxConfigPath = Path.Combine(
                    Environment.GetFolderPath(Environment.SpecialFolder.UserProfile),
                    ".cursor",
                    "mcp.json"
                ),
                mcpType = McpTypes.Cursor,
                configStatus = "Not Configured",
            },
            // 2) Claude Code
            new()
            {
                name = "Claude Code",
                windowsConfigPath = Path.Combine(
                    Environment.GetFolderPath(Environment.SpecialFolder.UserProfile),
                    ".claude.json"
                ),
                macConfigPath = Path.Combine(
                    Environment.GetFolderPath(Environment.SpecialFolder.UserProfile),
                    ".claude.json"
                ),
                linuxConfigPath = Path.Combine(
                    Environment.GetFolderPath(Environment.SpecialFolder.UserProfile),
                    ".claude.json"
                ),
                mcpType = McpTypes.ClaudeCode,
                configStatus = "Not Configured",
            },
            // 3) Windsurf
            new()
            {
                name = "Windsurf",
                windowsConfigPath = Path.Combine(
                    Environment.GetFolderPath(Environment.SpecialFolder.UserProfile),
                    ".codeium",
                    "windsurf",
                    "mcp_config.json"
                ),
                macConfigPath = Path.Combine(
                    Environment.GetFolderPath(Environment.SpecialFolder.UserProfile),
                    ".codeium",
                    "windsurf",
                    "mcp_config.json"
                ),
                linuxConfigPath = Path.Combine(
                    Environment.GetFolderPath(Environment.SpecialFolder.UserProfile),
                    ".codeium",
                    "windsurf",
                    "mcp_config.json"
                ),
                mcpType = McpTypes.Windsurf,
                configStatus = "Not Configured",
            },
            // 4) Claude Desktop
            new()
            {
                name = "Claude Desktop",
                windowsConfigPath = Path.Combine(
                    Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData),
                    "Claude",
                    "claude_desktop_config.json"
                ),

                macConfigPath = Path.Combine(
                    Environment.GetFolderPath(Environment.SpecialFolder.UserProfile),
                    "Library",
                    "Application Support",
                    "Claude",
                    "claude_desktop_config.json"
                ),
                linuxConfigPath = Path.Combine(
                    Environment.GetFolderPath(Environment.SpecialFolder.UserProfile),
                    ".config",
                    "Claude",
                    "claude_desktop_config.json"
                ),

                mcpType = McpTypes.ClaudeDesktop,
                configStatus = "Not Configured",
            },
            // 5) VSCode GitHub Copilot
            new()
            {
                name = "VSCode GitHub Copilot",
                // Windows path is canonical under %AppData%\Code\User
                windowsConfigPath = Path.Combine(
                    Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData),
                    "Code",
                    "User",
                    "mcp.json"
                ),
                // macOS: ~/Library/Application Support/Code/User/mcp.json
                macConfigPath = Path.Combine(
                    Environment.GetFolderPath(Environment.SpecialFolder.UserProfile),
                    "Library",
                    "Application Support",
                    "Code",
                    "User",
                    "mcp.json"
                ),
                // Linux: ~/.config/Code/User/mcp.json
                linuxConfigPath = Path.Combine(
                    Environment.GetFolderPath(Environment.SpecialFolder.UserProfile),
                    ".config",
                    "Code",
                    "User",
                    "mcp.json"
                ),
                mcpType = McpTypes.VSCode,
                configStatus = "Not Configured",
            },
            // 3) Kiro
            new()
            {
                name = "Kiro",
                windowsConfigPath = Path.Combine(
                    Environment.GetFolderPath(Environment.SpecialFolder.UserProfile),
                    ".kiro",
                    "settings",
                    "mcp.json"
                ),
                macConfigPath = Path.Combine(
                    Environment.GetFolderPath(Environment.SpecialFolder.UserProfile),
                    ".kiro",
                    "settings",
                    "mcp.json"
                ),
                linuxConfigPath = Path.Combine(
                    Environment.GetFolderPath(Environment.SpecialFolder.UserProfile),
                    ".kiro",
                    "settings",
                    "mcp.json"
                ),
                mcpType = McpTypes.Kiro,
                configStatus = "Not Configured",
            },
            // 4) Codex CLI
            new()
            {
                name = "Codex CLI",
                windowsConfigPath = Path.Combine(
                    Environment.GetFolderPath(Environment.SpecialFolder.UserProfile),
                    ".codex",
                    "config.toml"
                ),
                macConfigPath = Path.Combine(
                    Environment.GetFolderPath(Environment.SpecialFolder.UserProfile),
                    ".codex",
                    "config.toml"
                ),
                linuxConfigPath = Path.Combine(
                    Environment.GetFolderPath(Environment.SpecialFolder.UserProfile),
                    ".codex",
                    "config.toml"
                ),
                mcpType = McpTypes.Codex,
                configStatus = "Not Configured",
            },
        };

        // Initialize status enums after construction
        public McpClients()
        {
            foreach (var client in clients)
            {
                if (client.configStatus == "Not Configured")
                {
                    client.status = McpStatus.NotConfigured;
                }
            }
        }
    }
}

```

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

```python
from telemetry import record_telemetry, record_milestone, RecordType, MilestoneType
from mcp.server.fastmcp import FastMCP
import logging
from logging.handlers import RotatingFileHandler
import os
from contextlib import asynccontextmanager
from typing import AsyncIterator, Dict, Any
from config import config
from tools import register_all_tools
from resources import register_all_resources
from unity_connection import get_unity_connection, UnityConnection
import time

# Configure logging using settings from config
logging.basicConfig(
    level=getattr(logging, config.log_level),
    format=config.log_format,
    stream=None,  # None -> defaults to sys.stderr; avoid stdout used by MCP stdio
    force=True    # Ensure our handler replaces any prior stdout handlers
)
logger = logging.getLogger("mcp-for-unity-server")

# Also write logs to a rotating file so logs are available when launched via stdio
try:
    import os as _os
    _log_dir = _os.path.join(_os.path.expanduser(
        "~/Library/Application Support/UnityMCP"), "Logs")
    _os.makedirs(_log_dir, exist_ok=True)
    _file_path = _os.path.join(_log_dir, "unity_mcp_server.log")
    _fh = RotatingFileHandler(
        _file_path, maxBytes=512*1024, backupCount=2, encoding="utf-8")
    _fh.setFormatter(logging.Formatter(config.log_format))
    _fh.setLevel(getattr(logging, config.log_level))
    logger.addHandler(_fh)
    # Also route telemetry logger to the same rotating file and normal level
    try:
        tlog = logging.getLogger("unity-mcp-telemetry")
        tlog.setLevel(getattr(logging, config.log_level))
        tlog.addHandler(_fh)
    except Exception:
        # Never let logging setup break startup
        pass
except Exception:
    # Never let logging setup break startup
    pass
# Quieten noisy third-party loggers to avoid clutter during stdio handshake
for noisy in ("httpx", "urllib3"):
    try:
        logging.getLogger(noisy).setLevel(
            max(logging.WARNING, getattr(logging, config.log_level)))
    except Exception:
        pass

# Import telemetry only after logging is configured to ensure its logs use stderr and proper levels
# Ensure a slightly higher telemetry timeout unless explicitly overridden by env
try:

    # Ensure generous timeout unless explicitly overridden by env
    if not os.environ.get("UNITY_MCP_TELEMETRY_TIMEOUT"):
        os.environ["UNITY_MCP_TELEMETRY_TIMEOUT"] = "5.0"
except Exception:
    pass

# Global connection state
_unity_connection: UnityConnection = None


@asynccontextmanager
async def server_lifespan(server: FastMCP) -> AsyncIterator[Dict[str, Any]]:
    """Handle server startup and shutdown."""
    global _unity_connection
    logger.info("MCP for Unity Server starting up")

    # Record server startup telemetry
    start_time = time.time()
    start_clk = time.perf_counter()
    try:
        from pathlib import Path
        ver_path = Path(__file__).parent / "server_version.txt"
        server_version = ver_path.read_text(encoding="utf-8").strip()
    except Exception:
        server_version = "unknown"
    # Defer initial telemetry by 1s to avoid stdio handshake interference
    import threading

    def _emit_startup():
        try:
            record_telemetry(RecordType.STARTUP, {
                "server_version": server_version,
                "startup_time": start_time,
            })
            record_milestone(MilestoneType.FIRST_STARTUP)
        except Exception:
            logger.debug("Deferred startup telemetry failed", exc_info=True)
    threading.Timer(1.0, _emit_startup).start()

    try:
        skip_connect = os.environ.get(
            "UNITY_MCP_SKIP_STARTUP_CONNECT", "").lower() in ("1", "true", "yes", "on")
        if skip_connect:
            logger.info(
                "Skipping Unity connection on startup (UNITY_MCP_SKIP_STARTUP_CONNECT=1)")
        else:
            _unity_connection = get_unity_connection()
            logger.info("Connected to Unity on startup")

            # Record successful Unity connection (deferred)
            import threading as _t
            _t.Timer(1.0, lambda: record_telemetry(
                RecordType.UNITY_CONNECTION,
                {
                    "status": "connected",
                    "connection_time_ms": (time.perf_counter() - start_clk) * 1000,
                }
            )).start()

    except ConnectionError as e:
        logger.warning("Could not connect to Unity on startup: %s", e)
        _unity_connection = None

        # Record connection failure (deferred)
        import threading as _t
        _err_msg = str(e)[:200]
        _t.Timer(1.0, lambda: record_telemetry(
            RecordType.UNITY_CONNECTION,
            {
                "status": "failed",
                "error": _err_msg,
                "connection_time_ms": (time.perf_counter() - start_clk) * 1000,
            }
        )).start()
    except Exception as e:
        logger.warning(
            "Unexpected error connecting to Unity on startup: %s", e)
        _unity_connection = None
        import threading as _t
        _err_msg = str(e)[:200]
        _t.Timer(1.0, lambda: record_telemetry(
            RecordType.UNITY_CONNECTION,
            {
                "status": "failed",
                "error": _err_msg,
                "connection_time_ms": (time.perf_counter() - start_clk) * 1000,
            }
        )).start()

    try:
        # Yield the connection object so it can be attached to the context
        # The key 'bridge' matches how tools like read_console expect to access it (ctx.bridge)
        yield {"bridge": _unity_connection}
    finally:
        if _unity_connection:
            _unity_connection.disconnect()
            _unity_connection = None
        logger.info("MCP for Unity Server shut down")

# Initialize MCP server
mcp = FastMCP(
    name="mcp-for-unity-server",
    lifespan=server_lifespan
)

# Register all tools
register_all_tools(mcp)

# Register all resources
register_all_resources(mcp)


@mcp.prompt()
def asset_creation_strategy() -> str:
    """Guide for discovering and using MCP for Unity tools effectively."""
    return (
        "Available MCP for Unity Server Tools:\n\n"
        "- `manage_editor`: Controls editor state and queries info.\n"
        "- `execute_menu_item`: Executes, lists and checks for the existence of Unity Editor menu items.\n"
        "- `read_console`: Reads or clears Unity console messages, with filtering options.\n"
        "- `manage_scene`: Manages scenes.\n"
        "- `manage_gameobject`: Manages GameObjects in the scene.\n"
        "- `manage_script`: Manages C# script files.\n"
        "- `manage_asset`: Manages prefabs and assets.\n"
        "- `manage_shader`: Manages shaders.\n\n"
        "Tips:\n"
        "- Create prefabs for reusable GameObjects.\n"
        "- Always include a camera and main light in your scenes.\n"
        "- Unless specified otherwise, paths are relative to the project's `Assets/` folder.\n"
        "- After creating or modifying scripts with `manage_script`, allow Unity to recompile; use `read_console` to check for compile errors.\n"
        "- Use `execute_menu_item` for interacting with Unity systems and third party tools like a user would.\n"
    )


# Run the server
if __name__ == "__main__":
    mcp.run(transport='stdio')

```

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

```csharp
using System;
using System.Diagnostics;
using System.IO;
using System.Runtime.InteropServices;
using MCPForUnity.Editor.Dependencies.Models;
using MCPForUnity.Editor.Helpers;

namespace MCPForUnity.Editor.Dependencies.PlatformDetectors
{
    /// <summary>
    /// Linux-specific dependency detection
    /// </summary>
    public class LinuxPlatformDetector : PlatformDetectorBase
    {
        public override string PlatformName => "Linux";

        public override bool CanDetect => RuntimeInformation.IsOSPlatform(OSPlatform.Linux);

        public override DependencyStatus DetectPython()
        {
            var status = new DependencyStatus("Python", isRequired: true)
            {
                InstallationHint = GetPythonInstallUrl()
            };

            try
            {
                // Check common Python installation paths on Linux
                var candidates = new[]
                {
                    "python3",
                    "python",
                    "/usr/bin/python3",
                    "/usr/local/bin/python3",
                    "/opt/python/bin/python3",
                    "/snap/bin/python3"
                };

                foreach (var candidate in candidates)
                {
                    if (TryValidatePython(candidate, out string version, out string fullPath))
                    {
                        status.IsAvailable = true;
                        status.Version = version;
                        status.Path = fullPath;
                        status.Details = $"Found Python {version} at {fullPath}";
                        return status;
                    }
                }

                // Try PATH resolution using 'which' command
                if (TryFindInPath("python3", out string pathResult) ||
                    TryFindInPath("python", out pathResult))
                {
                    if (TryValidatePython(pathResult, out string version, out string fullPath))
                    {
                        status.IsAvailable = true;
                        status.Version = version;
                        status.Path = fullPath;
                        status.Details = $"Found Python {version} in PATH at {fullPath}";
                        return status;
                    }
                }

                status.ErrorMessage = "Python not found. Please install Python 3.10 or later.";
                status.Details = "Checked common installation paths including system, snap, and user-local locations.";
            }
            catch (Exception ex)
            {
                status.ErrorMessage = $"Error detecting Python: {ex.Message}";
            }

            return status;
        }

        public override string GetPythonInstallUrl()
        {
            return "https://www.python.org/downloads/source/";
        }

        public override string GetUVInstallUrl()
        {
            return "https://docs.astral.sh/uv/getting-started/installation/#linux";
        }

        public override string GetInstallationRecommendations()
        {
            return @"Linux Installation Recommendations:

1. Python: Install via package manager or pyenv
   - Ubuntu/Debian: sudo apt install python3 python3-pip
   - Fedora/RHEL: sudo dnf install python3 python3-pip
   - Arch: sudo pacman -S python python-pip
   - Or use pyenv: https://github.com/pyenv/pyenv

2. UV Package Manager: Install via curl
   - Run: curl -LsSf https://astral.sh/uv/install.sh | sh
   - Or download from: https://github.com/astral-sh/uv/releases

3. MCP Server: Will be installed automatically by MCP for Unity

Note: Make sure ~/.local/bin is in your PATH for user-local installations.";
        }

        private bool TryValidatePython(string pythonPath, out string version, out string fullPath)
        {
            version = null;
            fullPath = null;

            try
            {
                var psi = new ProcessStartInfo
                {
                    FileName = pythonPath,
                    Arguments = "--version",
                    UseShellExecute = false,
                    RedirectStandardOutput = true,
                    RedirectStandardError = true,
                    CreateNoWindow = true
                };

                // Set PATH to include common locations
                var homeDir = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile);
                var pathAdditions = new[]
                {
                    "/usr/local/bin",
                    "/usr/bin",
                    "/bin",
                    "/snap/bin",
                    Path.Combine(homeDir, ".local", "bin")
                };

                string currentPath = Environment.GetEnvironmentVariable("PATH") ?? "";
                psi.EnvironmentVariables["PATH"] = string.Join(":", pathAdditions) + ":" + currentPath;

                using var process = Process.Start(psi);
                if (process == null) return false;

                string output = process.StandardOutput.ReadToEnd().Trim();
                process.WaitForExit(5000);

                if (process.ExitCode == 0 && output.StartsWith("Python "))
                {
                    version = output.Substring(7); // Remove "Python " prefix
                    fullPath = pythonPath;

                    // Validate minimum version (Python 4+ or Python 3.10+)
                    if (TryParseVersion(version, out var major, out var minor))
                    {
                        return major > 3 || (major >= 3 && minor >= 10);
                    }
                }
            }
            catch
            {
                // Ignore validation errors
            }

            return false;
        }

        private bool TryFindInPath(string executable, out string fullPath)
        {
            fullPath = null;

            try
            {
                var psi = new ProcessStartInfo
                {
                    FileName = "/usr/bin/which",
                    Arguments = executable,
                    UseShellExecute = false,
                    RedirectStandardOutput = true,
                    RedirectStandardError = true,
                    CreateNoWindow = true
                };

                // Enhance PATH for Unity's GUI environment
                var homeDir = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile);
                var pathAdditions = new[]
                {
                    "/usr/local/bin",
                    "/usr/bin",
                    "/bin",
                    "/snap/bin",
                    Path.Combine(homeDir, ".local", "bin")
                };

                string currentPath = Environment.GetEnvironmentVariable("PATH") ?? "";
                psi.EnvironmentVariables["PATH"] = string.Join(":", pathAdditions) + ":" + currentPath;

                using var process = Process.Start(psi);
                if (process == null) return false;

                string output = process.StandardOutput.ReadToEnd().Trim();
                process.WaitForExit(3000);

                if (process.ExitCode == 0 && !string.IsNullOrEmpty(output) && File.Exists(output))
                {
                    fullPath = output;
                    return true;
                }
            }
            catch
            {
                // Ignore errors
            }

            return false;
        }
    }
}

```

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

```csharp
using System;
using System.Diagnostics;
using System.IO;
using System.Runtime.InteropServices;
using MCPForUnity.Editor.Dependencies.Models;
using MCPForUnity.Editor.Helpers;

namespace MCPForUnity.Editor.Dependencies.PlatformDetectors
{
    /// <summary>
    /// Linux-specific dependency detection
    /// </summary>
    public class LinuxPlatformDetector : PlatformDetectorBase
    {
        public override string PlatformName => "Linux";

        public override bool CanDetect => RuntimeInformation.IsOSPlatform(OSPlatform.Linux);

        public override DependencyStatus DetectPython()
        {
            var status = new DependencyStatus("Python", isRequired: true)
            {
                InstallationHint = GetPythonInstallUrl()
            };

            try
            {
                // Check common Python installation paths on Linux
                var candidates = new[]
                {
                    "python3",
                    "python",
                    "/usr/bin/python3",
                    "/usr/local/bin/python3",
                    "/opt/python/bin/python3",
                    "/snap/bin/python3"
                };

                foreach (var candidate in candidates)
                {
                    if (TryValidatePython(candidate, out string version, out string fullPath))
                    {
                        status.IsAvailable = true;
                        status.Version = version;
                        status.Path = fullPath;
                        status.Details = $"Found Python {version} at {fullPath}";
                        return status;
                    }
                }

                // Try PATH resolution using 'which' command
                if (TryFindInPath("python3", out string pathResult) ||
                    TryFindInPath("python", out pathResult))
                {
                    if (TryValidatePython(pathResult, out string version, out string fullPath))
                    {
                        status.IsAvailable = true;
                        status.Version = version;
                        status.Path = fullPath;
                        status.Details = $"Found Python {version} in PATH at {fullPath}";
                        return status;
                    }
                }

                status.ErrorMessage = "Python not found. Please install Python 3.10 or later.";
                status.Details = "Checked common installation paths including system, snap, and user-local locations.";
            }
            catch (Exception ex)
            {
                status.ErrorMessage = $"Error detecting Python: {ex.Message}";
            }

            return status;
        }

        public override string GetPythonInstallUrl()
        {
            return "https://www.python.org/downloads/source/";
        }

        public override string GetUVInstallUrl()
        {
            return "https://docs.astral.sh/uv/getting-started/installation/#linux";
        }

        public override string GetInstallationRecommendations()
        {
            return @"Linux Installation Recommendations:

1. Python: Install via package manager or pyenv
   - Ubuntu/Debian: sudo apt install python3 python3-pip
   - Fedora/RHEL: sudo dnf install python3 python3-pip
   - Arch: sudo pacman -S python python-pip
   - Or use pyenv: https://github.com/pyenv/pyenv

2. UV Package Manager: Install via curl
   - Run: curl -LsSf https://astral.sh/uv/install.sh | sh
   - Or download from: https://github.com/astral-sh/uv/releases

3. MCP Server: Will be installed automatically by Unity MCP Bridge

Note: Make sure ~/.local/bin is in your PATH for user-local installations.";
        }

        private bool TryValidatePython(string pythonPath, out string version, out string fullPath)
        {
            version = null;
            fullPath = null;

            try
            {
                var psi = new ProcessStartInfo
                {
                    FileName = pythonPath,
                    Arguments = "--version",
                    UseShellExecute = false,
                    RedirectStandardOutput = true,
                    RedirectStandardError = true,
                    CreateNoWindow = true
                };

                // Set PATH to include common locations
                var homeDir = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile);
                var pathAdditions = new[]
                {
                    "/usr/local/bin",
                    "/usr/bin",
                    "/bin",
                    "/snap/bin",
                    Path.Combine(homeDir, ".local", "bin")
                };

                string currentPath = Environment.GetEnvironmentVariable("PATH") ?? "";
                psi.EnvironmentVariables["PATH"] = string.Join(":", pathAdditions) + ":" + currentPath;

                using var process = Process.Start(psi);
                if (process == null) return false;

                string output = process.StandardOutput.ReadToEnd().Trim();
                process.WaitForExit(5000);

                if (process.ExitCode == 0 && output.StartsWith("Python "))
                {
                    version = output.Substring(7); // Remove "Python " prefix
                    fullPath = pythonPath;

                    // Validate minimum version (Python 4+ or Python 3.10+)
                    if (TryParseVersion(version, out var major, out var minor))
                    {
                        return major > 3 || (major >= 3 && minor >= 10);
                    }
                }
            }
            catch
            {
                // Ignore validation errors
            }

            return false;
        }

        private bool TryFindInPath(string executable, out string fullPath)
        {
            fullPath = null;

            try
            {
                var psi = new ProcessStartInfo
                {
                    FileName = "/usr/bin/which",
                    Arguments = executable,
                    UseShellExecute = false,
                    RedirectStandardOutput = true,
                    RedirectStandardError = true,
                    CreateNoWindow = true
                };

                // Enhance PATH for Unity's GUI environment
                var homeDir = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile);
                var pathAdditions = new[]
                {
                    "/usr/local/bin",
                    "/usr/bin",
                    "/bin",
                    "/snap/bin",
                    Path.Combine(homeDir, ".local", "bin")
                };

                string currentPath = Environment.GetEnvironmentVariable("PATH") ?? "";
                psi.EnvironmentVariables["PATH"] = string.Join(":", pathAdditions) + ":" + currentPath;

                using var process = Process.Start(psi);
                if (process == null) return false;

                string output = process.StandardOutput.ReadToEnd().Trim();
                process.WaitForExit(3000);

                if (process.ExitCode == 0 && !string.IsNullOrEmpty(output) && File.Exists(output))
                {
                    fullPath = output;
                    return true;
                }
            }
            catch
            {
                // Ignore errors
            }

            return false;
        }
    }
}

```

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

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

namespace MCPForUnity.Editor.Helpers
{
    /// <summary>
    /// Unity Bridge telemetry helper for collecting usage analytics
    /// Following privacy-first approach with easy opt-out mechanisms
    /// </summary>
    public static class TelemetryHelper
    {
        private const string TELEMETRY_DISABLED_KEY = "MCPForUnity.TelemetryDisabled";
        private const string CUSTOMER_UUID_KEY = "MCPForUnity.CustomerUUID";
        private static Action<Dictionary<string, object>> s_sender;

        /// <summary>
        /// Check if telemetry is enabled (can be disabled via Environment Variable or EditorPrefs)
        /// </summary>
        public static bool IsEnabled
        {
            get
            {
                // Check environment variables first
                var envDisable = Environment.GetEnvironmentVariable("DISABLE_TELEMETRY");
                if (!string.IsNullOrEmpty(envDisable) &&
                    (envDisable.ToLower() == "true" || envDisable == "1"))
                {
                    return false;
                }

                var unityMcpDisable = Environment.GetEnvironmentVariable("UNITY_MCP_DISABLE_TELEMETRY");
                if (!string.IsNullOrEmpty(unityMcpDisable) &&
                    (unityMcpDisable.ToLower() == "true" || unityMcpDisable == "1"))
                {
                    return false;
                }

                // Honor protocol-wide opt-out as well
                var mcpDisable = Environment.GetEnvironmentVariable("MCP_DISABLE_TELEMETRY");
                if (!string.IsNullOrEmpty(mcpDisable) &&
                    (mcpDisable.Equals("true", StringComparison.OrdinalIgnoreCase) || mcpDisable == "1"))
                {
                    return false;
                }

                // Check EditorPrefs
                return !UnityEditor.EditorPrefs.GetBool(TELEMETRY_DISABLED_KEY, false);
            }
        }

        /// <summary>
        /// Get or generate customer UUID for anonymous tracking
        /// </summary>
        public static string GetCustomerUUID()
        {
            var uuid = UnityEditor.EditorPrefs.GetString(CUSTOMER_UUID_KEY, "");
            if (string.IsNullOrEmpty(uuid))
            {
                uuid = System.Guid.NewGuid().ToString();
                UnityEditor.EditorPrefs.SetString(CUSTOMER_UUID_KEY, uuid);
            }
            return uuid;
        }

        /// <summary>
        /// Disable telemetry (stored in EditorPrefs)
        /// </summary>
        public static void DisableTelemetry()
        {
            UnityEditor.EditorPrefs.SetBool(TELEMETRY_DISABLED_KEY, true);
        }

        /// <summary>
        /// Enable telemetry (stored in EditorPrefs)
        /// </summary>
        public static void EnableTelemetry()
        {
            UnityEditor.EditorPrefs.SetBool(TELEMETRY_DISABLED_KEY, false);
        }

        /// <summary>
        /// Send telemetry data to MCP server for processing
        /// This is a lightweight bridge - the actual telemetry logic is in the MCP server
        /// </summary>
        public static void RecordEvent(string eventType, Dictionary<string, object> data = null)
        {
            if (!IsEnabled)
                return;

            try
            {
                var telemetryData = new Dictionary<string, object>
                {
                    ["event_type"] = eventType,
                    ["timestamp"] = DateTimeOffset.UtcNow.ToUnixTimeSeconds(),
                    ["customer_uuid"] = GetCustomerUUID(),
                    ["unity_version"] = Application.unityVersion,
                    ["platform"] = Application.platform.ToString(),
                    ["source"] = "unity_bridge"
                };

                if (data != null)
                {
                    telemetryData["data"] = data;
                }

                // Send to MCP server via existing bridge communication
                // The MCP server will handle actual telemetry transmission
                SendTelemetryToMcpServer(telemetryData);
            }
            catch (Exception e)
            {
                // Never let telemetry errors interfere with functionality
                if (IsDebugEnabled())
                {
                    McpLog.Warn($"Telemetry error (non-blocking): {e.Message}");
                }
            }
        }

        /// <summary>
        /// Allows the bridge to register a concrete sender for telemetry payloads.
        /// </summary>
        public static void RegisterTelemetrySender(Action<Dictionary<string, object>> sender)
        {
            Interlocked.Exchange(ref s_sender, sender);
        }

        public static void UnregisterTelemetrySender()
        {
            Interlocked.Exchange(ref s_sender, null);
        }

        /// <summary>
        /// Record bridge startup event
        /// </summary>
        public static void RecordBridgeStartup()
        {
            RecordEvent("bridge_startup", new Dictionary<string, object>
            {
                ["bridge_version"] = "3.0.2",
                ["auto_connect"] = MCPForUnityBridge.IsAutoConnectMode()
            });
        }

        /// <summary>
        /// Record bridge connection event
        /// </summary>
        public static void RecordBridgeConnection(bool success, string error = null)
        {
            var data = new Dictionary<string, object>
            {
                ["success"] = success
            };

            if (!string.IsNullOrEmpty(error))
            {
                data["error"] = error.Substring(0, Math.Min(200, error.Length));
            }

            RecordEvent("bridge_connection", data);
        }

        /// <summary>
        /// Record tool execution from Unity side
        /// </summary>
        public static void RecordToolExecution(string toolName, bool success, float durationMs, string error = null)
        {
            var data = new Dictionary<string, object>
            {
                ["tool_name"] = toolName,
                ["success"] = success,
                ["duration_ms"] = Math.Round(durationMs, 2)
            };

            if (!string.IsNullOrEmpty(error))
            {
                data["error"] = error.Substring(0, Math.Min(200, error.Length));
            }

            RecordEvent("tool_execution_unity", data);
        }

        private static void SendTelemetryToMcpServer(Dictionary<string, object> telemetryData)
        {
            var sender = Volatile.Read(ref s_sender);
            if (sender != null)
            {
                try
                {
                    sender(telemetryData);
                    return;
                }
                catch (Exception e)
                {
                    if (IsDebugEnabled())
                    {
                        McpLog.Warn($"Telemetry sender error (non-blocking): {e.Message}");
                    }
                }
            }

            // Fallback: log when debug is enabled
            if (IsDebugEnabled())
            {
                McpLog.Info($"Telemetry: {telemetryData["event_type"]}");
            }
        }

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

```

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

```python
from telemetry import record_telemetry, record_milestone, RecordType, MilestoneType
from mcp.server.fastmcp import FastMCP
import logging
from logging.handlers import RotatingFileHandler
import os
from contextlib import asynccontextmanager
from typing import AsyncIterator, Dict, Any
from config import config
from tools import register_all_tools
from unity_connection import get_unity_connection, UnityConnection
import time

# Configure logging using settings from config
logging.basicConfig(
    level=getattr(logging, config.log_level),
    format=config.log_format,
    stream=None,  # None -> defaults to sys.stderr; avoid stdout used by MCP stdio
    force=True    # Ensure our handler replaces any prior stdout handlers
)
logger = logging.getLogger("mcp-for-unity-server")

# Also write logs to a rotating file so logs are available when launched via stdio
try:
    import os as _os
    _log_dir = _os.path.join(_os.path.expanduser(
        "~/Library/Application Support/UnityMCP"), "Logs")
    _os.makedirs(_log_dir, exist_ok=True)
    _file_path = _os.path.join(_log_dir, "unity_mcp_server.log")
    _fh = RotatingFileHandler(
        _file_path, maxBytes=512*1024, backupCount=2, encoding="utf-8")
    _fh.setFormatter(logging.Formatter(config.log_format))
    _fh.setLevel(getattr(logging, config.log_level))
    logger.addHandler(_fh)
    # Also route telemetry logger to the same rotating file and normal level
    try:
        tlog = logging.getLogger("unity-mcp-telemetry")
        tlog.setLevel(getattr(logging, config.log_level))
        tlog.addHandler(_fh)
    except Exception:
        # Never let logging setup break startup
        pass
except Exception:
    # Never let logging setup break startup
    pass
# Quieten noisy third-party loggers to avoid clutter during stdio handshake
for noisy in ("httpx", "urllib3"):
    try:
        logging.getLogger(noisy).setLevel(
            max(logging.WARNING, getattr(logging, config.log_level)))
    except Exception:
        pass

# Import telemetry only after logging is configured to ensure its logs use stderr and proper levels
# Ensure a slightly higher telemetry timeout unless explicitly overridden by env
try:

    # Ensure generous timeout unless explicitly overridden by env
    if not os.environ.get("UNITY_MCP_TELEMETRY_TIMEOUT"):
        os.environ["UNITY_MCP_TELEMETRY_TIMEOUT"] = "5.0"
except Exception:
    pass

# Global connection state
_unity_connection: UnityConnection = None


@asynccontextmanager
async def server_lifespan(server: FastMCP) -> AsyncIterator[Dict[str, Any]]:
    """Handle server startup and shutdown."""
    global _unity_connection
    logger.info("MCP for Unity Server starting up")

    # Record server startup telemetry
    start_time = time.time()
    start_clk = time.perf_counter()
    try:
        from pathlib import Path
        ver_path = Path(__file__).parent / "server_version.txt"
        server_version = ver_path.read_text(encoding="utf-8").strip()
    except Exception:
        server_version = "unknown"
    # Defer initial telemetry by 1s to avoid stdio handshake interference
    import threading

    def _emit_startup():
        try:
            record_telemetry(RecordType.STARTUP, {
                "server_version": server_version,
                "startup_time": start_time,
            })
            record_milestone(MilestoneType.FIRST_STARTUP)
        except Exception:
            logger.debug("Deferred startup telemetry failed", exc_info=True)
    threading.Timer(1.0, _emit_startup).start()

    try:
        skip_connect = os.environ.get(
            "UNITY_MCP_SKIP_STARTUP_CONNECT", "").lower() in ("1", "true", "yes", "on")
        if skip_connect:
            logger.info(
                "Skipping Unity connection on startup (UNITY_MCP_SKIP_STARTUP_CONNECT=1)")
        else:
            _unity_connection = get_unity_connection()
            logger.info("Connected to Unity on startup")

            # Record successful Unity connection (deferred)
            import threading as _t
            _t.Timer(1.0, lambda: record_telemetry(
                RecordType.UNITY_CONNECTION,
                {
                    "status": "connected",
                    "connection_time_ms": (time.perf_counter() - start_clk) * 1000,
                }
            )).start()

    except ConnectionError as e:
        logger.warning("Could not connect to Unity on startup: %s", e)
        _unity_connection = None

        # Record connection failure (deferred)
        import threading as _t
        _err_msg = str(e)[:200]
        _t.Timer(1.0, lambda: record_telemetry(
            RecordType.UNITY_CONNECTION,
            {
                "status": "failed",
                "error": _err_msg,
                "connection_time_ms": (time.perf_counter() - start_clk) * 1000,
            }
        )).start()
    except Exception as e:
        logger.warning(
            "Unexpected error connecting to Unity on startup: %s", e)
        _unity_connection = None
        import threading as _t
        _err_msg = str(e)[:200]
        _t.Timer(1.0, lambda: record_telemetry(
            RecordType.UNITY_CONNECTION,
            {
                "status": "failed",
                "error": _err_msg,
                "connection_time_ms": (time.perf_counter() - start_clk) * 1000,
            }
        )).start()

    try:
        # Yield the connection object so it can be attached to the context
        # The key 'bridge' matches how tools like read_console expect to access it (ctx.bridge)
        yield {"bridge": _unity_connection}
    finally:
        if _unity_connection:
            _unity_connection.disconnect()
            _unity_connection = None
        logger.info("MCP for Unity Server shut down")

# Initialize MCP server
mcp = FastMCP(
    name="mcp-for-unity-server",
    lifespan=server_lifespan
)

# Register all tools
register_all_tools(mcp)

# Asset Creation Strategy


@mcp.prompt()
def asset_creation_strategy() -> str:
    """Guide for discovering and using MCP for Unity tools effectively."""
    return (
        "Available MCP for Unity Server Tools:\n\n"
        "- `manage_editor`: Controls editor state and queries info.\n"
        "- `manage_menu_item`: Executes, lists and checks for the existence of Unity Editor menu items.\n"
        "- `read_console`: Reads or clears Unity console messages, with filtering options.\n"
        "- `manage_scene`: Manages scenes.\n"
        "- `manage_gameobject`: Manages GameObjects in the scene.\n"
        "- `manage_script`: Manages C# script files.\n"
        "- `manage_asset`: Manages prefabs and assets.\n"
        "- `manage_shader`: Manages shaders.\n\n"
        "Tips:\n"
        "- Create prefabs for reusable GameObjects.\n"
        "- Always include a camera and main light in your scenes.\n"
        "- Unless specified otherwise, paths are relative to the project's `Assets/` folder.\n"
        "- After creating or modifying scripts with `manage_script`, allow Unity to recompile; use `read_console` to check for compile errors.\n"
        "- Use `manage_menu_item` for interacting with Unity systems and third party tools like a user would.\n"
        "- List menu items before using them if you are unsure of the menu path.\n"
        "- If a menu item seems missing, refresh the cache: use manage_menu_item with action='list' and refresh=true, or action='refresh'. Avoid refreshing every time; prefer refresh only when the menu set likely changed.\n"
    )


# Run the server
if __name__ == "__main__":
    mcp.run(transport='stdio')

```

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

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

namespace MCPForUnity.Editor.Helpers
{
    /// <summary>
    /// Unity Bridge telemetry helper for collecting usage analytics
    /// Following privacy-first approach with easy opt-out mechanisms
    /// </summary>
    public static class TelemetryHelper
    {
        private const string TELEMETRY_DISABLED_KEY = "MCPForUnity.TelemetryDisabled";
        private const string CUSTOMER_UUID_KEY = "MCPForUnity.CustomerUUID";
        private static Action<Dictionary<string, object>> s_sender;

        /// <summary>
        /// Check if telemetry is enabled (can be disabled via Environment Variable or EditorPrefs)
        /// </summary>
        public static bool IsEnabled
        {
            get
            {
                // Check environment variables first
                var envDisable = Environment.GetEnvironmentVariable("DISABLE_TELEMETRY");
                if (!string.IsNullOrEmpty(envDisable) &&
                    (envDisable.ToLower() == "true" || envDisable == "1"))
                {
                    return false;
                }

                var unityMcpDisable = Environment.GetEnvironmentVariable("UNITY_MCP_DISABLE_TELEMETRY");
                if (!string.IsNullOrEmpty(unityMcpDisable) &&
                    (unityMcpDisable.ToLower() == "true" || unityMcpDisable == "1"))
                {
                    return false;
                }

                // Honor protocol-wide opt-out as well
                var mcpDisable = Environment.GetEnvironmentVariable("MCP_DISABLE_TELEMETRY");
                if (!string.IsNullOrEmpty(mcpDisable) &&
                    (mcpDisable.Equals("true", StringComparison.OrdinalIgnoreCase) || mcpDisable == "1"))
                {
                    return false;
                }

                // Check EditorPrefs
                return !UnityEditor.EditorPrefs.GetBool(TELEMETRY_DISABLED_KEY, false);
            }
        }

        /// <summary>
        /// Get or generate customer UUID for anonymous tracking
        /// </summary>
        public static string GetCustomerUUID()
        {
            var uuid = UnityEditor.EditorPrefs.GetString(CUSTOMER_UUID_KEY, "");
            if (string.IsNullOrEmpty(uuid))
            {
                uuid = System.Guid.NewGuid().ToString();
                UnityEditor.EditorPrefs.SetString(CUSTOMER_UUID_KEY, uuid);
            }
            return uuid;
        }

        /// <summary>
        /// Disable telemetry (stored in EditorPrefs)
        /// </summary>
        public static void DisableTelemetry()
        {
            UnityEditor.EditorPrefs.SetBool(TELEMETRY_DISABLED_KEY, true);
        }

        /// <summary>
        /// Enable telemetry (stored in EditorPrefs)
        /// </summary>
        public static void EnableTelemetry()
        {
            UnityEditor.EditorPrefs.SetBool(TELEMETRY_DISABLED_KEY, false);
        }

        /// <summary>
        /// Send telemetry data to Python server for processing
        /// This is a lightweight bridge - the actual telemetry logic is in Python
        /// </summary>
        public static void RecordEvent(string eventType, Dictionary<string, object> data = null)
        {
            if (!IsEnabled)
                return;

            try
            {
                var telemetryData = new Dictionary<string, object>
                {
                    ["event_type"] = eventType,
                    ["timestamp"] = DateTimeOffset.UtcNow.ToUnixTimeSeconds(),
                    ["customer_uuid"] = GetCustomerUUID(),
                    ["unity_version"] = Application.unityVersion,
                    ["platform"] = Application.platform.ToString(),
                    ["source"] = "unity_bridge"
                };

                if (data != null)
                {
                    telemetryData["data"] = data;
                }

                // Send to Python server via existing bridge communication
                // The Python server will handle actual telemetry transmission
                SendTelemetryToPythonServer(telemetryData);
            }
            catch (Exception e)
            {
                // Never let telemetry errors interfere with functionality
                if (IsDebugEnabled())
                {
                    Debug.LogWarning($"Telemetry error (non-blocking): {e.Message}");
                }
            }
        }

        /// <summary>
        /// Allows the bridge to register a concrete sender for telemetry payloads.
        /// </summary>
        public static void RegisterTelemetrySender(Action<Dictionary<string, object>> sender)
        {
            Interlocked.Exchange(ref s_sender, sender);
        }

        public static void UnregisterTelemetrySender()
        {
            Interlocked.Exchange(ref s_sender, null);
        }

        /// <summary>
        /// Record bridge startup event
        /// </summary>
        public static void RecordBridgeStartup()
        {
            RecordEvent("bridge_startup", new Dictionary<string, object>
            {
                ["bridge_version"] = "3.0.2",
                ["auto_connect"] = MCPForUnityBridge.IsAutoConnectMode()
            });
        }

        /// <summary>
        /// Record bridge connection event
        /// </summary>
        public static void RecordBridgeConnection(bool success, string error = null)
        {
            var data = new Dictionary<string, object>
            {
                ["success"] = success
            };

            if (!string.IsNullOrEmpty(error))
            {
                data["error"] = error.Substring(0, Math.Min(200, error.Length));
            }

            RecordEvent("bridge_connection", data);
        }

        /// <summary>
        /// Record tool execution from Unity side
        /// </summary>
        public static void RecordToolExecution(string toolName, bool success, float durationMs, string error = null)
        {
            var data = new Dictionary<string, object>
            {
                ["tool_name"] = toolName,
                ["success"] = success,
                ["duration_ms"] = Math.Round(durationMs, 2)
            };

            if (!string.IsNullOrEmpty(error))
            {
                data["error"] = error.Substring(0, Math.Min(200, error.Length));
            }

            RecordEvent("tool_execution_unity", data);
        }

        private static void SendTelemetryToPythonServer(Dictionary<string, object> telemetryData)
        {
            var sender = Volatile.Read(ref s_sender);
            if (sender != null)
            {
                try
                {
                    sender(telemetryData);
                    return;
                }
                catch (Exception e)
                {
                    if (IsDebugEnabled())
                    {
                        Debug.LogWarning($"Telemetry sender error (non-blocking): {e.Message}");
                    }
                }
            }

            // Fallback: log when debug is enabled
            if (IsDebugEnabled())
            {
                Debug.Log($"<b><color=#2EA3FF>MCP-TELEMETRY</color></b>: {telemetryData["event_type"]}");
            }
        }

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

```

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

```csharp
using System;
using System.Diagnostics;
using System.IO;
using System.Runtime.InteropServices;
using MCPForUnity.Editor.Dependencies.Models;
using MCPForUnity.Editor.Helpers;

namespace MCPForUnity.Editor.Dependencies.PlatformDetectors
{
    /// <summary>
    /// macOS-specific dependency detection
    /// </summary>
    public class MacOSPlatformDetector : PlatformDetectorBase
    {
        public override string PlatformName => "macOS";

        public override bool CanDetect => RuntimeInformation.IsOSPlatform(OSPlatform.OSX);

        public override DependencyStatus DetectPython()
        {
            var status = new DependencyStatus("Python", isRequired: true)
            {
                InstallationHint = GetPythonInstallUrl()
            };

            try
            {
                // Check common Python installation paths on macOS
                var candidates = new[]
                {
                    "python3",
                    "python",
                    "/usr/bin/python3",
                    "/usr/local/bin/python3",
                    "/opt/homebrew/bin/python3",
                    "/Library/Frameworks/Python.framework/Versions/3.13/bin/python3",
                    "/Library/Frameworks/Python.framework/Versions/3.12/bin/python3",
                    "/Library/Frameworks/Python.framework/Versions/3.11/bin/python3",
                    "/Library/Frameworks/Python.framework/Versions/3.10/bin/python3"
                };

                foreach (var candidate in candidates)
                {
                    if (TryValidatePython(candidate, out string version, out string fullPath))
                    {
                        status.IsAvailable = true;
                        status.Version = version;
                        status.Path = fullPath;
                        status.Details = $"Found Python {version} at {fullPath}";
                        return status;
                    }
                }

                // Try PATH resolution using 'which' command
                if (TryFindInPath("python3", out string pathResult) ||
                    TryFindInPath("python", out pathResult))
                {
                    if (TryValidatePython(pathResult, out string version, out string fullPath))
                    {
                        status.IsAvailable = true;
                        status.Version = version;
                        status.Path = fullPath;
                        status.Details = $"Found Python {version} in PATH at {fullPath}";
                        return status;
                    }
                }

                status.ErrorMessage = "Python not found. Please install Python 3.10 or later.";
                status.Details = "Checked common installation paths including Homebrew, Framework, and system locations.";
            }
            catch (Exception ex)
            {
                status.ErrorMessage = $"Error detecting Python: {ex.Message}";
            }

            return status;
        }

        public override string GetPythonInstallUrl()
        {
            return "https://www.python.org/downloads/macos/";
        }

        public override string GetUVInstallUrl()
        {
            return "https://docs.astral.sh/uv/getting-started/installation/#macos";
        }

        public override string GetInstallationRecommendations()
        {
            return @"macOS Installation Recommendations:

1. Python: Install via Homebrew (recommended) or python.org
   - Homebrew: brew install python3
   - Direct download: https://python.org/downloads/macos/

2. UV Package Manager: Install via curl or Homebrew
   - Curl: curl -LsSf https://astral.sh/uv/install.sh | sh
   - Homebrew: brew install uv

3. MCP Server: Will be installed automatically by Unity MCP Bridge

Note: If using Homebrew, make sure /opt/homebrew/bin is in your PATH.";
        }

        private bool TryValidatePython(string pythonPath, out string version, out string fullPath)
        {
            version = null;
            fullPath = null;

            try
            {
                var psi = new ProcessStartInfo
                {
                    FileName = pythonPath,
                    Arguments = "--version",
                    UseShellExecute = false,
                    RedirectStandardOutput = true,
                    RedirectStandardError = true,
                    CreateNoWindow = true
                };

                // Set PATH to include common locations
                var homeDir = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile);
                var pathAdditions = new[]
                {
                    "/opt/homebrew/bin",
                    "/usr/local/bin",
                    "/usr/bin",
                    Path.Combine(homeDir, ".local", "bin")
                };

                string currentPath = Environment.GetEnvironmentVariable("PATH") ?? "";
                psi.EnvironmentVariables["PATH"] = string.Join(":", pathAdditions) + ":" + currentPath;

                using var process = Process.Start(psi);
                if (process == null) return false;

                string output = process.StandardOutput.ReadToEnd().Trim();
                process.WaitForExit(5000);

                if (process.ExitCode == 0 && output.StartsWith("Python "))
                {
                    version = output.Substring(7); // Remove "Python " prefix
                    fullPath = pythonPath;

                    // Validate minimum version (Python 4+ or Python 3.10+)
                    if (TryParseVersion(version, out var major, out var minor))
                    {
                        return major > 3 || (major >= 3 && minor >= 10);
                    }
                }
            }
            catch
            {
                // Ignore validation errors
            }

            return false;
        }

        private bool TryFindInPath(string executable, out string fullPath)
        {
            fullPath = null;

            try
            {
                var psi = new ProcessStartInfo
                {
                    FileName = "/usr/bin/which",
                    Arguments = executable,
                    UseShellExecute = false,
                    RedirectStandardOutput = true,
                    RedirectStandardError = true,
                    CreateNoWindow = true
                };

                // Enhance PATH for Unity's GUI environment
                var homeDir = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile);
                var pathAdditions = new[]
                {
                    "/opt/homebrew/bin",
                    "/usr/local/bin",
                    "/usr/bin",
                    "/bin",
                    Path.Combine(homeDir, ".local", "bin")
                };

                string currentPath = Environment.GetEnvironmentVariable("PATH") ?? "";
                psi.EnvironmentVariables["PATH"] = string.Join(":", pathAdditions) + ":" + currentPath;

                using var process = Process.Start(psi);
                if (process == null) return false;

                string output = process.StandardOutput.ReadToEnd().Trim();
                process.WaitForExit(3000);

                if (process.ExitCode == 0 && !string.IsNullOrEmpty(output) && File.Exists(output))
                {
                    fullPath = output;
                    return true;
                }
            }
            catch
            {
                // Ignore errors
            }

            return false;
        }
    }
}

```

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

```csharp
using System;
using System.Diagnostics;
using System.IO;
using System.Runtime.InteropServices;
using MCPForUnity.Editor.Dependencies.Models;
using MCPForUnity.Editor.Helpers;

namespace MCPForUnity.Editor.Dependencies.PlatformDetectors
{
    /// <summary>
    /// macOS-specific dependency detection
    /// </summary>
    public class MacOSPlatformDetector : PlatformDetectorBase
    {
        public override string PlatformName => "macOS";

        public override bool CanDetect => RuntimeInformation.IsOSPlatform(OSPlatform.OSX);

        public override DependencyStatus DetectPython()
        {
            var status = new DependencyStatus("Python", isRequired: true)
            {
                InstallationHint = GetPythonInstallUrl()
            };

            try
            {
                // Check common Python installation paths on macOS
                var candidates = new[]
                {
                    "python3",
                    "python",
                    "/usr/bin/python3",
                    "/usr/local/bin/python3",
                    "/opt/homebrew/bin/python3",
                    "/Library/Frameworks/Python.framework/Versions/3.13/bin/python3",
                    "/Library/Frameworks/Python.framework/Versions/3.12/bin/python3",
                    "/Library/Frameworks/Python.framework/Versions/3.11/bin/python3",
                    "/Library/Frameworks/Python.framework/Versions/3.10/bin/python3"
                };

                foreach (var candidate in candidates)
                {
                    if (TryValidatePython(candidate, out string version, out string fullPath))
                    {
                        status.IsAvailable = true;
                        status.Version = version;
                        status.Path = fullPath;
                        status.Details = $"Found Python {version} at {fullPath}";
                        return status;
                    }
                }

                // Try PATH resolution using 'which' command
                if (TryFindInPath("python3", out string pathResult) ||
                    TryFindInPath("python", out pathResult))
                {
                    if (TryValidatePython(pathResult, out string version, out string fullPath))
                    {
                        status.IsAvailable = true;
                        status.Version = version;
                        status.Path = fullPath;
                        status.Details = $"Found Python {version} in PATH at {fullPath}";
                        return status;
                    }
                }

                status.ErrorMessage = "Python not found. Please install Python 3.10 or later.";
                status.Details = "Checked common installation paths including Homebrew, Framework, and system locations.";
            }
            catch (Exception ex)
            {
                status.ErrorMessage = $"Error detecting Python: {ex.Message}";
            }

            return status;
        }

        public override string GetPythonInstallUrl()
        {
            return "https://www.python.org/downloads/macos/";
        }

        public override string GetUVInstallUrl()
        {
            return "https://docs.astral.sh/uv/getting-started/installation/#macos";
        }

        public override string GetInstallationRecommendations()
        {
            return @"macOS Installation Recommendations:

1. Python: Install via Homebrew (recommended) or python.org
   - Homebrew: brew install python3
   - Direct download: https://python.org/downloads/macos/

2. UV Package Manager: Install via curl or Homebrew
   - Curl: curl -LsSf https://astral.sh/uv/install.sh | sh
   - Homebrew: brew install uv

3. MCP Server: Will be installed automatically by MCP for Unity Bridge

Note: If using Homebrew, make sure /opt/homebrew/bin is in your PATH.";
        }

        private bool TryValidatePython(string pythonPath, out string version, out string fullPath)
        {
            version = null;
            fullPath = null;

            try
            {
                var psi = new ProcessStartInfo
                {
                    FileName = pythonPath,
                    Arguments = "--version",
                    UseShellExecute = false,
                    RedirectStandardOutput = true,
                    RedirectStandardError = true,
                    CreateNoWindow = true
                };

                // Set PATH to include common locations
                var homeDir = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile);
                var pathAdditions = new[]
                {
                    "/opt/homebrew/bin",
                    "/usr/local/bin",
                    "/usr/bin",
                    Path.Combine(homeDir, ".local", "bin")
                };

                string currentPath = Environment.GetEnvironmentVariable("PATH") ?? "";
                psi.EnvironmentVariables["PATH"] = string.Join(":", pathAdditions) + ":" + currentPath;

                using var process = Process.Start(psi);
                if (process == null) return false;

                string output = process.StandardOutput.ReadToEnd().Trim();
                process.WaitForExit(5000);

                if (process.ExitCode == 0 && output.StartsWith("Python "))
                {
                    version = output.Substring(7); // Remove "Python " prefix
                    fullPath = pythonPath;

                    // Validate minimum version (Python 4+ or Python 3.10+)
                    if (TryParseVersion(version, out var major, out var minor))
                    {
                        return major > 3 || (major >= 3 && minor >= 10);
                    }
                }
            }
            catch
            {
                // Ignore validation errors
            }

            return false;
        }

        private bool TryFindInPath(string executable, out string fullPath)
        {
            fullPath = null;

            try
            {
                var psi = new ProcessStartInfo
                {
                    FileName = "/usr/bin/which",
                    Arguments = executable,
                    UseShellExecute = false,
                    RedirectStandardOutput = true,
                    RedirectStandardError = true,
                    CreateNoWindow = true
                };

                // Enhance PATH for Unity's GUI environment
                var homeDir = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile);
                var pathAdditions = new[]
                {
                    "/opt/homebrew/bin",
                    "/usr/local/bin",
                    "/usr/bin",
                    "/bin",
                    Path.Combine(homeDir, ".local", "bin")
                };

                string currentPath = Environment.GetEnvironmentVariable("PATH") ?? "";
                psi.EnvironmentVariables["PATH"] = string.Join(":", pathAdditions) + ":" + currentPath;

                using var process = Process.Start(psi);
                if (process == null) return false;

                string output = process.StandardOutput.ReadToEnd().Trim();
                process.WaitForExit(3000);

                if (process.ExitCode == 0 && !string.IsNullOrEmpty(output) && File.Exists(output))
                {
                    fullPath = output;
                    return true;
                }
            }
            catch
            {
                // Ignore errors
            }

            return false;
        }
    }
}

```

--------------------------------------------------------------------------------
/MCPForUnity/UnityMcpServer~/src/tools/manage_gameobject.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="Manage GameObjects. Note: for 'get_components', the `data` field contains a dictionary of component names and their serialized properties. For 'get_component', specify 'component_name' to retrieve only that component's serialized data."
)
def manage_gameobject(
    ctx: Context,
    action: Annotated[Literal["create", "modify", "delete", "find", "add_component", "remove_component", "set_component_property", "get_components", "get_component"], "Perform CRUD operations on GameObjects and components."],
    target: Annotated[str,
                      "GameObject identifier by name or path for modify/delete/component actions"] | None = None,
    search_method: Annotated[Literal["by_id", "by_name", "by_path", "by_tag", "by_layer", "by_component"],
                             "How to find objects. Used with 'find' and some 'target' lookups."] | None = None,
    name: Annotated[str,
                    "GameObject name for 'create' (initial name) and 'modify' (rename) actions ONLY. For 'find' action, use 'search_term' instead."] | None = None,
    tag: Annotated[str,
                   "Tag name - used for both 'create' (initial tag) and 'modify' (change tag)"] | None = None,
    parent: Annotated[str,
                      "Parent GameObject reference - used for both 'create' (initial parent) and 'modify' (change parent)"] | None = None,
    position: Annotated[list[float],
                        "Position - used for both 'create' (initial position) and 'modify' (change position)"] | None = None,
    rotation: Annotated[list[float],
                        "Rotation - used for both 'create' (initial rotation) and 'modify' (change rotation)"] | None = None,
    scale: Annotated[list[float],
                     "Scale - used for both 'create' (initial scale) and 'modify' (change scale)"] | None = None,
    components_to_add: Annotated[list[str],
                                 "List of component names to add"] | None = None,
    primitive_type: Annotated[str,
                              "Primitive type for 'create' action"] | None = None,
    save_as_prefab: Annotated[bool,
                              "If True, saves the created GameObject as a prefab"] | None = None,
    prefab_path: Annotated[str, "Path for prefab creation"] | None = None,
    prefab_folder: Annotated[str,
                             "Folder for prefab creation"] | None = None,
    # --- Parameters for 'modify' ---
    set_active: Annotated[bool,
                          "If True, sets the GameObject active"] | None = None,
    layer: Annotated[str, "Layer name"] | None = None,
    components_to_remove: Annotated[list[str],
                                    "List of component names to remove"] | None = None,
    component_properties: Annotated[dict[str, dict[str, Any]],
                                    """Dictionary of component names to their properties to set. For example:
                                    `{"MyScript": {"otherObject": {"find": "Player", "method": "by_name"}}}` assigns GameObject
                                    `{"MyScript": {"playerHealth": {"find": "Player", "component": "HealthComponent"}}}` assigns Component
                                    Example set nested property:
                                    - Access shared material: `{"MeshRenderer": {"sharedMaterial.color": [1, 0, 0, 1]}}`"""] | None = None,
    # --- Parameters for 'find' ---
    search_term: Annotated[str,
                           "Search term for 'find' action ONLY. Use this (not 'name') when searching for GameObjects."] | None = None,
    find_all: Annotated[bool,
                        "If True, finds all GameObjects matching the search term"] | None = None,
    search_in_children: Annotated[bool,
                                  "If True, searches in children of the GameObject"] | None = None,
    search_inactive: Annotated[bool,
                               "If True, searches inactive GameObjects"] | None = None,
    # -- Component Management Arguments --
    component_name: Annotated[str,
                              "Component name for 'add_component' and 'remove_component' actions"] | None = None,
    # Controls whether serialization of private [SerializeField] fields is included
    includeNonPublicSerialized: Annotated[bool,
                                          "Controls whether serialization of private [SerializeField] fields is included"] | None = None,
) -> dict[str, Any]:
    ctx.info(f"Processing manage_gameobject: {action}")
    try:
        # Validate parameter usage to prevent silent failures
        if action == "find":
            if name is not None:
                return {
                    "success": False,
                    "message": "For 'find' action, use 'search_term' parameter, not 'name'. Remove 'name' parameter. Example: search_term='Player', search_method='by_name'"
                }
            if search_term is None:
                return {
                    "success": False,
                    "message": "For 'find' action, 'search_term' parameter is required. Use search_term (not 'name') to specify what to find."
                }

        if action in ["create", "modify"]:
            if search_term is not None:
                return {
                    "success": False,
                    "message": f"For '{action}' action, use 'name' parameter, not 'search_term'."
                }

        # Prepare parameters, removing None values
        params = {
            "action": action,
            "target": target,
            "searchMethod": search_method,
            "name": name,
            "tag": tag,
            "parent": parent,
            "position": position,
            "rotation": rotation,
            "scale": scale,
            "componentsToAdd": components_to_add,
            "primitiveType": primitive_type,
            "saveAsPrefab": save_as_prefab,
            "prefabPath": prefab_path,
            "prefabFolder": prefab_folder,
            "setActive": set_active,
            "layer": layer,
            "componentsToRemove": components_to_remove,
            "componentProperties": component_properties,
            "searchTerm": search_term,
            "findAll": find_all,
            "searchInChildren": search_in_children,
            "searchInactive": search_inactive,
            "componentName": component_name,
            "includeNonPublicSerialized": includeNonPublicSerialized
        }
        params = {k: v for k, v in params.items() if v is not None}

        # --- Handle Prefab Path Logic ---
        # Check if 'saveAsPrefab' is explicitly True in params
        if action == "create" and params.get("saveAsPrefab"):
            if "prefabPath" not in params:
                if "name" not in params or not params["name"]:
                    return {"success": False, "message": "Cannot create default prefab path: 'name' parameter is missing."}
                # Use the provided prefab_folder (which has a default) and the name to construct the path
                constructed_path = f"{prefab_folder}/{params['name']}.prefab"
                # Ensure clean path separators (Unity prefers '/')
                params["prefabPath"] = constructed_path.replace("\\", "/")
            elif not params["prefabPath"].lower().endswith(".prefab"):
                return {"success": False, "message": f"Invalid prefab_path: '{params['prefabPath']}' must end with .prefab"}
        # Ensure prefabFolder itself isn't sent if prefabPath was constructed or provided
        # The C# side only needs the final prefabPath
        params.pop("prefabFolder", None)
        # --------------------------------

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

        # Check if the response indicates success
        # If the response is not successful, raise an exception with the error message
        if isinstance(response, dict) and response.get("success"):
            return {"success": True, "message": response.get("message", "GameObject 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 GameObject: {str(e)}"}
```

--------------------------------------------------------------------------------
/UnityMcpBridge/UnityMcpServer~/src/tools/manage_gameobject.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="Manage GameObjects. Note: for 'get_components', the `data` field contains a dictionary of component names and their serialized properties. For 'get_component', specify 'component_name' to retrieve only that component's serialized data."
)
def manage_gameobject(
    ctx: Context,
    action: Annotated[Literal["create", "modify", "delete", "find", "add_component", "remove_component", "set_component_property", "get_components", "get_component"], "Perform CRUD operations on GameObjects and components."],
    target: Annotated[str,
                      "GameObject identifier by name or path for modify/delete/component actions"] | None = None,
    search_method: Annotated[Literal["by_id", "by_name", "by_path", "by_tag", "by_layer", "by_component"],
                             "How to find objects. Used with 'find' and some 'target' lookups."] | None = None,
    name: Annotated[str,
                    "GameObject name for 'create' (initial name) and 'modify' (rename) actions ONLY. For 'find' action, use 'search_term' instead."] | None = None,
    tag: Annotated[str,
                   "Tag name - used for both 'create' (initial tag) and 'modify' (change tag)"] | None = None,
    parent: Annotated[str,
                      "Parent GameObject reference - used for both 'create' (initial parent) and 'modify' (change parent)"] | None = None,
    position: Annotated[list[float],
                        "Position - used for both 'create' (initial position) and 'modify' (change position)"] | None = None,
    rotation: Annotated[list[float],
                        "Rotation - used for both 'create' (initial rotation) and 'modify' (change rotation)"] | None = None,
    scale: Annotated[list[float],
                     "Scale - used for both 'create' (initial scale) and 'modify' (change scale)"] | None = None,
    components_to_add: Annotated[list[str],
                                 "List of component names to add"] | None = None,
    primitive_type: Annotated[str,
                              "Primitive type for 'create' action"] | None = None,
    save_as_prefab: Annotated[bool,
                              "If True, saves the created GameObject as a prefab"] | None = None,
    prefab_path: Annotated[str, "Path for prefab creation"] | None = None,
    prefab_folder: Annotated[str,
                             "Folder for prefab creation"] | None = None,
    # --- Parameters for 'modify' ---
    set_active: Annotated[bool,
                          "If True, sets the GameObject active"] | None = None,
    layer: Annotated[str, "Layer name"] | None = None,
    components_to_remove: Annotated[list[str],
                                    "List of component names to remove"] | None = None,
    component_properties: Annotated[dict[str, dict[str, Any]],
                                    """Dictionary of component names to their properties to set. For example:
                                    `{"MyScript": {"otherObject": {"find": "Player", "method": "by_name"}}}` assigns GameObject
                                    `{"MyScript": {"playerHealth": {"find": "Player", "component": "HealthComponent"}}}` assigns Component
                                    Example set nested property:
                                    - Access shared material: `{"MeshRenderer": {"sharedMaterial.color": [1, 0, 0, 1]}}`"""] | None = None,
    # --- Parameters for 'find' ---
    search_term: Annotated[str,
                           "Search term for 'find' action ONLY. Use this (not 'name') when searching for GameObjects."] | None = None,
    find_all: Annotated[bool,
                        "If True, finds all GameObjects matching the search term"] | None = None,
    search_in_children: Annotated[bool,
                                  "If True, searches in children of the GameObject"] | None = None,
    search_inactive: Annotated[bool,
                               "If True, searches inactive GameObjects"] | None = None,
    # -- Component Management Arguments --
    component_name: Annotated[str,
                              "Component name for 'add_component' and 'remove_component' actions"] | None = None,
    # Controls whether serialization of private [SerializeField] fields is included
    includeNonPublicSerialized: Annotated[bool,
                                          "Controls whether serialization of private [SerializeField] fields is included"] | None = None,
) -> dict[str, Any]:
    ctx.info(f"Processing manage_gameobject: {action}")
    try:
        # Validate parameter usage to prevent silent failures
        if action == "find":
            if name is not None:
                return {
                    "success": False,
                    "message": "For 'find' action, use 'search_term' parameter, not 'name'. Remove 'name' parameter. Example: search_term='Player', search_method='by_name'"
                }
            if search_term is None:
                return {
                    "success": False,
                    "message": "For 'find' action, 'search_term' parameter is required. Use search_term (not 'name') to specify what to find."
                }

        if action in ["create", "modify"]:
            if search_term is not None:
                return {
                    "success": False,
                    "message": f"For '{action}' action, use 'name' parameter, not 'search_term'."
                }

        # Prepare parameters, removing None values
        params = {
            "action": action,
            "target": target,
            "searchMethod": search_method,
            "name": name,
            "tag": tag,
            "parent": parent,
            "position": position,
            "rotation": rotation,
            "scale": scale,
            "componentsToAdd": components_to_add,
            "primitiveType": primitive_type,
            "saveAsPrefab": save_as_prefab,
            "prefabPath": prefab_path,
            "prefabFolder": prefab_folder,
            "setActive": set_active,
            "layer": layer,
            "componentsToRemove": components_to_remove,
            "componentProperties": component_properties,
            "searchTerm": search_term,
            "findAll": find_all,
            "searchInChildren": search_in_children,
            "searchInactive": search_inactive,
            "componentName": component_name,
            "includeNonPublicSerialized": includeNonPublicSerialized
        }
        params = {k: v for k, v in params.items() if v is not None}

        # --- Handle Prefab Path Logic ---
        # Check if 'saveAsPrefab' is explicitly True in params
        if action == "create" and params.get("saveAsPrefab"):
            if "prefabPath" not in params:
                if "name" not in params or not params["name"]:
                    return {"success": False, "message": "Cannot create default prefab path: 'name' parameter is missing."}
                # Use the provided prefab_folder (which has a default) and the name to construct the path
                constructed_path = f"{prefab_folder}/{params['name']}.prefab"
                # Ensure clean path separators (Unity prefers '/')
                params["prefabPath"] = constructed_path.replace("\\", "/")
            elif not params["prefabPath"].lower().endswith(".prefab"):
                return {"success": False, "message": f"Invalid prefab_path: '{params['prefabPath']}' must end with .prefab"}
        # Ensure prefabFolder itself isn't sent if prefabPath was constructed or provided
        # The C# side only needs the final prefabPath
        params.pop("prefabFolder", None)
        # --------------------------------

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

        # Check if the response indicates success
        # If the response is not successful, raise an exception with the error message
        if isinstance(response, dict) and response.get("success"):
            return {"success": True, "message": response.get("message", "GameObject 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 GameObject: {str(e)}"}
```

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

```csharp
using System.IO;
using Newtonsoft.Json.Linq;
using NUnit.Framework;
using UnityEditor;
using UnityEditor.SceneManagement;
using UnityEngine;
using MCPForUnity.Editor.Tools.Prefabs;
using MCPForUnity.Editor.Tools;

namespace MCPForUnityTests.Editor.Tools
{
    public class ManagePrefabsTests
    {
        private const string TempDirectory = "Assets/Temp/ManagePrefabsTests";

        [SetUp]
        public void SetUp()
        {
            StageUtility.GoToMainStage();
            EnsureTempDirectoryExists();
        }

        [TearDown]
        public void TearDown()
        {
            StageUtility.GoToMainStage();
        }

        [OneTimeTearDown]
        public void CleanupAll()
        {
            StageUtility.GoToMainStage();
            if (AssetDatabase.IsValidFolder(TempDirectory))
            {
                AssetDatabase.DeleteAsset(TempDirectory);
            }
        }

        [Test]
        public void OpenStage_OpensPrefabInIsolation()
        {
            string prefabPath = CreateTestPrefab("OpenStageCube");

            try
            {
                var openParams = new JObject
                {
                    ["action"] = "open_stage",
                    ["prefabPath"] = prefabPath
                };

                var openResult = ToJObject(ManagePrefabs.HandleCommand(openParams));

                Assert.IsTrue(openResult.Value<bool>("success"), "open_stage should succeed for a valid prefab.");

                PrefabStage stage = PrefabStageUtility.GetCurrentPrefabStage();
                Assert.IsNotNull(stage, "Prefab stage should be open after open_stage.");
                Assert.AreEqual(prefabPath, stage.assetPath, "Opened stage should match prefab path.");

                var stageInfo = ToJObject(ManageEditor.HandleCommand(new JObject { ["action"] = "get_prefab_stage" }));
                Assert.IsTrue(stageInfo.Value<bool>("success"), "get_prefab_stage should succeed when stage is open.");

                var data = stageInfo["data"] as JObject;
                Assert.IsNotNull(data, "Stage info should include data payload.");
                Assert.IsTrue(data.Value<bool>("isOpen"));
                Assert.AreEqual(prefabPath, data.Value<string>("assetPath"));
            }
            finally
            {
                StageUtility.GoToMainStage();
                AssetDatabase.DeleteAsset(prefabPath);
            }
        }

        [Test]
        public void CloseStage_ReturnsSuccess_WhenNoStageOpen()
        {
            StageUtility.GoToMainStage();
            var closeResult = ToJObject(ManagePrefabs.HandleCommand(new JObject
            {
                ["action"] = "close_stage"
            }));

            Assert.IsTrue(closeResult.Value<bool>("success"), "close_stage should succeed even if no stage is open.");
        }

        [Test]
        public void CloseStage_ClosesOpenPrefabStage()
        {
            string prefabPath = CreateTestPrefab("CloseStageCube");

            try
            {
                ManagePrefabs.HandleCommand(new JObject
                {
                    ["action"] = "open_stage",
                    ["prefabPath"] = prefabPath
                });

                var closeResult = ToJObject(ManagePrefabs.HandleCommand(new JObject
                {
                    ["action"] = "close_stage"
                }));

                Assert.IsTrue(closeResult.Value<bool>("success"), "close_stage should succeed when stage is open.");
                Assert.IsNull(PrefabStageUtility.GetCurrentPrefabStage(), "Prefab stage should be closed after close_stage.");
            }
            finally
            {
                StageUtility.GoToMainStage();
                AssetDatabase.DeleteAsset(prefabPath);
            }
        }

        [Test]
        public void SaveOpenStage_SavesDirtyChanges()
        {
            string prefabPath = CreateTestPrefab("SaveStageCube");

            try
            {
                ManagePrefabs.HandleCommand(new JObject
                {
                    ["action"] = "open_stage",
                    ["prefabPath"] = prefabPath
                });

                PrefabStage stage = PrefabStageUtility.GetCurrentPrefabStage();
                Assert.IsNotNull(stage, "Stage should be open before modifying.");

                stage.prefabContentsRoot.transform.localScale = new Vector3(2f, 2f, 2f);

                var saveResult = ToJObject(ManagePrefabs.HandleCommand(new JObject
                {
                    ["action"] = "save_open_stage"
                }));

                Assert.IsTrue(saveResult.Value<bool>("success"), "save_open_stage should succeed when stage is open.");
                Assert.IsFalse(stage.scene.isDirty, "Stage scene should not be dirty after saving.");

                GameObject reloaded = AssetDatabase.LoadAssetAtPath<GameObject>(prefabPath);
                Assert.AreEqual(new Vector3(2f, 2f, 2f), reloaded.transform.localScale, "Saved prefab asset should include changes from open stage.");
            }
            finally
            {
                StageUtility.GoToMainStage();
                AssetDatabase.DeleteAsset(prefabPath);
            }
        }

        [Test]
        public void SaveOpenStage_ReturnsError_WhenNoStageOpen()
        {
            StageUtility.GoToMainStage();

            var saveResult = ToJObject(ManagePrefabs.HandleCommand(new JObject
            {
                ["action"] = "save_open_stage"
            }));

            Assert.IsFalse(saveResult.Value<bool>("success"), "save_open_stage should fail when no stage is open.");
        }

        [Test]
        public void CreateFromGameObject_CreatesPrefabAndLinksInstance()
        {
            EnsureTempDirectoryExists();
            StageUtility.GoToMainStage();

            string prefabPath = Path.Combine(TempDirectory, "SceneObjectSaved.prefab").Replace('\\', '/');
            GameObject sceneObject = new GameObject("ScenePrefabSource");

            try
            {
                var result = ToJObject(ManagePrefabs.HandleCommand(new JObject
                {
                    ["action"] = "create_from_gameobject",
                    ["target"] = sceneObject.name,
                    ["prefabPath"] = prefabPath
                }));

                Assert.IsTrue(result.Value<bool>("success"), "create_from_gameobject should succeed for a valid scene object.");

                var data = result["data"] as JObject;
                Assert.IsNotNull(data, "Response data should include prefab information.");

                string savedPath = data.Value<string>("prefabPath");
                Assert.AreEqual(prefabPath, savedPath, "Returned prefab path should match the requested path.");

                GameObject prefabAsset = AssetDatabase.LoadAssetAtPath<GameObject>(savedPath);
                Assert.IsNotNull(prefabAsset, "Prefab asset should exist at the saved path.");

                int instanceId = data.Value<int>("instanceId");
                var linkedInstance = EditorUtility.InstanceIDToObject(instanceId) as GameObject;
                Assert.IsNotNull(linkedInstance, "Linked instance should resolve from instanceId.");
                Assert.AreEqual(savedPath, PrefabUtility.GetPrefabAssetPathOfNearestInstanceRoot(linkedInstance), "Instance should be connected to the new prefab.");

                sceneObject = linkedInstance;
            }
            finally
            {
                if (AssetDatabase.LoadAssetAtPath<UnityEngine.Object>(prefabPath) != null)
                {
                    AssetDatabase.DeleteAsset(prefabPath);
                }

                if (sceneObject != null)
                {
                    if (PrefabUtility.IsPartOfPrefabInstance(sceneObject))
                    {
                        PrefabUtility.UnpackPrefabInstance(
                            sceneObject,
                            PrefabUnpackMode.Completely,
                            InteractionMode.AutomatedAction
                        );
                    }
                    UnityEngine.Object.DestroyImmediate(sceneObject, true);
                }
            }
        }

        private static string CreateTestPrefab(string name)
        {
            EnsureTempDirectoryExists();

            GameObject temp = GameObject.CreatePrimitive(PrimitiveType.Cube);
            temp.name = name;

            string path = Path.Combine(TempDirectory, name + ".prefab").Replace('\\', '/');
            PrefabUtility.SaveAsPrefabAsset(temp, path, out bool success);
            UnityEngine.Object.DestroyImmediate(temp);

            Assert.IsTrue(success, "PrefabUtility.SaveAsPrefabAsset should succeed for test prefab.");
            return path;
        }

        private static void EnsureTempDirectoryExists()
        {
            if (!AssetDatabase.IsValidFolder("Assets/Temp"))
            {
                AssetDatabase.CreateFolder("Assets", "Temp");
            }

            if (!AssetDatabase.IsValidFolder(TempDirectory))
            {
                AssetDatabase.CreateFolder("Assets/Temp", "ManagePrefabsTests");
            }
        }

        private static JObject ToJObject(object result)
        {
            return result as JObject ?? JObject.FromObject(result);
        }
    }
}

```

--------------------------------------------------------------------------------
/.claude/prompts/nl-unity-suite-nl.md:
--------------------------------------------------------------------------------

```markdown
# Unity NL Editing Suite — Additive Test Design

You are running inside CI for the `unity-mcp` repo. Use only the tools allowed by the workflow. Work autonomously; do not prompt the user. Do NOT spawn subagents.

**Print this once, verbatim, early in the run:**
AllowedTools: Write,mcp__unity__manage_editor,mcp__unity__list_resources,mcp__unity__read_resource,mcp__unity__apply_text_edits,mcp__unity__script_apply_edits,mcp__unity__validate_script,mcp__unity__find_in_file,mcp__unity__read_console,mcp__unity__get_sha

---

## Mission
1) Pick target file (prefer):
   - `unity://path/Assets/Scripts/LongUnityScriptClaudeTest.cs`
2) Execute NL tests NL-0..NL-4 in order using minimal, precise edits that build on each other.
3) Validate each edit with `mcp__unity__validate_script(level:"standard")`.
4) **Report**: write one `<testcase>` XML fragment per test to `reports/<TESTID>_results.xml`. Do **not** read or edit `$JUNIT_OUT`.

**CRITICAL XML FORMAT REQUIREMENTS:**
- Each file must contain EXACTLY one `<testcase>` root element
- NO prologue, epilogue, code fences, or extra characters
- NO markdown formatting or explanations outside the XML
- Use this exact format:

```xml
<testcase name="NL-0 — Baseline State Capture" classname="UnityMCP.NL-T">
  <system-out><![CDATA[
(evidence of what was accomplished)
  ]]></system-out>
</testcase>
```

- If test fails, include: `<failure message="reason"/>`
- TESTID must be one of: NL-0, NL-1, NL-2, NL-3, NL-4
5) **NO RESTORATION** - tests build additively on previous state.
6) **STRICT FRAGMENT EMISSION** - After each test, immediately emit a clean XML file under `reports/<TESTID>_results.xml` with exactly one `<testcase>` whose `name` begins with the exact test id. No prologue/epilogue or fences. If the test fails, include a `<failure message="..."/>` and still emit.

---

## Environment & Paths (CI)
- Always pass: `project_root: "TestProjects/UnityMCPTests"` and `ctx: {}` on list/read/edit/validate.
- **Canonical URIs only**:
  - Primary: `unity://path/Assets/...` (never embed `project_root` in the URI)
  - Relative (when supported): `Assets/...`

CI provides:
- `$JUNIT_OUT=reports/junit-nl-suite.xml` (pre‑created; leave alone)
- `$MD_OUT=reports/junit-nl-suite.md` (synthesized from JUnit)

---

## Transcript Minimization Rules
- Do not restate tool JSON; summarize in ≤ 2 short lines.
- Never paste full file contents. For matches, include only the matched line and ±1 line.
- Prefer `mcp__unity__find_in_file` for targeting; avoid `mcp__unity__read_resource` unless strictly necessary. If needed, limit to `head_bytes ≤ 256` or `tail_lines ≤ 10`.
- Per‑test `system-out` ≤ 400 chars: brief status only (no SHA).
- Console evidence: fetch the last 10 lines with `include_stacktrace:false` and include ≤ 3 lines in the fragment.
- Avoid quoting multi‑line diffs; reference markers instead.
— Console scans: perform two reads — last 10 `log/info` lines and up to 3 `error` entries (use `include_stacktrace:false`); include ≤ 3 lines total in the fragment; if no errors, state "no errors".

---

## Tool Mapping
- **Anchors/regex/structured**: `mcp__unity__script_apply_edits`
  - Allowed ops: `anchor_insert`, `replace_method`, `insert_method`, `delete_method`, `regex_replace`
  - For `anchor_insert`, always set `"position": "before"` or `"after"`.
- **Precise ranges / atomic batch**: `mcp__unity__apply_text_edits` (non‑overlapping ranges)
STRICT OP GUARDRAILS
- Do not use `anchor_replace`. Structured edits must be one of: `anchor_insert`, `replace_method`, `insert_method`, `delete_method`, `regex_replace`.
- For multi‑spot textual tweaks in one operation, compute non‑overlapping ranges with `mcp__unity__find_in_file` and use `mcp__unity__apply_text_edits`.

- **Hash-only**: `mcp__unity__get_sha` — returns `{sha256,lengthBytes,lastModifiedUtc}` without file body
- **Validation**: `mcp__unity__validate_script(level:"standard")`
- **Dynamic targeting**: Use `mcp__unity__find_in_file` to locate current positions of methods/markers

---

## Additive Test Design Principles

**Key Changes from Reset-Based:**
1. **Dynamic Targeting**: Use `find_in_file` to locate methods/content, never hardcode line numbers
2. **State Awareness**: Each test expects the file state left by the previous test
3. **Content-Based Operations**: Target methods by signature, classes by name, not coordinates
4. **Cumulative Validation**: Ensure the file remains structurally sound throughout the sequence
5. **Composability**: Tests demonstrate how operations work together in real workflows

**State Tracking:**
- Track file SHA after each test (`mcp__unity__get_sha`) for potential preconditions in later passes. Do not include SHA values in report fragments.
- Use content signatures (method names, comment markers) to verify expected state
- Validate structural integrity after each major change

---

## Execution Order & Additive Test Specs

### NL-0. Baseline State Capture
**Goal**: Establish initial file state and verify accessibility
**Actions**:
- Read file head and tail to confirm structure
- Locate key methods: `HasTarget()`, `GetCurrentTarget()`, `Update()`, `ApplyBlend()`
- Record initial SHA for tracking
- **Expected final state**: Unchanged baseline file

### NL-1. Core Method Operations (Additive State A)
**Goal**: Demonstrate method replacement operations
**Actions**: 
- Replace `HasTarget()` method body: `public bool HasTarget() { return currentTarget != null; }`
- Insert `PrintSeries()` method after `GetCurrentTarget()`: `public void PrintSeries() { Debug.Log("1,2,3"); }`
- Verify both methods exist and are properly formatted
- Delete `PrintSeries()` method (cleanup for next test)
- **Expected final state**: `HasTarget()` modified, file structure intact, no temporary methods

### NL-2. Anchor Comment Insertion (Additive State B) 
**Goal**: Demonstrate anchor-based insertions above methods
**Actions**:
- Use `find_in_file` to locate current position of `Update()` method
- Insert `// Build marker OK` comment line above `Update()` method
- Verify comment exists and `Update()` still functions
- **Expected final state**: State A + build marker comment above `Update()`

### NL-3. End-of-Class Content (Additive State C)
**Goal**: Demonstrate end-of-class insertions with smart brace matching
**Actions**:
- Match the final class-closing brace by scanning from EOF (e.g., last `^\s*}\s*$`)
  or compute via `find_in_file` + ranges; insert immediately before it.
- Insert three comment lines before final class brace:
  ```
  // Tail test A
  // Tail test B  
  // Tail test C
  ```
- **Expected final state**: State B + tail comments before class closing brace

### NL-4. Console State Verification (No State Change)
**Goal**: Verify Unity console integration without file modification
**Actions**:
- Read last 10 Unity console lines (log/info)
- Perform a targeted scan for errors/exceptions (type: errors), up to 3 entries
- Validate no compilation errors from previous operations
- **Expected final state**: State C (unchanged)
- **IMMEDIATELY** write clean XML fragment to `reports/NL-4_results.xml` (no extra text). The `<testcase name>` must start with `NL-4`. Include at most 3 lines total across both reads, or simply state "no errors; console OK" (≤ 400 chars).

## Dynamic Targeting Examples

**Instead of hardcoded coordinates:**
```json
{"startLine": 31, "startCol": 26, "endLine": 31, "endCol": 58}
```

**Use content-aware targeting:**
```json
# Find current method location
find_in_file(pattern: "public bool HasTarget\\(\\)")
# Then compute edit ranges from found position
```

**Method targeting by signature:**
```json
{"op": "replace_method", "className": "LongUnityScriptClaudeTest", "methodName": "HasTarget"}
```

**Anchor-based insertions:**
```json  
{"op": "anchor_insert", "anchor": "private void Update\\(\\)", "position": "before", "text": "// comment"}
```

---

## State Verification Patterns

**After each test:**
1. Verify expected content exists: `find_in_file` for key markers
2. Check structural integrity: `validate_script(level:"standard")`  
3. Update SHA tracking for next test's preconditions
4. Emit a per‑test fragment to `reports/<TESTID>_results.xml` immediately. If the test failed, still write a single `<testcase>` with a `<failure message="..."/>` and evidence in `system-out`.
5. Log cumulative changes in test evidence (keep concise per Transcript Minimization Rules; never paste raw tool JSON)

**Error Recovery:**
- If test fails, log current state but continue (don't restore)
- Next test adapts to actual current state, not expected state
- Demonstrates resilience of operations on varied file conditions

---

## Benefits of Additive Design

1. **Realistic Workflows**: Tests mirror actual development patterns
2. **Robust Operations**: Proves edits work on evolving files, not just pristine baselines  
3. **Composability Validation**: Shows operations coordinate well together
4. **Simplified Infrastructure**: No restore scripts or snapshots needed
5. **Better Failure Analysis**: Failures don't cascade - each test adapts to current reality
6. **State Evolution Testing**: Validates SDK handles cumulative file modifications correctly

This additive approach produces a more realistic and maintainable test suite that better represents actual SDK usage patterns.

---

BAN ON EXTRA TOOLS AND DIRS
- Do not use any tools outside `AllowedTools`. Do not create directories; assume `reports/` exists.

---


```

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

```csharp
using System;
using System.Diagnostics;
using System.IO;
using MCPForUnity.Editor.Helpers;
using UnityEditor;
using UnityEngine;

namespace MCPForUnity.Editor.Services
{
    /// <summary>
    /// Implementation of path resolver service with override support
    /// </summary>
    public class PathResolverService : IPathResolverService
    {
        private const string PythonDirOverrideKey = "MCPForUnity.PythonDirOverride";
        private const string UvPathOverrideKey = "MCPForUnity.UvPath";
        private const string ClaudeCliPathOverrideKey = "MCPForUnity.ClaudeCliPath";

        public bool HasMcpServerOverride => !string.IsNullOrEmpty(EditorPrefs.GetString(PythonDirOverrideKey, null));
        public bool HasUvPathOverride => !string.IsNullOrEmpty(EditorPrefs.GetString(UvPathOverrideKey, null));
        public bool HasClaudeCliPathOverride => !string.IsNullOrEmpty(EditorPrefs.GetString(ClaudeCliPathOverrideKey, null));

        public string GetMcpServerPath()
        {
            // Check for override first
            string overridePath = EditorPrefs.GetString(PythonDirOverrideKey, null);
            if (!string.IsNullOrEmpty(overridePath) && File.Exists(Path.Combine(overridePath, "server.py")))
            {
                return overridePath;
            }

            // Fall back to automatic detection
            return McpPathResolver.FindPackagePythonDirectory(false);
        }

        public string GetUvPath()
        {
            // Check for override first
            string overridePath = EditorPrefs.GetString(UvPathOverrideKey, null);
            if (!string.IsNullOrEmpty(overridePath) && File.Exists(overridePath))
            {
                return overridePath;
            }

            // Fall back to automatic detection
            try
            {
                return ServerInstaller.FindUvPath();
            }
            catch
            {
                return null;
            }
        }

        public string GetClaudeCliPath()
        {
            // Check for override first
            string overridePath = EditorPrefs.GetString(ClaudeCliPathOverrideKey, null);
            if (!string.IsNullOrEmpty(overridePath) && File.Exists(overridePath))
            {
                return overridePath;
            }

            // Fall back to automatic detection
            return ExecPath.ResolveClaude();
        }

        public bool IsPythonDetected()
        {
            try
            {
                // Windows-specific Python detection
                if (Application.platform == RuntimePlatform.WindowsEditor)
                {
                    // Common Windows Python installation paths
                    string[] windowsCandidates =
                    {
                        @"C:\Python313\python.exe",
                        @"C:\Python312\python.exe",
                        @"C:\Python311\python.exe",
                        @"C:\Python310\python.exe",
                        @"C:\Python39\python.exe",
                        Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), @"Programs\Python\Python313\python.exe"),
                        Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), @"Programs\Python\Python312\python.exe"),
                        Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), @"Programs\Python\Python311\python.exe"),
                        Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), @"Programs\Python\Python310\python.exe"),
                        Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), @"Programs\Python\Python39\python.exe"),
                        Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles), @"Python313\python.exe"),
                        Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles), @"Python312\python.exe"),
                        Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles), @"Python311\python.exe"),
                        Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles), @"Python310\python.exe"),
                        Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles), @"Python39\python.exe"),
                    };

                    foreach (string c in windowsCandidates)
                    {
                        if (File.Exists(c)) return true;
                    }

                    // Try 'where python' command (Windows equivalent of 'which')
                    var psi = new ProcessStartInfo
                    {
                        FileName = "where",
                        Arguments = "python",
                        UseShellExecute = false,
                        RedirectStandardOutput = true,
                        RedirectStandardError = true,
                        CreateNoWindow = true
                    };
                    using (var p = Process.Start(psi))
                    {
                        string outp = p.StandardOutput.ReadToEnd().Trim();
                        p.WaitForExit(2000);
                        if (p.ExitCode == 0 && !string.IsNullOrEmpty(outp))
                        {
                            string[] lines = outp.Split('\n');
                            foreach (string line in lines)
                            {
                                string trimmed = line.Trim();
                                if (File.Exists(trimmed)) return true;
                            }
                        }
                    }
                }
                else
                {
                    // macOS/Linux detection
                    string home = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile) ?? string.Empty;
                    string[] candidates =
                    {
                        "/opt/homebrew/bin/python3",
                        "/usr/local/bin/python3",
                        "/usr/bin/python3",
                        "/opt/local/bin/python3",
                        Path.Combine(home, ".local", "bin", "python3"),
                        "/Library/Frameworks/Python.framework/Versions/3.13/bin/python3",
                        "/Library/Frameworks/Python.framework/Versions/3.12/bin/python3",
                    };
                    foreach (string c in candidates)
                    {
                        if (File.Exists(c)) return true;
                    }

                    // Try 'which python3'
                    var psi = new ProcessStartInfo
                    {
                        FileName = "/usr/bin/which",
                        Arguments = "python3",
                        UseShellExecute = false,
                        RedirectStandardOutput = true,
                        RedirectStandardError = true,
                        CreateNoWindow = true
                    };
                    using (var p = Process.Start(psi))
                    {
                        string outp = p.StandardOutput.ReadToEnd().Trim();
                        p.WaitForExit(2000);
                        if (p.ExitCode == 0 && !string.IsNullOrEmpty(outp) && File.Exists(outp)) return true;
                    }
                }
            }
            catch { }
            return false;
        }

        public bool IsUvDetected()
        {
            return !string.IsNullOrEmpty(GetUvPath());
        }

        public bool IsClaudeCliDetected()
        {
            return !string.IsNullOrEmpty(GetClaudeCliPath());
        }

        public void SetMcpServerOverride(string path)
        {
            if (string.IsNullOrEmpty(path))
            {
                ClearMcpServerOverride();
                return;
            }

            if (!File.Exists(Path.Combine(path, "server.py")))
            {
                throw new ArgumentException("The selected folder does not contain server.py");
            }

            EditorPrefs.SetString(PythonDirOverrideKey, path);
        }

        public void SetUvPathOverride(string path)
        {
            if (string.IsNullOrEmpty(path))
            {
                ClearUvPathOverride();
                return;
            }

            if (!File.Exists(path))
            {
                throw new ArgumentException("The selected UV executable does not exist");
            }

            EditorPrefs.SetString(UvPathOverrideKey, path);
        }

        public void SetClaudeCliPathOverride(string path)
        {
            if (string.IsNullOrEmpty(path))
            {
                ClearClaudeCliPathOverride();
                return;
            }

            if (!File.Exists(path))
            {
                throw new ArgumentException("The selected Claude CLI executable does not exist");
            }

            EditorPrefs.SetString(ClaudeCliPathOverrideKey, path);
            // Also update the ExecPath helper for backwards compatibility
            ExecPath.SetClaudeCliPath(path);
        }

        public void ClearMcpServerOverride()
        {
            EditorPrefs.DeleteKey(PythonDirOverrideKey);
        }

        public void ClearUvPathOverride()
        {
            EditorPrefs.DeleteKey(UvPathOverrideKey);
        }

        public void ClearClaudeCliPathOverride()
        {
            EditorPrefs.DeleteKey(ClaudeCliPathOverrideKey);
        }
    }
}

```
Page 3/13FirstPrevNextLast