This is page 1 of 18. Use http://codebase.md/justinpbarnett/unity-mcp?lines=true&page={x} to view the full context. # Directory Structure ``` ├── .claude │ ├── prompts │ │ ├── nl-unity-suite-nl.md │ │ └── nl-unity-suite-t.md │ └── settings.json ├── .github │ ├── scripts │ │ └── mark_skipped.py │ └── workflows │ ├── bump-version.yml │ ├── claude-nl-suite.yml │ ├── github-repo-stats.yml │ └── unity-tests.yml ├── .gitignore ├── deploy-dev.bat ├── docs │ ├── CURSOR_HELP.md │ ├── CUSTOM_TOOLS.md │ ├── README-DEV-zh.md │ ├── README-DEV.md │ ├── screenshots │ │ ├── v5_01_uninstall.png │ │ ├── v5_02_install.png │ │ ├── v5_03_open_mcp_window.png │ │ ├── v5_04_rebuild_mcp_server.png │ │ ├── v5_05_rebuild_success.png │ │ ├── v6_2_create_python_tools_asset.png │ │ ├── v6_2_python_tools_asset.png │ │ ├── v6_new_ui_asset_store_version.png │ │ ├── v6_new_ui_dark.png │ │ └── v6_new_ui_light.png │ ├── TELEMETRY.md │ ├── v5_MIGRATION.md │ └── v6_NEW_UI_CHANGES.md ├── LICENSE ├── logo.png ├── mcp_source.py ├── MCPForUnity │ ├── Editor │ │ ├── AssemblyInfo.cs │ │ ├── AssemblyInfo.cs.meta │ │ ├── Data │ │ │ ├── DefaultServerConfig.cs │ │ │ ├── DefaultServerConfig.cs.meta │ │ │ ├── McpClients.cs │ │ │ ├── McpClients.cs.meta │ │ │ ├── PythonToolsAsset.cs │ │ │ └── PythonToolsAsset.cs.meta │ │ ├── Data.meta │ │ ├── Dependencies │ │ │ ├── DependencyManager.cs │ │ │ ├── DependencyManager.cs.meta │ │ │ ├── Models │ │ │ │ ├── DependencyCheckResult.cs │ │ │ │ ├── DependencyCheckResult.cs.meta │ │ │ │ ├── DependencyStatus.cs │ │ │ │ └── DependencyStatus.cs.meta │ │ │ ├── Models.meta │ │ │ ├── PlatformDetectors │ │ │ │ ├── IPlatformDetector.cs │ │ │ │ ├── IPlatformDetector.cs.meta │ │ │ │ ├── LinuxPlatformDetector.cs │ │ │ │ ├── LinuxPlatformDetector.cs.meta │ │ │ │ ├── MacOSPlatformDetector.cs │ │ │ │ ├── MacOSPlatformDetector.cs.meta │ │ │ │ ├── PlatformDetectorBase.cs │ │ │ │ ├── PlatformDetectorBase.cs.meta │ │ │ │ ├── WindowsPlatformDetector.cs │ │ │ │ └── WindowsPlatformDetector.cs.meta │ │ │ └── PlatformDetectors.meta │ │ ├── Dependencies.meta │ │ ├── External │ │ │ ├── Tommy.cs │ │ │ └── Tommy.cs.meta │ │ ├── External.meta │ │ ├── Helpers │ │ │ ├── AssetPathUtility.cs │ │ │ ├── AssetPathUtility.cs.meta │ │ │ ├── CodexConfigHelper.cs │ │ │ ├── CodexConfigHelper.cs.meta │ │ │ ├── ConfigJsonBuilder.cs │ │ │ ├── ConfigJsonBuilder.cs.meta │ │ │ ├── ExecPath.cs │ │ │ ├── ExecPath.cs.meta │ │ │ ├── GameObjectSerializer.cs │ │ │ ├── GameObjectSerializer.cs.meta │ │ │ ├── McpConfigFileHelper.cs │ │ │ ├── McpConfigFileHelper.cs.meta │ │ │ ├── McpConfigurationHelper.cs │ │ │ ├── McpConfigurationHelper.cs.meta │ │ │ ├── McpLog.cs │ │ │ ├── McpLog.cs.meta │ │ │ ├── McpPathResolver.cs │ │ │ ├── McpPathResolver.cs.meta │ │ │ ├── PackageDetector.cs │ │ │ ├── PackageDetector.cs.meta │ │ │ ├── PackageInstaller.cs │ │ │ ├── PackageInstaller.cs.meta │ │ │ ├── PortManager.cs │ │ │ ├── PortManager.cs.meta │ │ │ ├── PythonToolSyncProcessor.cs │ │ │ ├── PythonToolSyncProcessor.cs.meta │ │ │ ├── Response.cs │ │ │ ├── Response.cs.meta │ │ │ ├── ServerInstaller.cs │ │ │ ├── ServerInstaller.cs.meta │ │ │ ├── ServerPathResolver.cs │ │ │ ├── ServerPathResolver.cs.meta │ │ │ ├── TelemetryHelper.cs │ │ │ ├── TelemetryHelper.cs.meta │ │ │ ├── Vector3Helper.cs │ │ │ └── Vector3Helper.cs.meta │ │ ├── Helpers.meta │ │ ├── Importers │ │ │ ├── PythonFileImporter.cs │ │ │ └── PythonFileImporter.cs.meta │ │ ├── Importers.meta │ │ ├── MCPForUnity.Editor.asmdef │ │ ├── MCPForUnity.Editor.asmdef.meta │ │ ├── MCPForUnityBridge.cs │ │ ├── MCPForUnityBridge.cs.meta │ │ ├── Models │ │ │ ├── Command.cs │ │ │ ├── Command.cs.meta │ │ │ ├── McpClient.cs │ │ │ ├── McpClient.cs.meta │ │ │ ├── McpConfig.cs │ │ │ ├── McpConfig.cs.meta │ │ │ ├── MCPConfigServer.cs │ │ │ ├── MCPConfigServer.cs.meta │ │ │ ├── MCPConfigServers.cs │ │ │ ├── MCPConfigServers.cs.meta │ │ │ ├── McpStatus.cs │ │ │ ├── McpStatus.cs.meta │ │ │ ├── McpTypes.cs │ │ │ ├── McpTypes.cs.meta │ │ │ ├── ServerConfig.cs │ │ │ └── ServerConfig.cs.meta │ │ ├── Models.meta │ │ ├── Resources │ │ │ ├── McpForUnityResourceAttribute.cs │ │ │ ├── McpForUnityResourceAttribute.cs.meta │ │ │ ├── MenuItems │ │ │ │ ├── GetMenuItems.cs │ │ │ │ └── GetMenuItems.cs.meta │ │ │ ├── MenuItems.meta │ │ │ ├── Tests │ │ │ │ ├── GetTests.cs │ │ │ │ └── GetTests.cs.meta │ │ │ └── Tests.meta │ │ ├── Resources.meta │ │ ├── Services │ │ │ ├── BridgeControlService.cs │ │ │ ├── BridgeControlService.cs.meta │ │ │ ├── ClientConfigurationService.cs │ │ │ ├── ClientConfigurationService.cs.meta │ │ │ ├── IBridgeControlService.cs │ │ │ ├── IBridgeControlService.cs.meta │ │ │ ├── IClientConfigurationService.cs │ │ │ ├── IClientConfigurationService.cs.meta │ │ │ ├── IPackageUpdateService.cs │ │ │ ├── IPackageUpdateService.cs.meta │ │ │ ├── IPathResolverService.cs │ │ │ ├── IPathResolverService.cs.meta │ │ │ ├── IPythonToolRegistryService.cs │ │ │ ├── IPythonToolRegistryService.cs.meta │ │ │ ├── ITestRunnerService.cs │ │ │ ├── ITestRunnerService.cs.meta │ │ │ ├── IToolSyncService.cs │ │ │ ├── IToolSyncService.cs.meta │ │ │ ├── MCPServiceLocator.cs │ │ │ ├── MCPServiceLocator.cs.meta │ │ │ ├── PackageUpdateService.cs │ │ │ ├── PackageUpdateService.cs.meta │ │ │ ├── PathResolverService.cs │ │ │ ├── PathResolverService.cs.meta │ │ │ ├── PythonToolRegistryService.cs │ │ │ ├── PythonToolRegistryService.cs.meta │ │ │ ├── TestRunnerService.cs │ │ │ ├── TestRunnerService.cs.meta │ │ │ ├── ToolSyncService.cs │ │ │ └── ToolSyncService.cs.meta │ │ ├── Services.meta │ │ ├── Setup │ │ │ ├── SetupWizard.cs │ │ │ ├── SetupWizard.cs.meta │ │ │ ├── SetupWizardWindow.cs │ │ │ └── SetupWizardWindow.cs.meta │ │ ├── Setup.meta │ │ ├── Tools │ │ │ ├── CommandRegistry.cs │ │ │ ├── CommandRegistry.cs.meta │ │ │ ├── ExecuteMenuItem.cs │ │ │ ├── ExecuteMenuItem.cs.meta │ │ │ ├── ManageAsset.cs │ │ │ ├── ManageAsset.cs.meta │ │ │ ├── ManageEditor.cs │ │ │ ├── ManageEditor.cs.meta │ │ │ ├── ManageGameObject.cs │ │ │ ├── ManageGameObject.cs.meta │ │ │ ├── ManageScene.cs │ │ │ ├── ManageScene.cs.meta │ │ │ ├── ManageScript.cs │ │ │ ├── ManageScript.cs.meta │ │ │ ├── ManageShader.cs │ │ │ ├── ManageShader.cs.meta │ │ │ ├── McpForUnityToolAttribute.cs │ │ │ ├── McpForUnityToolAttribute.cs.meta │ │ │ ├── Prefabs │ │ │ │ ├── ManagePrefabs.cs │ │ │ │ └── ManagePrefabs.cs.meta │ │ │ ├── Prefabs.meta │ │ │ ├── ReadConsole.cs │ │ │ ├── ReadConsole.cs.meta │ │ │ ├── RunTests.cs │ │ │ └── RunTests.cs.meta │ │ ├── Tools.meta │ │ ├── Windows │ │ │ ├── ManualConfigEditorWindow.cs │ │ │ ├── ManualConfigEditorWindow.cs.meta │ │ │ ├── MCPForUnityEditorWindow.cs │ │ │ ├── MCPForUnityEditorWindow.cs.meta │ │ │ ├── MCPForUnityEditorWindowNew.cs │ │ │ ├── MCPForUnityEditorWindowNew.cs.meta │ │ │ ├── MCPForUnityEditorWindowNew.uss │ │ │ ├── MCPForUnityEditorWindowNew.uss.meta │ │ │ ├── MCPForUnityEditorWindowNew.uxml │ │ │ ├── MCPForUnityEditorWindowNew.uxml.meta │ │ │ ├── VSCodeManualSetupWindow.cs │ │ │ └── VSCodeManualSetupWindow.cs.meta │ │ └── Windows.meta │ ├── Editor.meta │ ├── package.json │ ├── package.json.meta │ ├── README.md │ ├── README.md.meta │ ├── Runtime │ │ ├── MCPForUnity.Runtime.asmdef │ │ ├── MCPForUnity.Runtime.asmdef.meta │ │ ├── Serialization │ │ │ ├── UnityTypeConverters.cs │ │ │ └── UnityTypeConverters.cs.meta │ │ └── Serialization.meta │ ├── Runtime.meta │ └── UnityMcpServer~ │ └── src │ ├── __init__.py │ ├── config.py │ ├── Dockerfile │ ├── models.py │ ├── module_discovery.py │ ├── port_discovery.py │ ├── pyproject.toml │ ├── pyrightconfig.json │ ├── registry │ │ ├── __init__.py │ │ ├── resource_registry.py │ │ └── tool_registry.py │ ├── reload_sentinel.py │ ├── resources │ │ ├── __init__.py │ │ ├── menu_items.py │ │ └── tests.py │ ├── server_version.txt │ ├── server.py │ ├── telemetry_decorator.py │ ├── telemetry.py │ ├── test_telemetry.py │ ├── tools │ │ ├── __init__.py │ │ ├── execute_menu_item.py │ │ ├── manage_asset.py │ │ ├── manage_editor.py │ │ ├── manage_gameobject.py │ │ ├── manage_prefabs.py │ │ ├── manage_scene.py │ │ ├── manage_script.py │ │ ├── manage_shader.py │ │ ├── read_console.py │ │ ├── resource_tools.py │ │ ├── run_tests.py │ │ └── script_apply_edits.py │ ├── unity_connection.py │ └── uv.lock ├── prune_tool_results.py ├── README-zh.md ├── README.md ├── restore-dev.bat ├── scripts │ └── validate-nlt-coverage.sh ├── test_unity_socket_framing.py ├── TestProjects │ └── UnityMCPTests │ ├── .gitignore │ ├── Assets │ │ ├── Editor.meta │ │ ├── Scenes │ │ │ ├── SampleScene.unity │ │ │ └── SampleScene.unity.meta │ │ ├── Scenes.meta │ │ ├── Scripts │ │ │ ├── Hello.cs │ │ │ ├── Hello.cs.meta │ │ │ ├── LongUnityScriptClaudeTest.cs │ │ │ ├── LongUnityScriptClaudeTest.cs.meta │ │ │ ├── TestAsmdef │ │ │ │ ├── CustomComponent.cs │ │ │ │ ├── CustomComponent.cs.meta │ │ │ │ ├── TestAsmdef.asmdef │ │ │ │ └── TestAsmdef.asmdef.meta │ │ │ └── TestAsmdef.meta │ │ ├── Scripts.meta │ │ ├── Tests │ │ │ ├── EditMode │ │ │ │ ├── Data │ │ │ │ │ ├── PythonToolsAssetTests.cs │ │ │ │ │ └── PythonToolsAssetTests.cs.meta │ │ │ │ ├── Data.meta │ │ │ │ ├── Helpers │ │ │ │ │ ├── CodexConfigHelperTests.cs │ │ │ │ │ ├── CodexConfigHelperTests.cs.meta │ │ │ │ │ ├── WriteToConfigTests.cs │ │ │ │ │ └── WriteToConfigTests.cs.meta │ │ │ │ ├── Helpers.meta │ │ │ │ ├── MCPForUnityTests.Editor.asmdef │ │ │ │ ├── MCPForUnityTests.Editor.asmdef.meta │ │ │ │ ├── Resources │ │ │ │ │ ├── GetMenuItemsTests.cs │ │ │ │ │ └── GetMenuItemsTests.cs.meta │ │ │ │ ├── Resources.meta │ │ │ │ ├── Services │ │ │ │ │ ├── PackageUpdateServiceTests.cs │ │ │ │ │ ├── PackageUpdateServiceTests.cs.meta │ │ │ │ │ ├── PythonToolRegistryServiceTests.cs │ │ │ │ │ ├── PythonToolRegistryServiceTests.cs.meta │ │ │ │ │ ├── ToolSyncServiceTests.cs │ │ │ │ │ └── ToolSyncServiceTests.cs.meta │ │ │ │ ├── Services.meta │ │ │ │ ├── Tools │ │ │ │ │ ├── AIPropertyMatchingTests.cs │ │ │ │ │ ├── AIPropertyMatchingTests.cs.meta │ │ │ │ │ ├── CommandRegistryTests.cs │ │ │ │ │ ├── CommandRegistryTests.cs.meta │ │ │ │ │ ├── ComponentResolverTests.cs │ │ │ │ │ ├── ComponentResolverTests.cs.meta │ │ │ │ │ ├── ExecuteMenuItemTests.cs │ │ │ │ │ ├── ExecuteMenuItemTests.cs.meta │ │ │ │ │ ├── ManageGameObjectTests.cs │ │ │ │ │ ├── ManageGameObjectTests.cs.meta │ │ │ │ │ ├── ManagePrefabsTests.cs │ │ │ │ │ ├── ManagePrefabsTests.cs.meta │ │ │ │ │ ├── ManageScriptValidationTests.cs │ │ │ │ │ └── ManageScriptValidationTests.cs.meta │ │ │ │ ├── Tools.meta │ │ │ │ ├── Windows │ │ │ │ │ ├── ManualConfigJsonBuilderTests.cs │ │ │ │ │ └── ManualConfigJsonBuilderTests.cs.meta │ │ │ │ └── Windows.meta │ │ │ └── EditMode.meta │ │ └── Tests.meta │ ├── Packages │ │ └── manifest.json │ └── ProjectSettings │ ├── Packages │ │ └── com.unity.testtools.codecoverage │ │ └── Settings.json │ └── ProjectVersion.txt ├── tests │ ├── conftest.py │ ├── test_edit_normalization_and_noop.py │ ├── test_edit_strict_and_warnings.py │ ├── test_find_in_file_minimal.py │ ├── test_get_sha.py │ ├── test_improved_anchor_matching.py │ ├── test_logging_stdout.py │ ├── test_manage_script_uri.py │ ├── test_read_console_truncate.py │ ├── test_read_resource_minimal.py │ ├── test_resources_api.py │ ├── test_script_editing.py │ ├── test_script_tools.py │ ├── test_telemetry_endpoint_validation.py │ ├── test_telemetry_queue_worker.py │ ├── test_telemetry_subaction.py │ ├── test_transport_framing.py │ └── test_validate_script_summary.py ├── tools │ └── stress_mcp.py └── UnityMcpBridge ├── Editor │ ├── AssemblyInfo.cs │ ├── AssemblyInfo.cs.meta │ ├── Data │ │ ├── DefaultServerConfig.cs │ │ ├── DefaultServerConfig.cs.meta │ │ ├── McpClients.cs │ │ └── McpClients.cs.meta │ ├── Data.meta │ ├── Dependencies │ │ ├── DependencyManager.cs │ │ ├── DependencyManager.cs.meta │ │ ├── Models │ │ │ ├── DependencyCheckResult.cs │ │ │ ├── DependencyCheckResult.cs.meta │ │ │ ├── DependencyStatus.cs │ │ │ └── DependencyStatus.cs.meta │ │ ├── Models.meta │ │ ├── PlatformDetectors │ │ │ ├── IPlatformDetector.cs │ │ │ ├── IPlatformDetector.cs.meta │ │ │ ├── LinuxPlatformDetector.cs │ │ │ ├── LinuxPlatformDetector.cs.meta │ │ │ ├── MacOSPlatformDetector.cs │ │ │ ├── MacOSPlatformDetector.cs.meta │ │ │ ├── PlatformDetectorBase.cs │ │ │ ├── PlatformDetectorBase.cs.meta │ │ │ ├── WindowsPlatformDetector.cs │ │ │ └── WindowsPlatformDetector.cs.meta │ │ └── PlatformDetectors.meta │ ├── Dependencies.meta │ ├── External │ │ ├── Tommy.cs │ │ └── Tommy.cs.meta │ ├── External.meta │ ├── Helpers │ │ ├── AssetPathUtility.cs │ │ ├── AssetPathUtility.cs.meta │ │ ├── CodexConfigHelper.cs │ │ ├── CodexConfigHelper.cs.meta │ │ ├── ConfigJsonBuilder.cs │ │ ├── ConfigJsonBuilder.cs.meta │ │ ├── ExecPath.cs │ │ ├── ExecPath.cs.meta │ │ ├── GameObjectSerializer.cs │ │ ├── GameObjectSerializer.cs.meta │ │ ├── McpConfigFileHelper.cs │ │ ├── McpConfigFileHelper.cs.meta │ │ ├── McpConfigurationHelper.cs │ │ ├── McpConfigurationHelper.cs.meta │ │ ├── McpLog.cs │ │ ├── McpLog.cs.meta │ │ ├── McpPathResolver.cs │ │ ├── McpPathResolver.cs.meta │ │ ├── PackageDetector.cs │ │ ├── PackageDetector.cs.meta │ │ ├── PackageInstaller.cs │ │ ├── PackageInstaller.cs.meta │ │ ├── PortManager.cs │ │ ├── PortManager.cs.meta │ │ ├── Response.cs │ │ ├── Response.cs.meta │ │ ├── ServerInstaller.cs │ │ ├── ServerInstaller.cs.meta │ │ ├── ServerPathResolver.cs │ │ ├── ServerPathResolver.cs.meta │ │ ├── TelemetryHelper.cs │ │ ├── TelemetryHelper.cs.meta │ │ ├── Vector3Helper.cs │ │ └── Vector3Helper.cs.meta │ ├── Helpers.meta │ ├── MCPForUnity.Editor.asmdef │ ├── MCPForUnity.Editor.asmdef.meta │ ├── MCPForUnityBridge.cs │ ├── MCPForUnityBridge.cs.meta │ ├── Models │ │ ├── Command.cs │ │ ├── Command.cs.meta │ │ ├── McpClient.cs │ │ ├── McpClient.cs.meta │ │ ├── McpConfig.cs │ │ ├── McpConfig.cs.meta │ │ ├── MCPConfigServer.cs │ │ ├── MCPConfigServer.cs.meta │ │ ├── MCPConfigServers.cs │ │ ├── MCPConfigServers.cs.meta │ │ ├── McpStatus.cs │ │ ├── McpStatus.cs.meta │ │ ├── McpTypes.cs │ │ ├── McpTypes.cs.meta │ │ ├── ServerConfig.cs │ │ └── ServerConfig.cs.meta │ ├── Models.meta │ ├── Setup │ │ ├── SetupWizard.cs │ │ ├── SetupWizard.cs.meta │ │ ├── SetupWizardWindow.cs │ │ └── SetupWizardWindow.cs.meta │ ├── Setup.meta │ ├── Tools │ │ ├── CommandRegistry.cs │ │ ├── CommandRegistry.cs.meta │ │ ├── ManageAsset.cs │ │ ├── ManageAsset.cs.meta │ │ ├── ManageEditor.cs │ │ ├── ManageEditor.cs.meta │ │ ├── ManageGameObject.cs │ │ ├── ManageGameObject.cs.meta │ │ ├── ManageScene.cs │ │ ├── ManageScene.cs.meta │ │ ├── ManageScript.cs │ │ ├── ManageScript.cs.meta │ │ ├── ManageShader.cs │ │ ├── ManageShader.cs.meta │ │ ├── McpForUnityToolAttribute.cs │ │ ├── McpForUnityToolAttribute.cs.meta │ │ ├── MenuItems │ │ │ ├── ManageMenuItem.cs │ │ │ ├── ManageMenuItem.cs.meta │ │ │ ├── MenuItemExecutor.cs │ │ │ ├── MenuItemExecutor.cs.meta │ │ │ ├── MenuItemsReader.cs │ │ │ └── MenuItemsReader.cs.meta │ │ ├── MenuItems.meta │ │ ├── Prefabs │ │ │ ├── ManagePrefabs.cs │ │ │ └── ManagePrefabs.cs.meta │ │ ├── Prefabs.meta │ │ ├── ReadConsole.cs │ │ └── ReadConsole.cs.meta │ ├── Tools.meta │ ├── Windows │ │ ├── ManualConfigEditorWindow.cs │ │ ├── ManualConfigEditorWindow.cs.meta │ │ ├── MCPForUnityEditorWindow.cs │ │ ├── MCPForUnityEditorWindow.cs.meta │ │ ├── VSCodeManualSetupWindow.cs │ │ └── VSCodeManualSetupWindow.cs.meta │ └── Windows.meta ├── Editor.meta ├── package.json ├── package.json.meta ├── README.md ├── README.md.meta ├── Runtime │ ├── MCPForUnity.Runtime.asmdef │ ├── MCPForUnity.Runtime.asmdef.meta │ ├── Serialization │ │ ├── UnityTypeConverters.cs │ │ └── UnityTypeConverters.cs.meta │ └── Serialization.meta ├── Runtime.meta └── UnityMcpServer~ └── src ├── __init__.py ├── config.py ├── Dockerfile ├── port_discovery.py ├── pyproject.toml ├── pyrightconfig.json ├── registry │ ├── __init__.py │ └── tool_registry.py ├── reload_sentinel.py ├── server_version.txt ├── server.py ├── telemetry_decorator.py ├── telemetry.py ├── test_telemetry.py ├── tools │ ├── __init__.py │ ├── manage_asset.py │ ├── manage_editor.py │ ├── manage_gameobject.py │ ├── manage_menu_item.py │ ├── manage_prefabs.py │ ├── manage_scene.py │ ├── manage_script.py │ ├── manage_shader.py │ ├── read_console.py │ ├── resource_tools.py │ └── script_apply_edits.py ├── unity_connection.py └── uv.lock ``` # Files -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- ``` 1 | # AI-related files 2 | .cursorrules 3 | .cursorignore 4 | .windsurf 5 | .codeiumignore 6 | .kiro 7 | CLAUDE.md 8 | 9 | # Code-copy related files 10 | .clipignore 11 | 12 | # Python-generated files 13 | __pycache__/ 14 | __pycache__.meta 15 | build/ 16 | dist/ 17 | wheels/ 18 | *.egg-info 19 | UnityMcpServer/**/*.meta 20 | UnityMcpServer.meta 21 | 22 | # Virtual environments 23 | .venv 24 | 25 | # Unity Editor 26 | *.unitypackage 27 | *.asset 28 | LICENSE.meta 29 | CONTRIBUTING.md.meta 30 | 31 | # IDE 32 | .idea/ 33 | .vscode/ 34 | .aider* 35 | .DS_Store* 36 | # Unity test project lock files 37 | TestProjects/UnityMCPTests/Packages/packages-lock.json 38 | 39 | # Backup artifacts 40 | *.backup 41 | *.backup.meta 42 | ``` -------------------------------------------------------------------------------- /TestProjects/UnityMCPTests/.gitignore: -------------------------------------------------------------------------------- ``` 1 | # This .gitignore file should be placed at the root of your Unity project directory 2 | # 3 | # Get latest from https://github.com/github/gitignore/blob/main/Unity.gitignore 4 | # 5 | .utmp/ 6 | /[Ll]ibrary/ 7 | /[Tt]emp/ 8 | /[Oo]bj/ 9 | /[Bb]uild/ 10 | /[Bb]uilds/ 11 | /[Ll]ogs/ 12 | /[Uu]ser[Ss]ettings/ 13 | *.log 14 | 15 | # By default unity supports Blender asset imports, *.blend1 blender files do not need to be commited to version control. 16 | *.blend1 17 | *.blend1.meta 18 | 19 | # MemoryCaptures can get excessive in size. 20 | # They also could contain extremely sensitive data 21 | /[Mm]emoryCaptures/ 22 | 23 | # Recordings can get excessive in size 24 | /[Rr]ecordings/ 25 | 26 | # Uncomment this line if you wish to ignore the asset store tools plugin 27 | # /[Aa]ssets/AssetStoreTools* 28 | 29 | # Autogenerated Jetbrains Rider plugin 30 | /[Aa]ssets/Plugins/Editor/JetBrains* 31 | # Jetbrains Rider personal-layer settings 32 | *.DotSettings.user 33 | 34 | # Visual Studio cache directory 35 | .vs/ 36 | 37 | # Gradle cache directory 38 | .gradle/ 39 | 40 | # Autogenerated VS/MD/Consulo solution and project files 41 | ExportedObj/ 42 | .consulo/ 43 | *.csproj 44 | *.unityproj 45 | *.sln 46 | *.suo 47 | *.tmp 48 | *.user 49 | *.userprefs 50 | *.pidb 51 | *.booproj 52 | *.svd 53 | *.pdb 54 | *.mdb 55 | *.opendb 56 | *.VC.db 57 | 58 | # Unity3D generated meta files 59 | *.pidb.meta 60 | *.pdb.meta 61 | *.mdb.meta 62 | 63 | # Unity3D generated file on crash reports 64 | sysinfo.txt 65 | 66 | # Mono auto generated files 67 | mono_crash.* 68 | 69 | # Builds 70 | *.apk 71 | *.aab 72 | *.unitypackage 73 | *.unitypackage.meta 74 | *.app 75 | 76 | # Crashlytics generated file 77 | crashlytics-build.properties 78 | 79 | # TestRunner generated files 80 | InitTestScene*.unity* 81 | 82 | # Addressables default ignores, before user customizations 83 | /ServerData 84 | /[Aa]ssets/StreamingAssets/aa* 85 | /[Aa]ssets/AddressableAssetsData/link.xml* 86 | /[Aa]ssets/Addressables_Temp* 87 | # By default, Addressables content builds will generate addressables_content_state.bin 88 | # files in platform-specific subfolders, for example: 89 | # /Assets/AddressableAssetsData/OSX/addressables_content_state.bin 90 | /[Aa]ssets/AddressableAssetsData/*/*.bin* 91 | 92 | # Visual Scripting auto-generated files 93 | /[Aa]ssets/Unity.VisualScripting.Generated/VisualScripting.Flow/UnitOptions.db 94 | /[Aa]ssets/Unity.VisualScripting.Generated/VisualScripting.Flow/UnitOptions.db.meta 95 | /[Aa]ssets/Unity.VisualScripting.Generated/VisualScripting.Core/Property Providers 96 | /[Aa]ssets/Unity.VisualScripting.Generated/VisualScripting.Core/Property Providers.meta 97 | 98 | # Auto-generated scenes by play mode tests 99 | /[Aa]ssets/[Ii]nit[Tt]est[Ss]cene*.unity* 100 | 101 | .vscode 102 | .cursor 103 | .windsurf 104 | .claude 105 | .DS_Store 106 | ``` -------------------------------------------------------------------------------- /MCPForUnity/README.md: -------------------------------------------------------------------------------- ```markdown 1 | # MCP for Unity — Editor Plugin Guide 2 | 3 | Use this guide to configure and run MCP for Unity inside the Unity Editor. Installation is covered elsewhere; this document focuses on the Editor window, client configuration, and troubleshooting. 4 | 5 | ## Open the window 6 | - Unity menu: Window > MCP for Unity 7 | 8 | The window has four areas: Server Status, Unity Bridge, MCP Client Configuration, and Script Validation. 9 | 10 | --- 11 | 12 | ## Quick start 13 | 1. Open Window > MCP for Unity. 14 | 2. Click “Auto-Setup”. 15 | 3. If prompted: 16 | - Select the server folder that contains `server.py` (UnityMcpServer~/src). 17 | - Install Python and/or uv if missing. 18 | - For Claude Code, ensure the `claude` CLI is installed. 19 | 4. Click “Start Bridge” if the Unity Bridge shows “Stopped”. 20 | 5. Use your MCP client (Cursor, VS Code, Windsurf, Claude Code) to connect. 21 | 22 | --- 23 | 24 | ## Server Status 25 | - Status dot and label: 26 | - Installed / Installed (Embedded) / Not Installed. 27 | - Mode and ports: 28 | - Mode: Auto or Standard. 29 | - Ports: Unity (varies; shown in UI), MCP 6500. 30 | - Actions: 31 | - Auto-Setup: Registers/updates your selected MCP client(s), ensures bridge connectivity. Shows “Connected ✓” after success. 32 | - Rebuild MCP Server: Rebuilds the Python based MCP server 33 | - Select server folder…: Choose the folder containing `server.py`. 34 | - Verify again: Re-checks server presence. 35 | - If Python isn’t detected, use “Open Install Instructions”. 36 | 37 | --- 38 | 39 | ## Unity Bridge 40 | - Shows Running or Stopped with a status dot. 41 | - Start/Stop Bridge button toggles the Unity bridge process used by MCP clients to talk to Unity. 42 | - Tip: After Auto-Setup, the bridge may auto-start in Auto mode. 43 | 44 | --- 45 | 46 | ## MCP Client Configuration 47 | - Select Client: Choose your target MCP client (e.g., Cursor, VS Code, Windsurf, Claude Code). 48 | - Per-client actions: 49 | - Cursor / VS Code / Windsurf: 50 | - Auto Configure: Writes/updates your config to launch the server via uv: 51 | - Command: uv 52 | - Args: run --directory <pythonDir> server.py 53 | - Manual Setup: Opens a window with a pre-filled JSON snippet to copy/paste into your client config. 54 | - Choose `uv` Install Location: If uv isn’t on PATH, select the uv binary. 55 | - A compact “Config:” line shows the resolved config file name once uv/server are detected. 56 | - Claude Code: 57 | - Register with Claude Code / Unregister MCP for Unity with Claude Code. 58 | - If the CLI isn’t found, click “Choose Claude Install Location”. 59 | - The window displays the resolved Claude CLI path when detected. 60 | 61 | Notes: 62 | - The UI shows a status dot and a short status text (e.g., “Configured”, “uv Not Found”, “Claude Not Found”). 63 | - Use “Auto Configure” for one-click setup; use “Manual Setup” when you prefer to review/copy config. 64 | 65 | --- 66 | 67 | ## Script Validation 68 | - Validation Level options: 69 | - Basic — Only syntax checks 70 | - Standard — Syntax + Unity practices 71 | - Comprehensive — All checks + semantic analysis 72 | - Strict — Full semantic validation (requires Roslyn) 73 | - Pick a level based on your project’s needs. A description is shown under the dropdown. 74 | 75 | --- 76 | 77 | ## Troubleshooting 78 | - Python or `uv` not found: 79 | - Help: [Fix MCP for Unity with Cursor, VS Code & Windsurf](https://github.com/CoplayDev/unity-mcp/wiki/1.-Fix-Unity-MCP-and-Cursor,-VSCode-&-Windsurf) 80 | - Claude CLI not found: 81 | - Help: [Fix MCP for Unity with Claude Code](https://github.com/CoplayDev/unity-mcp/wiki/2.-Fix-Unity-MCP-and-Claude-Code) 82 | 83 | --- 84 | 85 | ## Tips 86 | - Enable “Show Debug Logs” in the header for more details in the Console when diagnosing issues. 87 | 88 | --- ``` -------------------------------------------------------------------------------- /UnityMcpBridge/README.md: -------------------------------------------------------------------------------- ```markdown 1 | # MCP for Unity — Editor Plugin Guide 2 | 3 | Use this guide to configure and run MCP for Unity inside the Unity Editor. Installation is covered elsewhere; this document focuses on the Editor window, client configuration, and troubleshooting. 4 | 5 | ## Open the window 6 | - Unity menu: Window > MCP for Unity 7 | 8 | The window has four areas: Server Status, Unity Bridge, MCP Client Configuration, and Script Validation. 9 | 10 | --- 11 | 12 | ## Quick start 13 | 1. Open Window > MCP for Unity. 14 | 2. Click “Auto-Setup”. 15 | 3. If prompted: 16 | - Select the server folder that contains `server.py` (UnityMcpServer~/src). 17 | - Install Python and/or uv if missing. 18 | - For Claude Code, ensure the `claude` CLI is installed. 19 | 4. Click “Start Bridge” if the Unity Bridge shows “Stopped”. 20 | 5. Use your MCP client (Cursor, VS Code, Windsurf, Claude Code) to connect. 21 | 22 | --- 23 | 24 | ## Server Status 25 | - Status dot and label: 26 | - Installed / Installed (Embedded) / Not Installed. 27 | - Mode and ports: 28 | - Mode: Auto or Standard. 29 | - Ports: Unity (varies; shown in UI), MCP 6500. 30 | - Actions: 31 | - Auto-Setup: Registers/updates your selected MCP client(s), ensures bridge connectivity. Shows “Connected ✓” after success. 32 | - Rebuild MCP Server: Rebuilds the Python based MCP server 33 | - Select server folder…: Choose the folder containing `server.py`. 34 | - Verify again: Re-checks server presence. 35 | - If Python isn’t detected, use “Open Install Instructions”. 36 | 37 | --- 38 | 39 | ## Unity Bridge 40 | - Shows Running or Stopped with a status dot. 41 | - Start/Stop Bridge button toggles the Unity bridge process used by MCP clients to talk to Unity. 42 | - Tip: After Auto-Setup, the bridge may auto-start in Auto mode. 43 | 44 | --- 45 | 46 | ## MCP Client Configuration 47 | - Select Client: Choose your target MCP client (e.g., Cursor, VS Code, Windsurf, Claude Code). 48 | - Per-client actions: 49 | - Cursor / VS Code / Windsurf: 50 | - Auto Configure: Writes/updates your config to launch the server via uv: 51 | - Command: uv 52 | - Args: run --directory <pythonDir> server.py 53 | - Manual Setup: Opens a window with a pre-filled JSON snippet to copy/paste into your client config. 54 | - Choose `uv` Install Location: If uv isn’t on PATH, select the uv binary. 55 | - A compact “Config:” line shows the resolved config file name once uv/server are detected. 56 | - Claude Code: 57 | - Register with Claude Code / Unregister MCP for Unity with Claude Code. 58 | - If the CLI isn’t found, click “Choose Claude Install Location”. 59 | - The window displays the resolved Claude CLI path when detected. 60 | 61 | Notes: 62 | - The UI shows a status dot and a short status text (e.g., “Configured”, “uv Not Found”, “Claude Not Found”). 63 | - Use “Auto Configure” for one-click setup; use “Manual Setup” when you prefer to review/copy config. 64 | 65 | --- 66 | 67 | ## Script Validation 68 | - Validation Level options: 69 | - Basic — Only syntax checks 70 | - Standard — Syntax + Unity practices 71 | - Comprehensive — All checks + semantic analysis 72 | - Strict — Full semantic validation (requires Roslyn) 73 | - Pick a level based on your project’s needs. A description is shown under the dropdown. 74 | 75 | --- 76 | 77 | ## Troubleshooting 78 | - Python or `uv` not found: 79 | - Help: [Fix MCP for Unity with Cursor, VS Code & Windsurf](https://github.com/CoplayDev/unity-mcp/wiki/1.-Fix-Unity-MCP-and-Cursor,-VSCode-&-Windsurf) 80 | - Claude CLI not found: 81 | - Help: [Fix MCP for Unity with Claude Code](https://github.com/CoplayDev/unity-mcp/wiki/2.-Fix-Unity-MCP-and-Claude-Code) 82 | 83 | --- 84 | 85 | ## Tips 86 | - Enable “Show Debug Logs” in the header for more details in the Console when diagnosing issues. 87 | 88 | --- ``` -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- ```markdown 1 | <img width="676" height="380" alt="MCP for Unity" src="https://github.com/user-attachments/assets/b712e41d-273c-48b2-9041-82bd17ace267" /> 2 | 3 | | [English](README.md) | [简体中文](README-zh.md) | 4 | |----------------------|---------------------------------| 5 | 6 | #### Proudly sponsored and maintained by [Coplay](https://www.coplay.dev/?ref=unity-mcp) -- the best AI assistant for Unity. 7 | 8 | [](https://discord.gg/y4p8KfzrN4) 9 | [](https://www.coplay.dev/?ref=unity-mcp) 10 | [](https://unity.com/releases/editor/archive) 11 | [](https://www.python.org) 12 | [](https://modelcontextprotocol.io/introduction) 13 |  14 |  15 | [](https://opensource.org/licenses/MIT) 16 | 17 | **Create your Unity apps with LLMs!** 18 | 19 | MCP for Unity acts as a bridge, allowing AI assistants (like Claude, Cursor) to interact directly with your Unity Editor via a local **MCP (Model Context Protocol) Client**. Give your LLM tools to manage assets, control scenes, edit scripts, and automate tasks within Unity. 20 | 21 | --- 22 | 23 | ### 💬 Join Our [Discord](https://discord.gg/y4p8KfzrN4) 24 | 25 | **Get help, share ideas, and collaborate with other MCP for Unity developers!** 26 | 27 | --- 28 | 29 | ## Key Features 🚀 30 | 31 | * **🗣️ Natural Language Control:** Instruct your LLM to perform Unity tasks. 32 | * **🛠️ Powerful Tools:** Manage assets, scenes, materials, scripts, and editor functions. 33 | * **🤖 Automation:** Automate repetitive Unity workflows. 34 | * **🧩 Extensible:** Designed to work with various MCP Clients. 35 | 36 | <details open> 37 | <summary><strong> Available Tools </strong></summary> 38 | 39 | Your LLM can use functions like: 40 | 41 | * `read_console`: Gets messages from or clears the console. 42 | * `manage_script`: Manages C# scripts (create, read, update, delete). 43 | * `manage_editor`: Controls and queries the editor's state and settings. 44 | * `manage_scene`: Manages scenes (load, save, create, get hierarchy, etc.). 45 | * `manage_asset`: Performs asset operations (import, create, modify, delete, etc.). 46 | * `manage_shader`: Performs shader CRUD operations (create, read, modify, delete). 47 | * `manage_gameobject`: Manages GameObjects: create, modify, delete, find, and component operations. 48 | * `execute_menu_item`: Executes Unity Editor menu items (e.g., "File/Save Project"). 49 | * `apply_text_edits`: Precise text edits with precondition hashes and atomic multi-edit batches. 50 | * `script_apply_edits`: Structured C# method/class edits (insert/replace/delete) with safer boundaries. 51 | * `validate_script`: Fast validation (basic/standard) to catch syntax/structure issues before/after writes. 52 | </details> 53 | 54 | --- 55 | 56 | ## How It Works 57 | 58 | MCP for Unity connects your tools using two components: 59 | 60 | 1. **MCP for Unity Bridge:** A Unity package running inside the Editor. (Installed via Package Manager). 61 | 2. **MCP for Unity Server:** A Python server that runs locally, communicating between the Unity Bridge and your MCP Client. (Installed automatically by the package on first run or via Auto-Setup; manual setup is available as a fallback). 62 | 63 | <img width="562" height="121" alt="image" src="https://github.com/user-attachments/assets/9abf9c66-70d1-4b82-9587-658e0d45dc3e" /> 64 | 65 | --- 66 | 67 | ## Installation ⚙️ 68 | 69 | ### Prerequisites 70 | 71 | * **Python:** Version 3.12 or newer. [Download Python](https://www.python.org/downloads/) 72 | * **Unity Hub & Editor:** Version 2021.3 LTS or newer. [Download Unity](https://unity.com/download) 73 | * **uv (Python toolchain manager):** 74 | ```bash 75 | # macOS / Linux 76 | curl -LsSf https://astral.sh/uv/install.sh | sh 77 | 78 | # Windows (PowerShell) 79 | winget install --id=astral-sh.uv -e 80 | 81 | # Docs: https://docs.astral.sh/uv/getting-started/installation/ 82 | ``` 83 | 84 | * **An MCP Client:** : [Claude Desktop](https://claude.ai/download) | [Claude Code](https://github.com/anthropics/claude-code) | [Cursor](https://www.cursor.com/en/downloads) | [Visual Studio Code Copilot](https://code.visualstudio.com/docs/copilot/overview) | [Windsurf](https://windsurf.com) | Others work with manual config 85 | 86 | * <details> <summary><strong>[Optional] Roslyn for Advanced Script Validation</strong></summary> 87 | 88 | For **Strict** validation level that catches undefined namespaces, types, and methods: 89 | 90 | **Method 1: NuGet for Unity (Recommended)** 91 | 1. Install [NuGetForUnity](https://github.com/GlitchEnzo/NuGetForUnity) 92 | 2. Go to `Window > NuGet Package Manager` 93 | 3. Search for `Microsoft.CodeAnalysis`, select version 4.14.0, and install the package 94 | 4. Also install package `SQLitePCLRaw.core` and `SQLitePCLRaw.bundle_e_sqlite3`. 95 | 5. Go to `Player Settings > Scripting Define Symbols` 96 | 6. Add `USE_ROSLYN` 97 | 7. Restart Unity 98 | 99 | **Method 2: Manual DLL Installation** 100 | 1. Download Microsoft.CodeAnalysis.CSharp.dll and dependencies from [NuGet](https://www.nuget.org/packages/Microsoft.CodeAnalysis.CSharp/) 101 | 2. Place DLLs in `Assets/Plugins/` folder 102 | 3. Ensure .NET compatibility settings are correct 103 | 4. Add `USE_ROSLYN` to Scripting Define Symbols 104 | 5. Restart Unity 105 | 106 | **Note:** Without Roslyn, script validation falls back to basic structural checks. Roslyn enables full C# compiler diagnostics with precise error reporting.</details> 107 | 108 | --- 109 | ### 🌟 Step 1: Install the Unity Package 110 | 111 | #### To install via Git URL 112 | 113 | 1. Open your Unity project. 114 | 2. Go to `Window > Package Manager`. 115 | 3. Click `+` -> `Add package from git URL...`. 116 | 4. Enter: 117 | ``` 118 | https://github.com/CoplayDev/unity-mcp.git?path=/MCPForUnity 119 | ``` 120 | 5. Click `Add`. 121 | 6. The MCP server is installed automatically by the package on first run or via Auto-Setup. If that fails, use Manual Configuration (below). 122 | 123 | #### To install via OpenUPM 124 | 125 | 1. Install the [OpenUPM CLI](https://openupm.com/docs/getting-started-cli.html) 126 | 2. Open a terminal (PowerShell, Terminal, etc.) and navigate to your Unity project directory 127 | 3. Run `openupm add com.coplaydev.unity-mcp` 128 | 129 | **Note:** If you installed the MCP Server before Coplay's maintenance, you will need to uninstall the old package before re-installing the new one. 130 | 131 | ### 🛠️ Step 2: Configure Your MCP Client 132 | Connect your MCP Client (Claude, Cursor, etc.) to the Python server set up in Step 1 (auto) or via Manual Configuration (below). 133 | 134 | <img width="648" height="599" alt="MCPForUnity-Readme-Image" src="https://github.com/user-attachments/assets/b4a725da-5c43-4bd6-80d6-ee2e3cca9596" /> 135 | 136 | **Option A: Auto-Setup (Recommended for Claude/Cursor/VSC Copilot)** 137 | 138 | 1. In Unity, go to `Window > MCP for Unity`. 139 | 2. Click `Auto-Setup`. 140 | 3. Look for a green status indicator 🟢 and "Connected ✓". *(This attempts to modify the MCP Client's config file automatically).* 141 | 142 | <details><summary><strong>Client-specific troubleshooting</strong></summary> 143 | 144 | - **VSCode**: uses `Code/User/mcp.json` with top-level `servers.unityMCP` and `"type": "stdio"`. On Windows, MCP for Unity writes an absolute `uv.exe` (prefers WinGet Links shim) to avoid PATH issues. 145 | - **Cursor / Windsurf** [(**help link**)](https://github.com/CoplayDev/unity-mcp/wiki/1.-Fix-Unity-MCP-and-Cursor,-VSCode-&-Windsurf): if `uv` is missing, the MCP for Unity window shows "uv Not Found" with a quick [HELP] link and a "Choose `uv` Install Location" button. 146 | - **Claude Code** [(**help link**)](https://github.com/CoplayDev/unity-mcp/wiki/2.-Fix-Unity-MCP-and-Claude-Code): if `claude` isn't found, the window shows "Claude Not Found" with [HELP] and a "Choose Claude Location" button. Unregister now updates the UI immediately.</details> 147 | 148 | 149 | **Option B: Manual Configuration** 150 | 151 | If Auto-Setup fails or you use a different client: 152 | 153 | 1. **Find your MCP Client's configuration file.** (Check client documentation). 154 | * *Claude Example (macOS):* `~/Library/Application Support/Claude/claude_desktop_config.json` 155 | * *Claude Example (Windows):* `%APPDATA%\Claude\claude_desktop_config.json` 156 | 2. **Edit the file** to add/update the `mcpServers` section, using the *exact* paths from Step 1. 157 | 158 | <details> 159 | <summary><strong>Click for Client-Specific JSON Configuration Snippets...</strong></summary> 160 | 161 | --- 162 | **Claude Code** 163 | 164 | If you're using Claude Code, you can register the MCP server using the below commands: 165 | 🚨**make sure to run these from your Unity project's home directory**🚨 166 | 167 | **macOS:** 168 | 169 | ```bash 170 | claude mcp add UnityMCP -- uv --directory /Users/USERNAME/Library/AppSupport/UnityMCP/UnityMcpServer/src run server.py 171 | ``` 172 | 173 | **Windows:** 174 | 175 | ```bash 176 | claude mcp add UnityMCP -- "C:/Users/USERNAME/AppData/Local/Microsoft/WinGet/Links/uv.exe" --directory "C:/Users/USERNAME/AppData/Local/UnityMCP/UnityMcpServer/src" run server.py 177 | ``` 178 | **VSCode (all OS)** 179 | 180 | ```json 181 | { 182 | "servers": { 183 | "unityMCP": { 184 | "command": "uv", 185 | "args": ["--directory","<ABSOLUTE_PATH_TO>/UnityMcpServer/src","run","server.py"], 186 | "type": "stdio" 187 | } 188 | } 189 | } 190 | ``` 191 | 192 | On Windows, set `command` to the absolute shim, e.g. `C:\\Users\\YOU\\AppData\\Local\\Microsoft\\WinGet\\Links\\uv.exe`. 193 | 194 | **Windows:** 195 | 196 | ```json 197 | { 198 | "mcpServers": { 199 | "UnityMCP": { 200 | "command": "uv", 201 | "args": [ 202 | "run", 203 | "--directory", 204 | "C:\\Users\\YOUR_USERNAME\\AppData\\Local\\UnityMCP\\UnityMcpServer\\src", 205 | "server.py" 206 | ] 207 | } 208 | // ... other servers might be here ... 209 | } 210 | } 211 | ``` 212 | 213 | (Remember to replace YOUR_USERNAME and use double backslashes \\) 214 | 215 | **macOS:** 216 | 217 | ```json 218 | { 219 | "mcpServers": { 220 | "UnityMCP": { 221 | "command": "uv", 222 | "args": [ 223 | "run", 224 | "--directory", 225 | "/Users/YOUR_USERNAME/Library/AppSupport/UnityMCP/UnityMcpServer/src", 226 | "server.py" 227 | ] 228 | } 229 | // ... other servers might be here ... 230 | } 231 | } 232 | ``` 233 | 234 | (Replace YOUR_USERNAME. Note: AppSupport is a symlink to "Application Support" to avoid quoting issues) 235 | 236 | **Linux:** 237 | 238 | ```json 239 | { 240 | "mcpServers": { 241 | "UnityMCP": { 242 | "command": "uv", 243 | "args": [ 244 | "run", 245 | "--directory", 246 | "/home/YOUR_USERNAME/.local/share/UnityMCP/UnityMcpServer/src", 247 | "server.py" 248 | ] 249 | } 250 | // ... other servers might be here ... 251 | } 252 | } 253 | ``` 254 | 255 | (Replace YOUR_USERNAME) 256 | 257 | 258 | </details> 259 | 260 | --- 261 | 262 | ## Usage ▶️ 263 | 264 | 1. **Open your Unity Project.** The MCP for Unity package should connect automatically. Check status via Window > MCP for Unity. 265 | 266 | 2. **Start your MCP Client** (Claude, Cursor, etc.). It should automatically launch the MCP for Unity Server (Python) using the configuration from Installation Step 2. 267 | 268 | 3. **Interact!** Unity tools should now be available in your MCP Client. 269 | 270 | Example Prompt: `Create a 3D player controller`, `Create a tic-tac-toe game in 3D`, `Create a cool shader and apply to a cube`. 271 | 272 | --- 273 | 274 | ## Development & Contributing 🛠️ 275 | 276 | ### Adding Custom Tools 277 | 278 | MCP for Unity uses a Python MCP Server tied with Unity's C# scripts for tools. If you'd like to extend the functionality with your own tools, learn how to do so in **[CUSTOM_TOOLS.md](docs/CUSTOM_TOOLS.md)**. 279 | 280 | ### Contributing to the Project 281 | 282 | If you're contributing to MCP for Unity or want to test core changes, we have development tools to streamline your workflow: 283 | 284 | - **Development Deployment Scripts**: Quickly deploy and test your changes to MCP for Unity Bridge and Python Server 285 | - **Automatic Backup System**: Safe testing with easy rollback capabilities 286 | - **Hot Reload Workflow**: Fast iteration cycle for core development 287 | 288 | 📖 **See [README-DEV.md](docs/README-DEV.md)** for complete development setup and workflow documentation. 289 | 290 | ### Contributing 🤝 291 | 292 | Help make MCP for Unity better! 293 | 294 | 1. **Fork** the main repository. 295 | 2. **Create a branch** (`feature/your-idea` or `bugfix/your-fix`). 296 | 3. **Make changes.** 297 | 4. **Commit** (feat: Add cool new feature). 298 | 5. **Push** your branch. 299 | 6. **Open a Pull Request** against the main branch. 300 | 301 | --- 302 | 303 | ## 📊 Telemetry & Privacy 304 | 305 | MCP for Unity includes **privacy-focused, anonymous telemetry** to help us improve the product. We collect usage analytics and performance data, but **never** your code, project names, or personal information. 306 | 307 | - **🔒 Anonymous**: Random UUIDs only, no personal data 308 | - **🚫 Easy opt-out**: Set `DISABLE_TELEMETRY=true` environment variable 309 | - **📖 Transparent**: See [TELEMETRY.md](docs/TELEMETRY.md) for full details 310 | 311 | Your privacy matters to us. All telemetry is optional and designed to respect your workflow. 312 | 313 | --- 314 | 315 | ## Troubleshooting ❓ 316 | 317 | <details> 318 | <summary><strong>Click to view common issues and fixes...</strong></summary> 319 | 320 | - **Unity Bridge Not Running/Connecting:** 321 | - Ensure Unity Editor is open. 322 | - Check the status window: Window > MCP for Unity. 323 | - Restart Unity. 324 | - **MCP Client Not Connecting / Server Not Starting:** 325 | - **Verify Server Path:** Double-check the --directory path in your MCP Client's JSON config. It must exactly match the installation location: 326 | - **Windows:** `%USERPROFILE%\AppData\Local\UnityMCP\UnityMcpServer\src` 327 | - **macOS:** `~/Library/AppSupport/UnityMCP/UnityMcpServer\src` 328 | - **Linux:** `~/.local/share/UnityMCP/UnityMcpServer\src` 329 | - **Verify uv:** Make sure `uv` is installed and working (`uv --version`). 330 | - **Run Manually:** Try running the server directly from the terminal to see errors: 331 | ```bash 332 | cd /path/to/your/UnityMCP/UnityMcpServer/src 333 | uv run server.py 334 | ``` 335 | - **Auto-Configure Failed:** 336 | - Use the Manual Configuration steps. Auto-configure might lack permissions to write to the MCP client's config file. 337 | 338 | </details> 339 | 340 | Still stuck? [Open an Issue](https://github.com/CoplayDev/unity-mcp/issues) or [Join the Discord](https://discord.gg/y4p8KfzrN4)! 341 | 342 | --- 343 | 344 | ## License 📜 345 | 346 | MIT License. See [LICENSE](LICENSE) file. 347 | 348 | --- 349 | 350 | ## Star History 351 | 352 | [](https://www.star-history.com/#CoplayDev/unity-mcp&Date) 353 | 354 | ## Unity AI Tools by Coplay 355 | 356 | Coplay offers 2 AI tools for Unity 357 | - **MCP for Unity** is available freely under the MIT license. 358 | - **Coplay** is a premium Unity AI assistant that sits within Unity and is more than the MCP for Unity. 359 | 360 | (These tools have different tech stacks. See this blog post [comparing Coplay to MCP for Unity](https://www.coplay.dev/blog/comparing-coplay-and-unity-mcp).) 361 | 362 | ## Disclaimer 363 | 364 | This project is a free and open-source tool for the Unity Editor, and is not affiliated with Unity Technologies. 365 | ``` -------------------------------------------------------------------------------- /MCPForUnity/UnityMcpServer~/src/__init__.py: -------------------------------------------------------------------------------- ```python 1 | ``` -------------------------------------------------------------------------------- /MCPForUnity/UnityMcpServer~/src/server_version.txt: -------------------------------------------------------------------------------- ``` 1 | 6.2.0 2 | ``` -------------------------------------------------------------------------------- /UnityMcpBridge/UnityMcpServer~/src/server_version.txt: -------------------------------------------------------------------------------- ``` 1 | 4.1.1 2 | ``` -------------------------------------------------------------------------------- /UnityMcpBridge/UnityMcpServer~/src/__init__.py: -------------------------------------------------------------------------------- ```python 1 | """ 2 | MCP for Unity Server package. 3 | """ 4 | ``` -------------------------------------------------------------------------------- /TestProjects/UnityMCPTests/ProjectSettings/ProjectVersion.txt: -------------------------------------------------------------------------------- ``` 1 | m_EditorVersion: 2021.3.45f2 2 | m_EditorVersionWithRevision: 2021.3.45f2 (88f88f591b2e) 3 | ``` -------------------------------------------------------------------------------- /MCPForUnity/Editor/AssemblyInfo.cs: -------------------------------------------------------------------------------- ```csharp 1 | using System.Runtime.CompilerServices; 2 | 3 | [assembly: InternalsVisibleTo("MCPForUnityTests.EditMode")] 4 | ``` -------------------------------------------------------------------------------- /UnityMcpBridge/Editor/AssemblyInfo.cs: -------------------------------------------------------------------------------- ```csharp 1 | using System.Runtime.CompilerServices; 2 | 3 | [assembly: InternalsVisibleTo("MCPForUnityTests.EditMode")] 4 | ``` -------------------------------------------------------------------------------- /TestProjects/UnityMCPTests/Assets/Scripts/Hello.cs: -------------------------------------------------------------------------------- ```csharp 1 | using UnityEngine; 2 | using System.Collections; 3 | 4 | public class Hello : MonoBehaviour 5 | { 6 | void Start() 7 | { 8 | Debug.Log("Hello World"); 9 | } 10 | } 11 | ``` -------------------------------------------------------------------------------- /TestProjects/UnityMCPTests/ProjectSettings/Packages/com.unity.testtools.codecoverage/Settings.json: -------------------------------------------------------------------------------- ```json 1 | { 2 | "m_Name": "Settings", 3 | "m_Path": "ProjectSettings/Packages/com.unity.testtools.codecoverage/Settings.json", 4 | "m_Dictionary": { 5 | "m_DictionaryValues": [] 6 | } 7 | } ``` -------------------------------------------------------------------------------- /MCPForUnity/UnityMcpServer~/src/pyrightconfig.json: -------------------------------------------------------------------------------- ```json 1 | { 2 | "typeCheckingMode": "basic", 3 | "reportMissingImports": "none", 4 | "pythonVersion": "3.11", 5 | "executionEnvironments": [ 6 | { 7 | "root": ".", 8 | "pythonVersion": "3.11" 9 | } 10 | ] 11 | } 12 | ``` -------------------------------------------------------------------------------- /UnityMcpBridge/UnityMcpServer~/src/pyrightconfig.json: -------------------------------------------------------------------------------- ```json 1 | { 2 | "typeCheckingMode": "basic", 3 | "reportMissingImports": "none", 4 | "pythonVersion": "3.11", 5 | "executionEnvironments": [ 6 | { 7 | "root": ".", 8 | "pythonVersion": "3.11" 9 | } 10 | ] 11 | } 12 | ``` -------------------------------------------------------------------------------- /MCPForUnity/UnityMcpServer~/src/models.py: -------------------------------------------------------------------------------- ```python 1 | from typing import Any 2 | from pydantic import BaseModel 3 | 4 | 5 | class MCPResponse(BaseModel): 6 | success: bool 7 | message: str | None = None 8 | error: str | None = None 9 | data: Any | None = None 10 | ``` -------------------------------------------------------------------------------- /MCPForUnity/Editor/Models/McpTypes.cs: -------------------------------------------------------------------------------- ```csharp 1 | namespace MCPForUnity.Editor.Models 2 | { 3 | public enum McpTypes 4 | { 5 | ClaudeCode, 6 | ClaudeDesktop, 7 | Codex, 8 | Cursor, 9 | Kiro, 10 | VSCode, 11 | Windsurf, 12 | } 13 | } 14 | ``` -------------------------------------------------------------------------------- /UnityMcpBridge/Editor/Models/McpTypes.cs: -------------------------------------------------------------------------------- ```csharp 1 | namespace MCPForUnity.Editor.Models 2 | { 3 | public enum McpTypes 4 | { 5 | ClaudeCode, 6 | ClaudeDesktop, 7 | Codex, 8 | Cursor, 9 | Kiro, 10 | VSCode, 11 | Windsurf, 12 | } 13 | } 14 | ``` -------------------------------------------------------------------------------- /MCPForUnity/Editor/Models/McpConfig.cs: -------------------------------------------------------------------------------- ```csharp 1 | using System; 2 | using Newtonsoft.Json; 3 | 4 | namespace MCPForUnity.Editor.Models 5 | { 6 | [Serializable] 7 | public class McpConfig 8 | { 9 | [JsonProperty("mcpServers")] 10 | public McpConfigServers mcpServers; 11 | } 12 | } 13 | ``` -------------------------------------------------------------------------------- /UnityMcpBridge/Editor/Models/McpConfig.cs: -------------------------------------------------------------------------------- ```csharp 1 | using System; 2 | using Newtonsoft.Json; 3 | 4 | namespace MCPForUnity.Editor.Models 5 | { 6 | [Serializable] 7 | public class McpConfig 8 | { 9 | [JsonProperty("mcpServers")] 10 | public McpConfigServers mcpServers; 11 | } 12 | } 13 | ``` -------------------------------------------------------------------------------- /MCPForUnity/Editor/Models/MCPConfigServers.cs: -------------------------------------------------------------------------------- ```csharp 1 | using System; 2 | using Newtonsoft.Json; 3 | 4 | namespace MCPForUnity.Editor.Models 5 | { 6 | [Serializable] 7 | public class McpConfigServers 8 | { 9 | [JsonProperty("unityMCP")] 10 | public McpConfigServer unityMCP; 11 | } 12 | } 13 | ``` -------------------------------------------------------------------------------- /UnityMcpBridge/Editor/Models/MCPConfigServers.cs: -------------------------------------------------------------------------------- ```csharp 1 | using System; 2 | using Newtonsoft.Json; 3 | 4 | namespace MCPForUnity.Editor.Models 5 | { 6 | [Serializable] 7 | public class McpConfigServers 8 | { 9 | [JsonProperty("unityMCP")] 10 | public McpConfigServer unityMCP; 11 | } 12 | } 13 | ``` -------------------------------------------------------------------------------- /MCPForUnity/UnityMcpServer~/src/Dockerfile: -------------------------------------------------------------------------------- ```dockerfile 1 | FROM python:3.13-slim 2 | 3 | RUN apt-get update && apt-get install -y --no-install-recommends \ 4 | git \ 5 | && rm -rf /var/lib/apt/lists/* 6 | 7 | WORKDIR /app 8 | 9 | RUN pip install uv 10 | 11 | COPY . /app 12 | 13 | RUN uv sync 14 | 15 | CMD ["uv", "run", "server.py"] 16 | ``` -------------------------------------------------------------------------------- /UnityMcpBridge/UnityMcpServer~/src/registry/__init__.py: -------------------------------------------------------------------------------- ```python 1 | """ 2 | Registry package for MCP tool auto-discovery. 3 | """ 4 | from .tool_registry import ( 5 | mcp_for_unity_tool, 6 | get_registered_tools, 7 | clear_registry 8 | ) 9 | 10 | __all__ = [ 11 | 'mcp_for_unity_tool', 12 | 'get_registered_tools', 13 | 'clear_registry' 14 | ] 15 | ``` -------------------------------------------------------------------------------- /.claude/settings.json: -------------------------------------------------------------------------------- ```json 1 | { 2 | "permissions": { 3 | "allow": [ 4 | "mcp__unity", 5 | "Edit(reports/**)", 6 | "MultiEdit(reports/**)" 7 | ], 8 | "deny": [ 9 | "Bash", 10 | "WebFetch", 11 | "WebSearch", 12 | "Task", 13 | "TodoWrite", 14 | "NotebookEdit", 15 | "NotebookRead" 16 | ] 17 | } 18 | } 19 | ``` -------------------------------------------------------------------------------- /tests/conftest.py: -------------------------------------------------------------------------------- ```python 1 | import os 2 | 3 | # Ensure telemetry is disabled during test collection and execution to avoid 4 | # any background network or thread startup that could slow or block pytest. 5 | os.environ.setdefault("DISABLE_TELEMETRY", "true") 6 | os.environ.setdefault("UNITY_MCP_DISABLE_TELEMETRY", "true") 7 | os.environ.setdefault("MCP_DISABLE_TELEMETRY", "true") 8 | ``` -------------------------------------------------------------------------------- /scripts/validate-nlt-coverage.sh: -------------------------------------------------------------------------------- ```bash 1 | #!/usr/bin/env bash 2 | set -euo pipefail 3 | cd "$(git rev-parse --show-toplevel)" 4 | missing=() 5 | for id in NL-0 NL-1 NL-2 NL-3 NL-4 T-A T-B T-C T-D T-E T-F T-G T-H T-I T-J; do 6 | [[ -s "reports/${id}_results.xml" ]] || missing+=("$id") 7 | done 8 | if (( ${#missing[@]} )); then 9 | echo "Missing fragments: ${missing[*]}" 10 | exit 2 11 | fi 12 | echo "All NL/T fragments present." 13 | ``` -------------------------------------------------------------------------------- /MCPForUnity/UnityMcpServer~/src/reload_sentinel.py: -------------------------------------------------------------------------------- ```python 1 | """ 2 | Deprecated: Sentinel flipping is handled inside Unity via the MCP menu 3 | 'MCP/Flip Reload Sentinel'. This module remains only as a compatibility shim. 4 | All functions are no-ops to prevent accidental external writes. 5 | """ 6 | 7 | 8 | def flip_reload_sentinel(*args, **kwargs) -> str: 9 | return "reload_sentinel.py is deprecated; use execute_menu_item → 'MCP/Flip Reload Sentinel'" 10 | ``` -------------------------------------------------------------------------------- /UnityMcpBridge/UnityMcpServer~/src/reload_sentinel.py: -------------------------------------------------------------------------------- ```python 1 | """ 2 | Deprecated: Sentinel flipping is handled inside Unity via the MCP menu 3 | 'MCP/Flip Reload Sentinel'. This module remains only as a compatibility shim. 4 | All functions are no-ops to prevent accidental external writes. 5 | """ 6 | 7 | 8 | def flip_reload_sentinel(*args, **kwargs) -> str: 9 | return "reload_sentinel.py is deprecated; use execute_menu_item → 'MCP/Flip Reload Sentinel'" 10 | ``` -------------------------------------------------------------------------------- /TestProjects/UnityMCPTests/Assets/Scripts/TestAsmdef/CustomComponent.cs: -------------------------------------------------------------------------------- ```csharp 1 | using UnityEngine; 2 | 3 | namespace TestNamespace 4 | { 5 | public class CustomComponent : MonoBehaviour 6 | { 7 | [SerializeField] 8 | private string customText = "Hello from custom asmdef!"; 9 | 10 | [SerializeField] 11 | private float customFloat = 42.0f; 12 | 13 | void Start() 14 | { 15 | Debug.Log($"CustomComponent started: {customText}, value: {customFloat}"); 16 | } 17 | } 18 | } 19 | ``` -------------------------------------------------------------------------------- /MCPForUnity/Editor/Services/IPythonToolRegistryService.cs: -------------------------------------------------------------------------------- ```csharp 1 | using System.Collections.Generic; 2 | using UnityEngine; 3 | using MCPForUnity.Editor.Data; 4 | 5 | namespace MCPForUnity.Editor.Services 6 | { 7 | public interface IPythonToolRegistryService 8 | { 9 | IEnumerable<PythonToolsAsset> GetAllRegistries(); 10 | bool NeedsSync(PythonToolsAsset registry, TextAsset file); 11 | void RecordSync(PythonToolsAsset registry, TextAsset file); 12 | string ComputeHash(TextAsset file); 13 | } 14 | } 15 | ``` -------------------------------------------------------------------------------- /MCPForUnity/Editor/Models/MCPConfigServer.cs: -------------------------------------------------------------------------------- ```csharp 1 | using System; 2 | using Newtonsoft.Json; 3 | 4 | namespace MCPForUnity.Editor.Models 5 | { 6 | [Serializable] 7 | public class McpConfigServer 8 | { 9 | [JsonProperty("command")] 10 | public string command; 11 | 12 | [JsonProperty("args")] 13 | public string[] args; 14 | 15 | // VSCode expects a transport type; include only when explicitly set 16 | [JsonProperty("type", NullValueHandling = NullValueHandling.Ignore)] 17 | public string type; 18 | } 19 | } 20 | ``` -------------------------------------------------------------------------------- /UnityMcpBridge/Editor/Models/MCPConfigServer.cs: -------------------------------------------------------------------------------- ```csharp 1 | using System; 2 | using Newtonsoft.Json; 3 | 4 | namespace MCPForUnity.Editor.Models 5 | { 6 | [Serializable] 7 | public class McpConfigServer 8 | { 9 | [JsonProperty("command")] 10 | public string command; 11 | 12 | [JsonProperty("args")] 13 | public string[] args; 14 | 15 | // VSCode expects a transport type; include only when explicitly set 16 | [JsonProperty("type", NullValueHandling = NullValueHandling.Ignore)] 17 | public string type; 18 | } 19 | } 20 | ``` -------------------------------------------------------------------------------- /MCPForUnity/Editor/Models/Command.cs: -------------------------------------------------------------------------------- ```csharp 1 | using Newtonsoft.Json.Linq; 2 | 3 | namespace MCPForUnity.Editor.Models 4 | { 5 | /// <summary> 6 | /// Represents a command received from the MCP client 7 | /// </summary> 8 | public class Command 9 | { 10 | /// <summary> 11 | /// The type of command to execute 12 | /// </summary> 13 | public string type { get; set; } 14 | 15 | /// <summary> 16 | /// The parameters for the command 17 | /// </summary> 18 | public JObject @params { get; set; } 19 | } 20 | } 21 | 22 | ``` -------------------------------------------------------------------------------- /UnityMcpBridge/Editor/Models/Command.cs: -------------------------------------------------------------------------------- ```csharp 1 | using Newtonsoft.Json.Linq; 2 | 3 | namespace MCPForUnity.Editor.Models 4 | { 5 | /// <summary> 6 | /// Represents a command received from the MCP client 7 | /// </summary> 8 | public class Command 9 | { 10 | /// <summary> 11 | /// The type of command to execute 12 | /// </summary> 13 | public string type { get; set; } 14 | 15 | /// <summary> 16 | /// The parameters for the command 17 | /// </summary> 18 | public JObject @params { get; set; } 19 | } 20 | } 21 | 22 | ``` -------------------------------------------------------------------------------- /MCPForUnity/UnityMcpServer~/src/registry/__init__.py: -------------------------------------------------------------------------------- ```python 1 | """ 2 | Registry package for MCP tool auto-discovery. 3 | """ 4 | from .tool_registry import ( 5 | mcp_for_unity_tool, 6 | get_registered_tools, 7 | clear_tool_registry, 8 | ) 9 | from .resource_registry import ( 10 | mcp_for_unity_resource, 11 | get_registered_resources, 12 | clear_resource_registry, 13 | ) 14 | 15 | __all__ = [ 16 | 'mcp_for_unity_tool', 17 | 'get_registered_tools', 18 | 'clear_tool_registry', 19 | 'mcp_for_unity_resource', 20 | 'get_registered_resources', 21 | 'clear_resource_registry' 22 | ] 23 | ``` -------------------------------------------------------------------------------- /MCPForUnity/Editor/Services/IToolSyncService.cs: -------------------------------------------------------------------------------- ```csharp 1 | using System.Collections.Generic; 2 | 3 | namespace MCPForUnity.Editor.Services 4 | { 5 | public class ToolSyncResult 6 | { 7 | public int CopiedCount { get; set; } 8 | public int SkippedCount { get; set; } 9 | public int ErrorCount { get; set; } 10 | public List<string> Messages { get; set; } = new List<string>(); 11 | public bool Success => ErrorCount == 0; 12 | } 13 | 14 | public interface IToolSyncService 15 | { 16 | ToolSyncResult SyncProjectTools(string destToolsDir); 17 | } 18 | } 19 | ``` -------------------------------------------------------------------------------- /UnityMcpBridge/UnityMcpServer~/src/pyproject.toml: -------------------------------------------------------------------------------- ```toml 1 | [project] 2 | name = "MCPForUnityServer" 3 | version = "4.1.1" 4 | description = "MCP for Unity Server: A Unity package for Unity Editor integration via the Model Context Protocol (MCP)." 5 | readme = "README.md" 6 | requires-python = ">=3.10" 7 | dependencies = [ 8 | "httpx>=0.27.2", 9 | "mcp[cli]>=1.15.0", 10 | "tomli>=2.3.0", 11 | ] 12 | 13 | [build-system] 14 | requires = ["setuptools>=64.0.0", "wheel"] 15 | build-backend = "setuptools.build_meta" 16 | 17 | [tool.setuptools] 18 | py-modules = ["config", "server", "unity_connection"] 19 | packages = ["tools"] 20 | ``` -------------------------------------------------------------------------------- /MCPForUnity/UnityMcpServer~/src/pyproject.toml: -------------------------------------------------------------------------------- ```toml 1 | [project] 2 | name = "MCPForUnityServer" 3 | version = "6.2.0" 4 | description = "MCP for Unity Server: A Unity package for Unity Editor integration via the Model Context Protocol (MCP)." 5 | readme = "README.md" 6 | requires-python = ">=3.10" 7 | dependencies = [ 8 | "httpx>=0.27.2", 9 | "mcp[cli]>=1.17.0", 10 | "pydantic>=2.12.0", 11 | "tomli>=2.3.0", 12 | ] 13 | 14 | [build-system] 15 | requires = ["setuptools>=64.0.0", "wheel"] 16 | build-backend = "setuptools.build_meta" 17 | 18 | [tool.setuptools] 19 | py-modules = ["config", "server", "unity_connection"] 20 | packages = ["tools"] 21 | ``` -------------------------------------------------------------------------------- /.github/workflows/github-repo-stats.yml: -------------------------------------------------------------------------------- ```yaml 1 | name: github-repo-stats 2 | 3 | on: 4 | schedule: 5 | # Run this once per day, towards the end of the day for keeping the most 6 | # recent data point most meaningful (hours are interpreted in UTC). 7 | - cron: "0 23 * * *" 8 | workflow_dispatch: # Allow for running this manually. 9 | 10 | jobs: 11 | j1: 12 | name: github-repo-stats 13 | runs-on: ubuntu-latest 14 | steps: 15 | - name: run-ghrs 16 | # Use latest release. 17 | uses: jgehrcke/github-repo-stats@RELEASE 18 | with: 19 | ghtoken: ${{ secrets.ghrs_github_api_token }} 20 | ``` -------------------------------------------------------------------------------- /UnityMcpBridge/UnityMcpServer~/src/Dockerfile: -------------------------------------------------------------------------------- ```dockerfile 1 | FROM python:3.12-slim 2 | 3 | # Install required system dependencies 4 | RUN apt-get update && apt-get install -y --no-install-recommends \ 5 | git \ 6 | && rm -rf /var/lib/apt/lists/* 7 | 8 | # Set working directory 9 | WORKDIR /app 10 | 11 | # Install uv package manager 12 | RUN pip install uv 13 | 14 | # Copy required files 15 | COPY config.py /app/ 16 | COPY server.py /app/ 17 | COPY unity_connection.py /app/ 18 | COPY pyproject.toml /app/ 19 | COPY __init__.py /app/ 20 | COPY tools/ /app/tools/ 21 | 22 | # Install dependencies using uv 23 | RUN uv pip install --system -e . 24 | 25 | 26 | # Command to run the server 27 | CMD ["uv", "run", "server.py"] 28 | ``` -------------------------------------------------------------------------------- /MCPForUnity/Editor/Data/DefaultServerConfig.cs: -------------------------------------------------------------------------------- ```csharp 1 | using MCPForUnity.Editor.Models; 2 | 3 | namespace MCPForUnity.Editor.Data 4 | { 5 | public class DefaultServerConfig : ServerConfig 6 | { 7 | public new string unityHost = "localhost"; 8 | public new int unityPort = 6400; 9 | public new int mcpPort = 6500; 10 | public new float connectionTimeout = 15.0f; 11 | public new int bufferSize = 32768; 12 | public new string logLevel = "INFO"; 13 | public new string logFormat = "%(asctime)s - %(name)s - %(levelname)s - %(message)s"; 14 | public new int maxRetries = 3; 15 | public new float retryDelay = 1.0f; 16 | } 17 | } 18 | ``` -------------------------------------------------------------------------------- /UnityMcpBridge/Editor/Data/DefaultServerConfig.cs: -------------------------------------------------------------------------------- ```csharp 1 | using MCPForUnity.Editor.Models; 2 | 3 | namespace MCPForUnity.Editor.Data 4 | { 5 | public class DefaultServerConfig : ServerConfig 6 | { 7 | public new string unityHost = "localhost"; 8 | public new int unityPort = 6400; 9 | public new int mcpPort = 6500; 10 | public new float connectionTimeout = 15.0f; 11 | public new int bufferSize = 32768; 12 | public new string logLevel = "INFO"; 13 | public new string logFormat = "%(asctime)s - %(name)s - %(levelname)s - %(message)s"; 14 | public new int maxRetries = 3; 15 | public new float retryDelay = 1.0f; 16 | } 17 | } 18 | ``` -------------------------------------------------------------------------------- /MCPForUnity/Editor/Models/McpStatus.cs: -------------------------------------------------------------------------------- ```csharp 1 | namespace MCPForUnity.Editor.Models 2 | { 3 | // Enum representing the various status states for MCP clients 4 | public enum McpStatus 5 | { 6 | NotConfigured, // Not set up yet 7 | Configured, // Successfully configured 8 | Running, // Service is running 9 | Connected, // Successfully connected 10 | IncorrectPath, // Configuration has incorrect paths 11 | CommunicationError, // Connected but communication issues 12 | NoResponse, // Connected but not responding 13 | MissingConfig, // Config file exists but missing required elements 14 | UnsupportedOS, // OS is not supported 15 | Error, // General error state 16 | } 17 | } 18 | 19 | ``` -------------------------------------------------------------------------------- /UnityMcpBridge/Editor/Models/McpStatus.cs: -------------------------------------------------------------------------------- ```csharp 1 | namespace MCPForUnity.Editor.Models 2 | { 3 | // Enum representing the various status states for MCP clients 4 | public enum McpStatus 5 | { 6 | NotConfigured, // Not set up yet 7 | Configured, // Successfully configured 8 | Running, // Service is running 9 | Connected, // Successfully connected 10 | IncorrectPath, // Configuration has incorrect paths 11 | CommunicationError, // Connected but communication issues 12 | NoResponse, // Connected but not responding 13 | MissingConfig, // Config file exists but missing required elements 14 | UnsupportedOS, // OS is not supported 15 | Error, // General error state 16 | } 17 | } 18 | 19 | ``` -------------------------------------------------------------------------------- /MCPForUnity/Editor/Importers/PythonFileImporter.cs: -------------------------------------------------------------------------------- ```csharp 1 | using UnityEngine; 2 | using UnityEditor.AssetImporters; 3 | using System.IO; 4 | 5 | namespace MCPForUnity.Editor.Importers 6 | { 7 | /// <summary> 8 | /// Custom importer that allows Unity to recognize .py files as TextAssets. 9 | /// This enables Python files to be selected in the Inspector and used like any other text asset. 10 | /// </summary> 11 | [ScriptedImporter(1, "py")] 12 | public class PythonFileImporter : ScriptedImporter 13 | { 14 | public override void OnImportAsset(AssetImportContext ctx) 15 | { 16 | var textAsset = new TextAsset(File.ReadAllText(ctx.assetPath)); 17 | ctx.AddObjectToAsset("main obj", textAsset); 18 | ctx.SetMainObject(textAsset); 19 | } 20 | } 21 | } 22 | ``` -------------------------------------------------------------------------------- /MCPForUnity/Editor/Services/ITestRunnerService.cs: -------------------------------------------------------------------------------- ```csharp 1 | using System.Collections.Generic; 2 | using System.Threading.Tasks; 3 | using UnityEditor.TestTools.TestRunner.Api; 4 | 5 | namespace MCPForUnity.Editor.Services 6 | { 7 | /// <summary> 8 | /// Provides access to Unity Test Runner data and execution. 9 | /// </summary> 10 | public interface ITestRunnerService 11 | { 12 | /// <summary> 13 | /// Retrieve the list of tests for the requested mode(s). 14 | /// When <paramref name="mode"/> is null, tests for both EditMode and PlayMode are returned. 15 | /// </summary> 16 | Task<IReadOnlyList<Dictionary<string, string>>> GetTestsAsync(TestMode? mode); 17 | 18 | /// <summary> 19 | /// Execute tests for the supplied mode. 20 | /// </summary> 21 | Task<TestRunResult> RunTestsAsync(TestMode mode); 22 | } 23 | } 24 | ``` -------------------------------------------------------------------------------- /MCPForUnity/Editor/Models/ServerConfig.cs: -------------------------------------------------------------------------------- ```csharp 1 | using System; 2 | using Newtonsoft.Json; 3 | 4 | namespace MCPForUnity.Editor.Models 5 | { 6 | [Serializable] 7 | public class ServerConfig 8 | { 9 | [JsonProperty("unity_host")] 10 | public string unityHost = "localhost"; 11 | 12 | [JsonProperty("unity_port")] 13 | public int unityPort; 14 | 15 | [JsonProperty("mcp_port")] 16 | public int mcpPort; 17 | 18 | [JsonProperty("connection_timeout")] 19 | public float connectionTimeout; 20 | 21 | [JsonProperty("buffer_size")] 22 | public int bufferSize; 23 | 24 | [JsonProperty("log_level")] 25 | public string logLevel; 26 | 27 | [JsonProperty("log_format")] 28 | public string logFormat; 29 | 30 | [JsonProperty("max_retries")] 31 | public int maxRetries; 32 | 33 | [JsonProperty("retry_delay")] 34 | public float retryDelay; 35 | } 36 | } 37 | ``` -------------------------------------------------------------------------------- /UnityMcpBridge/Editor/Models/ServerConfig.cs: -------------------------------------------------------------------------------- ```csharp 1 | using System; 2 | using Newtonsoft.Json; 3 | 4 | namespace MCPForUnity.Editor.Models 5 | { 6 | [Serializable] 7 | public class ServerConfig 8 | { 9 | [JsonProperty("unity_host")] 10 | public string unityHost = "localhost"; 11 | 12 | [JsonProperty("unity_port")] 13 | public int unityPort; 14 | 15 | [JsonProperty("mcp_port")] 16 | public int mcpPort; 17 | 18 | [JsonProperty("connection_timeout")] 19 | public float connectionTimeout; 20 | 21 | [JsonProperty("buffer_size")] 22 | public int bufferSize; 23 | 24 | [JsonProperty("log_level")] 25 | public string logLevel; 26 | 27 | [JsonProperty("log_format")] 28 | public string logFormat; 29 | 30 | [JsonProperty("max_retries")] 31 | public int maxRetries; 32 | 33 | [JsonProperty("retry_delay")] 34 | public float retryDelay; 35 | } 36 | } 37 | ``` -------------------------------------------------------------------------------- /UnityMcpBridge/Editor/Helpers/AssetPathUtility.cs: -------------------------------------------------------------------------------- ```csharp 1 | using System; 2 | 3 | namespace MCPForUnity.Editor.Helpers 4 | { 5 | /// <summary> 6 | /// Provides common utility methods for working with Unity asset paths. 7 | /// </summary> 8 | public static class AssetPathUtility 9 | { 10 | /// <summary> 11 | /// Normalizes a Unity asset path by ensuring forward slashes are used and that it is rooted under "Assets/". 12 | /// </summary> 13 | public static string SanitizeAssetPath(string path) 14 | { 15 | if (string.IsNullOrEmpty(path)) 16 | { 17 | return path; 18 | } 19 | 20 | path = path.Replace('\\', '/'); 21 | if (!path.StartsWith("Assets/", StringComparison.OrdinalIgnoreCase)) 22 | { 23 | return "Assets/" + path.TrimStart('/'); 24 | } 25 | 26 | return path; 27 | } 28 | } 29 | } 30 | ``` -------------------------------------------------------------------------------- /MCPForUnity/UnityMcpServer~/src/resources/menu_items.py: -------------------------------------------------------------------------------- ```python 1 | from models import MCPResponse 2 | from registry import mcp_for_unity_resource 3 | from unity_connection import async_send_command_with_retry 4 | 5 | 6 | class GetMenuItemsResponse(MCPResponse): 7 | data: list[str] = [] 8 | 9 | 10 | @mcp_for_unity_resource( 11 | uri="mcpforunity://menu-items", 12 | name="get_menu_items", 13 | description="Provides a list of all menu items." 14 | ) 15 | async def get_menu_items() -> GetMenuItemsResponse: 16 | """Provides a list of all menu items.""" 17 | # Later versions of FastMCP support these as query parameters 18 | # See: https://gofastmcp.com/servers/resources#query-parameters 19 | params = { 20 | "refresh": True, 21 | "search": "", 22 | } 23 | 24 | response = await async_send_command_with_retry("get_menu_items", params) 25 | return GetMenuItemsResponse(**response) if isinstance(response, dict) else response 26 | ``` -------------------------------------------------------------------------------- /MCPForUnity/Editor/Helpers/Vector3Helper.cs: -------------------------------------------------------------------------------- ```csharp 1 | using Newtonsoft.Json.Linq; 2 | using UnityEngine; 3 | 4 | namespace MCPForUnity.Editor.Helpers 5 | { 6 | /// <summary> 7 | /// Helper class for Vector3 operations 8 | /// </summary> 9 | public static class Vector3Helper 10 | { 11 | /// <summary> 12 | /// Parses a JArray into a Vector3 13 | /// </summary> 14 | /// <param name="array">The array containing x, y, z coordinates</param> 15 | /// <returns>A Vector3 with the parsed coordinates</returns> 16 | /// <exception cref="System.Exception">Thrown when array is invalid</exception> 17 | public static Vector3 ParseVector3(JArray array) 18 | { 19 | if (array == null || array.Count != 3) 20 | throw new System.Exception("Vector3 must be an array of 3 floats [x, y, z]."); 21 | return new Vector3((float)array[0], (float)array[1], (float)array[2]); 22 | } 23 | } 24 | } 25 | ``` -------------------------------------------------------------------------------- /UnityMcpBridge/Editor/Helpers/Vector3Helper.cs: -------------------------------------------------------------------------------- ```csharp 1 | using Newtonsoft.Json.Linq; 2 | using UnityEngine; 3 | 4 | namespace MCPForUnity.Editor.Helpers 5 | { 6 | /// <summary> 7 | /// Helper class for Vector3 operations 8 | /// </summary> 9 | public static class Vector3Helper 10 | { 11 | /// <summary> 12 | /// Parses a JArray into a Vector3 13 | /// </summary> 14 | /// <param name="array">The array containing x, y, z coordinates</param> 15 | /// <returns>A Vector3 with the parsed coordinates</returns> 16 | /// <exception cref="System.Exception">Thrown when array is invalid</exception> 17 | public static Vector3 ParseVector3(JArray array) 18 | { 19 | if (array == null || array.Count != 3) 20 | throw new System.Exception("Vector3 must be an array of 3 floats [x, y, z]."); 21 | return new Vector3((float)array[0], (float)array[1], (float)array[2]); 22 | } 23 | } 24 | } 25 | ``` -------------------------------------------------------------------------------- /UnityMcpBridge/Editor/Helpers/McpLog.cs: -------------------------------------------------------------------------------- ```csharp 1 | using UnityEditor; 2 | using UnityEngine; 3 | 4 | namespace MCPForUnity.Editor.Helpers 5 | { 6 | internal static class McpLog 7 | { 8 | private const string Prefix = "<b><color=#2EA3FF>MCP-FOR-UNITY</color></b>:"; 9 | 10 | private static bool IsDebugEnabled() 11 | { 12 | try { return EditorPrefs.GetBool("MCPForUnity.DebugLogs", false); } catch { return false; } 13 | } 14 | 15 | public static void Info(string message, bool always = true) 16 | { 17 | if (!always && !IsDebugEnabled()) return; 18 | Debug.Log($"{Prefix} {message}"); 19 | } 20 | 21 | public static void Warn(string message) 22 | { 23 | Debug.LogWarning($"<color=#cc7a00>{Prefix} {message}</color>"); 24 | } 25 | 26 | public static void Error(string message) 27 | { 28 | Debug.LogError($"<color=#cc3333>{Prefix} {message}</color>"); 29 | } 30 | } 31 | } 32 | ``` -------------------------------------------------------------------------------- /MCPForUnity/UnityMcpServer~/src/tools/execute_menu_item.py: -------------------------------------------------------------------------------- ```python 1 | """ 2 | Defines the execute_menu_item tool for executing and reading Unity Editor menu items. 3 | """ 4 | from typing import Annotated, Any 5 | 6 | from mcp.server.fastmcp import Context 7 | 8 | from models import MCPResponse 9 | from registry import mcp_for_unity_tool 10 | from unity_connection import async_send_command_with_retry 11 | 12 | 13 | @mcp_for_unity_tool( 14 | description="Execute a Unity menu item by path." 15 | ) 16 | async def execute_menu_item( 17 | ctx: Context, 18 | menu_path: Annotated[str, 19 | "Menu path for 'execute' or 'exists' (e.g., 'File/Save Project')"] | None = None, 20 | ) -> MCPResponse: 21 | await ctx.info(f"Processing execute_menu_item: {menu_path}") 22 | params_dict: dict[str, Any] = {"menuPath": menu_path} 23 | params_dict = {k: v for k, v in params_dict.items() if v is not None} 24 | result = await async_send_command_with_retry("execute_menu_item", params_dict) 25 | return MCPResponse(**result) if isinstance(result, dict) else result 26 | ``` -------------------------------------------------------------------------------- /MCPForUnity/package.json: -------------------------------------------------------------------------------- ```json 1 | { 2 | "name": "com.coplaydev.unity-mcp", 3 | "version": "6.2.0", 4 | "displayName": "MCP for Unity", 5 | "description": "A bridge that connects AI assistants to Unity via the MCP (Model Context Protocol). Allows AI clients like Claude Code, Cursor, and VSCode to directly control your Unity Editor for enhanced development workflows.\n\nFeatures automated setup wizard, cross-platform support, and seamless integration with popular AI development tools.\n\nJoin Our Discord: https://discord.gg/y4p8KfzrN4", 6 | "unity": "2021.3", 7 | "documentationUrl": "https://github.com/CoplayDev/unity-mcp", 8 | "licensesUrl": "https://github.com/CoplayDev/unity-mcp/blob/main/LICENSE", 9 | "dependencies": { 10 | "com.unity.nuget.newtonsoft-json": "3.0.2" 11 | }, 12 | "keywords": [ 13 | "unity", 14 | "ai", 15 | "llm", 16 | "mcp", 17 | "model-context-protocol", 18 | "mcp-server", 19 | "mcp-client" 20 | ], 21 | "author": { 22 | "name": "Coplay", 23 | "email": "[email protected]", 24 | "url": "https://coplay.dev" 25 | } 26 | } 27 | ``` -------------------------------------------------------------------------------- /UnityMcpBridge/package.json: -------------------------------------------------------------------------------- ```json 1 | { 2 | "name": "com.coplaydev.unity-mcp", 3 | "version": "4.1.1", 4 | "displayName": "MCP for Unity", 5 | "description": "A bridge that connects AI assistants to Unity via the MCP (Model Context Protocol). Allows AI clients like Claude Code, Cursor, and VSCode to directly control your Unity Editor for enhanced development workflows.\n\nFeatures automated setup wizard, cross-platform support, and seamless integration with popular AI development tools.\n\nJoin Our Discord: https://discord.gg/y4p8KfzrN4", 6 | "unity": "2021.3", 7 | "documentationUrl": "https://github.com/CoplayDev/unity-mcp", 8 | "licensesUrl": "https://github.com/CoplayDev/unity-mcp/blob/main/LICENSE", 9 | "dependencies": { 10 | "com.unity.nuget.newtonsoft-json": "3.0.2" 11 | }, 12 | "keywords": [ 13 | "unity", 14 | "ai", 15 | "llm", 16 | "mcp", 17 | "model-context-protocol", 18 | "mcp-server", 19 | "mcp-client" 20 | ], 21 | "author": { 22 | "name": "Coplay", 23 | "email": "[email protected]", 24 | "url": "https://coplay.dev" 25 | } 26 | } 27 | ``` -------------------------------------------------------------------------------- /MCPForUnity/Editor/Helpers/McpLog.cs: -------------------------------------------------------------------------------- ```csharp 1 | using UnityEditor; 2 | using UnityEngine; 3 | 4 | namespace MCPForUnity.Editor.Helpers 5 | { 6 | internal static class McpLog 7 | { 8 | private const string LogPrefix = "<b><color=#2EA3FF>MCP-FOR-UNITY</color></b>:"; 9 | private const string WarnPrefix = "<b><color=#cc7a00>MCP-FOR-UNITY</color></b>:"; 10 | private const string ErrorPrefix = "<b><color=#cc3333>MCP-FOR-UNITY</color></b>:"; 11 | 12 | private static bool IsDebugEnabled() 13 | { 14 | try { return EditorPrefs.GetBool("MCPForUnity.DebugLogs", false); } catch { return false; } 15 | } 16 | 17 | public static void Info(string message, bool always = true) 18 | { 19 | if (!always && !IsDebugEnabled()) return; 20 | Debug.Log($"{LogPrefix} {message}"); 21 | } 22 | 23 | public static void Warn(string message) 24 | { 25 | Debug.LogWarning($"{WarnPrefix} {message}"); 26 | } 27 | 28 | public static void Error(string message) 29 | { 30 | Debug.LogError($"{ErrorPrefix} {message}"); 31 | } 32 | } 33 | } 34 | ``` -------------------------------------------------------------------------------- /tests/test_script_editing.py: -------------------------------------------------------------------------------- ```python 1 | import pytest 2 | 3 | 4 | @pytest.mark.xfail(strict=False, reason="pending: create new script, validate, apply edits, build and compile scene") 5 | def test_script_edit_happy_path(): 6 | pass 7 | 8 | 9 | @pytest.mark.xfail(strict=False, reason="pending: multiple micro-edits debounce to single compilation") 10 | def test_micro_edits_debounce(): 11 | pass 12 | 13 | 14 | @pytest.mark.xfail(strict=False, reason="pending: line ending variations handled correctly") 15 | def test_line_endings_and_columns(): 16 | pass 17 | 18 | 19 | @pytest.mark.xfail(strict=False, reason="pending: regex_replace no-op with allow_noop honored") 20 | def test_regex_replace_noop_allowed(): 21 | pass 22 | 23 | 24 | @pytest.mark.xfail(strict=False, reason="pending: large edit size boundaries and overflow protection") 25 | def test_large_edit_size_and_overflow(): 26 | pass 27 | 28 | 29 | @pytest.mark.xfail(strict=False, reason="pending: symlink and junction protections on edits") 30 | def test_symlink_and_junction_protection(): 31 | pass 32 | 33 | 34 | @pytest.mark.xfail(strict=False, reason="pending: atomic write guarantees") 35 | def test_atomic_write_guarantees(): 36 | pass 37 | ``` -------------------------------------------------------------------------------- /tests/test_find_in_file_minimal.py: -------------------------------------------------------------------------------- ```python 1 | from tools.resource_tools import register_resource_tools # type: ignore 2 | import sys 3 | import pathlib 4 | import importlib.util 5 | import types 6 | import asyncio 7 | import pytest 8 | 9 | ROOT = pathlib.Path(__file__).resolve().parents[1] 10 | SRC = ROOT / "MCPForUnity" / "UnityMcpServer~" / "src" 11 | sys.path.insert(0, str(SRC)) 12 | 13 | 14 | class DummyMCP: 15 | def __init__(self): 16 | self.tools = {} 17 | 18 | def tool(self, *args, **kwargs): 19 | def deco(fn): 20 | self.tools[fn.__name__] = fn 21 | return fn 22 | return deco 23 | 24 | 25 | @pytest.fixture() 26 | def resource_tools(): 27 | mcp = DummyMCP() 28 | register_resource_tools(mcp) 29 | return mcp.tools 30 | 31 | 32 | def test_find_in_file_returns_positions(resource_tools, tmp_path): 33 | proj = tmp_path 34 | assets = proj / "Assets" 35 | assets.mkdir() 36 | f = assets / "A.txt" 37 | f.write_text("hello world", encoding="utf-8") 38 | find_in_file = resource_tools["find_in_file"] 39 | loop = asyncio.new_event_loop() 40 | try: 41 | resp = loop.run_until_complete( 42 | find_in_file(uri="unity://path/Assets/A.txt", 43 | pattern="world", ctx=None, project_root=str(proj)) 44 | ) 45 | finally: 46 | loop.close() 47 | assert resp["success"] is True 48 | assert resp["data"]["matches"] == [ 49 | {"startLine": 1, "startCol": 7, "endLine": 1, "endCol": 12}] 50 | ``` -------------------------------------------------------------------------------- /UnityMcpBridge/UnityMcpServer~/src/registry/tool_registry.py: -------------------------------------------------------------------------------- ```python 1 | """ 2 | Tool registry for auto-discovery of MCP tools. 3 | """ 4 | from typing import Callable, Any 5 | 6 | # Global registry to collect decorated tools 7 | _tool_registry: list[dict[str, Any]] = [] 8 | 9 | 10 | def mcp_for_unity_tool( 11 | name: str | None = None, 12 | description: str | None = None, 13 | **kwargs 14 | ) -> Callable: 15 | """ 16 | Decorator for registering MCP tools in the server's tools directory. 17 | 18 | Tools are registered in the global tool registry. 19 | 20 | Args: 21 | name: Tool name (defaults to function name) 22 | description: Tool description 23 | **kwargs: Additional arguments passed to @mcp.tool() 24 | 25 | Example: 26 | @mcp_for_unity_tool(description="Does something cool") 27 | async def my_custom_tool(ctx: Context, ...): 28 | pass 29 | """ 30 | def decorator(func: Callable) -> Callable: 31 | tool_name = name if name is not None else func.__name__ 32 | _tool_registry.append({ 33 | 'func': func, 34 | 'name': tool_name, 35 | 'description': description, 36 | 'kwargs': kwargs 37 | }) 38 | 39 | return func 40 | 41 | return decorator 42 | 43 | 44 | def get_registered_tools() -> list[dict[str, Any]]: 45 | """Get all registered tools""" 46 | return _tool_registry.copy() 47 | 48 | 49 | def clear_registry(): 50 | """Clear the tool registry (useful for testing)""" 51 | _tool_registry.clear() 52 | ``` -------------------------------------------------------------------------------- /MCPForUnity/UnityMcpServer~/src/registry/tool_registry.py: -------------------------------------------------------------------------------- ```python 1 | """ 2 | Tool registry for auto-discovery of MCP tools. 3 | """ 4 | from typing import Callable, Any 5 | 6 | # Global registry to collect decorated tools 7 | _tool_registry: list[dict[str, Any]] = [] 8 | 9 | 10 | def mcp_for_unity_tool( 11 | name: str | None = None, 12 | description: str | None = None, 13 | **kwargs 14 | ) -> Callable: 15 | """ 16 | Decorator for registering MCP tools in the server's tools directory. 17 | 18 | Tools are registered in the global tool registry. 19 | 20 | Args: 21 | name: Tool name (defaults to function name) 22 | description: Tool description 23 | **kwargs: Additional arguments passed to @mcp.tool() 24 | 25 | Example: 26 | @mcp_for_unity_tool(description="Does something cool") 27 | async def my_custom_tool(ctx: Context, ...): 28 | pass 29 | """ 30 | def decorator(func: Callable) -> Callable: 31 | tool_name = name if name is not None else func.__name__ 32 | _tool_registry.append({ 33 | 'func': func, 34 | 'name': tool_name, 35 | 'description': description, 36 | 'kwargs': kwargs 37 | }) 38 | 39 | return func 40 | 41 | return decorator 42 | 43 | 44 | def get_registered_tools() -> list[dict[str, Any]]: 45 | """Get all registered tools""" 46 | return _tool_registry.copy() 47 | 48 | 49 | def clear_tool_registry(): 50 | """Clear the tool registry (useful for testing)""" 51 | _tool_registry.clear() 52 | ``` -------------------------------------------------------------------------------- /MCPForUnity/Editor/Tools/McpForUnityToolAttribute.cs: -------------------------------------------------------------------------------- ```csharp 1 | using System; 2 | 3 | namespace MCPForUnity.Editor.Tools 4 | { 5 | /// <summary> 6 | /// Marks a class as an MCP tool handler for auto-discovery. 7 | /// The class must have a public static HandleCommand(JObject) method. 8 | /// </summary> 9 | [AttributeUsage(AttributeTargets.Class, AllowMultiple = false)] 10 | public class McpForUnityToolAttribute : Attribute 11 | { 12 | /// <summary> 13 | /// The command name used to route requests to this tool. 14 | /// If not specified, defaults to the PascalCase class name converted to snake_case. 15 | /// </summary> 16 | public string CommandName { get; } 17 | 18 | /// <summary> 19 | /// Create an MCP tool attribute with auto-generated command name. 20 | /// The command name will be derived from the class name (PascalCase → snake_case). 21 | /// Example: ManageAsset → manage_asset 22 | /// </summary> 23 | public McpForUnityToolAttribute() 24 | { 25 | CommandName = null; // Will be auto-generated 26 | } 27 | 28 | /// <summary> 29 | /// Create an MCP tool attribute with explicit command name. 30 | /// </summary> 31 | /// <param name="commandName">The command name (e.g., "manage_asset")</param> 32 | public McpForUnityToolAttribute(string commandName) 33 | { 34 | CommandName = commandName; 35 | } 36 | } 37 | } 38 | ``` -------------------------------------------------------------------------------- /UnityMcpBridge/Editor/Tools/McpForUnityToolAttribute.cs: -------------------------------------------------------------------------------- ```csharp 1 | using System; 2 | 3 | namespace MCPForUnity.Editor.Tools 4 | { 5 | /// <summary> 6 | /// Marks a class as an MCP tool handler for auto-discovery. 7 | /// The class must have a public static HandleCommand(JObject) method. 8 | /// </summary> 9 | [AttributeUsage(AttributeTargets.Class, AllowMultiple = false)] 10 | public class McpForUnityToolAttribute : Attribute 11 | { 12 | /// <summary> 13 | /// The command name used to route requests to this tool. 14 | /// If not specified, defaults to the PascalCase class name converted to snake_case. 15 | /// </summary> 16 | public string CommandName { get; } 17 | 18 | /// <summary> 19 | /// Create an MCP tool attribute with auto-generated command name. 20 | /// The command name will be derived from the class name (PascalCase → snake_case). 21 | /// Example: ManageAsset → manage_asset 22 | /// </summary> 23 | public McpForUnityToolAttribute() 24 | { 25 | CommandName = null; // Will be auto-generated 26 | } 27 | 28 | /// <summary> 29 | /// Create an MCP tool attribute with explicit command name. 30 | /// </summary> 31 | /// <param name="commandName">The command name (e.g., "manage_asset")</param> 32 | public McpForUnityToolAttribute(string commandName) 33 | { 34 | CommandName = commandName; 35 | } 36 | } 37 | } 38 | ``` -------------------------------------------------------------------------------- /MCPForUnity/Editor/Resources/McpForUnityResourceAttribute.cs: -------------------------------------------------------------------------------- ```csharp 1 | using System; 2 | 3 | namespace MCPForUnity.Editor.Resources 4 | { 5 | /// <summary> 6 | /// Marks a class as an MCP resource handler for auto-discovery. 7 | /// The class must have a public static HandleCommand(JObject) method. 8 | /// </summary> 9 | [AttributeUsage(AttributeTargets.Class, AllowMultiple = false)] 10 | public class McpForUnityResourceAttribute : Attribute 11 | { 12 | /// <summary> 13 | /// The resource name used to route requests to this resource. 14 | /// If not specified, defaults to the PascalCase class name converted to snake_case. 15 | /// </summary> 16 | public string ResourceName { get; } 17 | 18 | /// <summary> 19 | /// Create an MCP resource attribute with auto-generated resource name. 20 | /// The resource name will be derived from the class name (PascalCase → snake_case). 21 | /// Example: ManageAsset → manage_asset 22 | /// </summary> 23 | public McpForUnityResourceAttribute() 24 | { 25 | ResourceName = null; // Will be auto-generated 26 | } 27 | 28 | /// <summary> 29 | /// Create an MCP resource attribute with explicit resource name. 30 | /// </summary> 31 | /// <param name="resourceName">The resource name (e.g., "manage_asset")</param> 32 | public McpForUnityResourceAttribute(string resourceName) 33 | { 34 | ResourceName = resourceName; 35 | } 36 | } 37 | } 38 | ``` -------------------------------------------------------------------------------- /MCPForUnity/UnityMcpServer~/src/config.py: -------------------------------------------------------------------------------- ```python 1 | """ 2 | Configuration settings for the MCP for Unity Server. 3 | This file contains all configurable parameters for the server. 4 | """ 5 | 6 | from dataclasses import dataclass 7 | 8 | 9 | @dataclass 10 | class ServerConfig: 11 | """Main configuration class for the MCP server.""" 12 | 13 | # Network settings 14 | unity_host: str = "localhost" 15 | unity_port: int = 6400 16 | mcp_port: int = 6500 17 | 18 | # Connection settings 19 | connection_timeout: float = 30.0 20 | buffer_size: int = 16 * 1024 * 1024 # 16MB buffer 21 | # Framed receive behavior 22 | # max seconds to wait while consuming heartbeats only 23 | framed_receive_timeout: float = 2.0 24 | # cap heartbeat frames consumed before giving up 25 | max_heartbeat_frames: int = 16 26 | 27 | # Logging settings 28 | log_level: str = "INFO" 29 | log_format: str = "%(asctime)s - %(name)s - %(levelname)s - %(message)s" 30 | 31 | # Server settings 32 | max_retries: int = 5 33 | retry_delay: float = 0.25 34 | # Backoff hint returned to clients when Unity is reloading (milliseconds) 35 | reload_retry_ms: int = 250 36 | # Number of polite retries when Unity reports reloading 37 | # 40 × 250ms ≈ 10s default window 38 | reload_max_retries: int = 40 39 | 40 | # Telemetry settings 41 | telemetry_enabled: bool = True 42 | # Align with telemetry.py default Cloud Run endpoint 43 | telemetry_endpoint: str = "https://api-prod.coplay.dev/telemetry/events" 44 | 45 | 46 | # Create a global config instance 47 | config = ServerConfig() 48 | ``` -------------------------------------------------------------------------------- /MCPForUnity/Editor/Dependencies/PlatformDetectors/IPlatformDetector.cs: -------------------------------------------------------------------------------- ```csharp 1 | using MCPForUnity.Editor.Dependencies.Models; 2 | 3 | namespace MCPForUnity.Editor.Dependencies.PlatformDetectors 4 | { 5 | /// <summary> 6 | /// Interface for platform-specific dependency detection 7 | /// </summary> 8 | public interface IPlatformDetector 9 | { 10 | /// <summary> 11 | /// Platform name this detector handles 12 | /// </summary> 13 | string PlatformName { get; } 14 | 15 | /// <summary> 16 | /// Whether this detector can run on the current platform 17 | /// </summary> 18 | bool CanDetect { get; } 19 | 20 | /// <summary> 21 | /// Detect Python installation on this platform 22 | /// </summary> 23 | DependencyStatus DetectPython(); 24 | 25 | /// <summary> 26 | /// Detect UV package manager on this platform 27 | /// </summary> 28 | DependencyStatus DetectUV(); 29 | 30 | /// <summary> 31 | /// Detect MCP server installation on this platform 32 | /// </summary> 33 | DependencyStatus DetectMCPServer(); 34 | 35 | /// <summary> 36 | /// Get platform-specific installation recommendations 37 | /// </summary> 38 | string GetInstallationRecommendations(); 39 | 40 | /// <summary> 41 | /// Get platform-specific Python installation URL 42 | /// </summary> 43 | string GetPythonInstallUrl(); 44 | 45 | /// <summary> 46 | /// Get platform-specific UV installation URL 47 | /// </summary> 48 | string GetUVInstallUrl(); 49 | } 50 | } 51 | ``` -------------------------------------------------------------------------------- /UnityMcpBridge/Editor/Dependencies/PlatformDetectors/IPlatformDetector.cs: -------------------------------------------------------------------------------- ```csharp 1 | using MCPForUnity.Editor.Dependencies.Models; 2 | 3 | namespace MCPForUnity.Editor.Dependencies.PlatformDetectors 4 | { 5 | /// <summary> 6 | /// Interface for platform-specific dependency detection 7 | /// </summary> 8 | public interface IPlatformDetector 9 | { 10 | /// <summary> 11 | /// Platform name this detector handles 12 | /// </summary> 13 | string PlatformName { get; } 14 | 15 | /// <summary> 16 | /// Whether this detector can run on the current platform 17 | /// </summary> 18 | bool CanDetect { get; } 19 | 20 | /// <summary> 21 | /// Detect Python installation on this platform 22 | /// </summary> 23 | DependencyStatus DetectPython(); 24 | 25 | /// <summary> 26 | /// Detect UV package manager on this platform 27 | /// </summary> 28 | DependencyStatus DetectUV(); 29 | 30 | /// <summary> 31 | /// Detect MCP server installation on this platform 32 | /// </summary> 33 | DependencyStatus DetectMCPServer(); 34 | 35 | /// <summary> 36 | /// Get platform-specific installation recommendations 37 | /// </summary> 38 | string GetInstallationRecommendations(); 39 | 40 | /// <summary> 41 | /// Get platform-specific Python installation URL 42 | /// </summary> 43 | string GetPythonInstallUrl(); 44 | 45 | /// <summary> 46 | /// Get platform-specific UV installation URL 47 | /// </summary> 48 | string GetUVInstallUrl(); 49 | } 50 | } 51 | ``` -------------------------------------------------------------------------------- /UnityMcpBridge/UnityMcpServer~/src/config.py: -------------------------------------------------------------------------------- ```python 1 | """ 2 | Configuration settings for the MCP for Unity Server. 3 | This file contains all configurable parameters for the server. 4 | """ 5 | 6 | from dataclasses import dataclass 7 | 8 | 9 | @dataclass 10 | class ServerConfig: 11 | """Main configuration class for the MCP server.""" 12 | 13 | # Network settings 14 | unity_host: str = "localhost" 15 | unity_port: int = 6400 16 | mcp_port: int = 6500 17 | 18 | # Connection settings 19 | # short initial timeout; retries use shorter timeouts 20 | connection_timeout: float = 1.0 21 | buffer_size: int = 16 * 1024 * 1024 # 16MB buffer 22 | # Framed receive behavior 23 | # max seconds to wait while consuming heartbeats only 24 | framed_receive_timeout: float = 2.0 25 | # cap heartbeat frames consumed before giving up 26 | max_heartbeat_frames: int = 16 27 | 28 | # Logging settings 29 | log_level: str = "INFO" 30 | log_format: str = "%(asctime)s - %(name)s - %(levelname)s - %(message)s" 31 | 32 | # Server settings 33 | max_retries: int = 10 34 | retry_delay: float = 0.25 35 | # Backoff hint returned to clients when Unity is reloading (milliseconds) 36 | reload_retry_ms: int = 250 37 | # Number of polite retries when Unity reports reloading 38 | # 40 × 250ms ≈ 10s default window 39 | reload_max_retries: int = 40 40 | 41 | # Telemetry settings 42 | telemetry_enabled: bool = True 43 | # Align with telemetry.py default Cloud Run endpoint 44 | telemetry_endpoint: str = "https://api-prod.coplay.dev/telemetry/events" 45 | 46 | 47 | # Create a global config instance 48 | config = ServerConfig() 49 | ``` -------------------------------------------------------------------------------- /MCPForUnity/UnityMcpServer~/src/resources/tests.py: -------------------------------------------------------------------------------- ```python 1 | from typing import Annotated, Literal 2 | from pydantic import BaseModel, Field 3 | 4 | from models import MCPResponse 5 | from registry import mcp_for_unity_resource 6 | from unity_connection import async_send_command_with_retry 7 | 8 | 9 | class TestItem(BaseModel): 10 | name: Annotated[str, Field(description="The name of the test.")] 11 | full_name: Annotated[str, Field(description="The full name of the test.")] 12 | mode: Annotated[Literal["EditMode", "PlayMode"], 13 | Field(description="The mode the test is for.")] 14 | 15 | 16 | class GetTestsResponse(MCPResponse): 17 | data: list[TestItem] = [] 18 | 19 | 20 | @mcp_for_unity_resource(uri="mcpforunity://tests", name="get_tests", description="Provides a list of all tests.") 21 | async def get_tests() -> GetTestsResponse: 22 | """Provides a list of all tests.""" 23 | response = await async_send_command_with_retry("get_tests", {}) 24 | return GetTestsResponse(**response) if isinstance(response, dict) else response 25 | 26 | 27 | @mcp_for_unity_resource(uri="mcpforunity://tests/{mode}", name="get_tests_for_mode", description="Provides a list of tests for a specific mode.") 28 | async def get_tests_for_mode(mode: Annotated[Literal["EditMode", "PlayMode"], Field(description="The mode to filter tests by.")]) -> GetTestsResponse: 29 | """Provides a list of tests for a specific mode.""" 30 | response = await async_send_command_with_retry("get_tests_for_mode", {"mode": mode}) 31 | return GetTestsResponse(**response) if isinstance(response, dict) else response 32 | ``` -------------------------------------------------------------------------------- /UnityMcpBridge/Editor/Tools/MenuItems/ManageMenuItem.cs: -------------------------------------------------------------------------------- ```csharp 1 | using System; 2 | using Newtonsoft.Json.Linq; 3 | using MCPForUnity.Editor.Helpers; 4 | 5 | namespace MCPForUnity.Editor.Tools.MenuItems 6 | { 7 | [McpForUnityTool("manage_menu_item")] 8 | public static class ManageMenuItem 9 | { 10 | /// <summary> 11 | /// Routes actions: execute, list, exists, refresh 12 | /// </summary> 13 | public static object HandleCommand(JObject @params) 14 | { 15 | string action = @params["action"]?.ToString()?.ToLowerInvariant(); 16 | if (string.IsNullOrEmpty(action)) 17 | { 18 | return Response.Error("Action parameter is required. Valid actions are: execute, list, exists, refresh."); 19 | } 20 | 21 | try 22 | { 23 | switch (action) 24 | { 25 | case "execute": 26 | return MenuItemExecutor.Execute(@params); 27 | case "list": 28 | return MenuItemsReader.List(@params); 29 | case "exists": 30 | return MenuItemsReader.Exists(@params); 31 | default: 32 | return Response.Error($"Unknown action: '{action}'. Valid actions are: execute, list, exists, refresh."); 33 | } 34 | } 35 | catch (Exception e) 36 | { 37 | McpLog.Error($"[ManageMenuItem] Action '{action}' failed: {e}"); 38 | return Response.Error($"Internal error: {e.Message}"); 39 | } 40 | } 41 | } 42 | } 43 | ``` -------------------------------------------------------------------------------- /MCPForUnity/UnityMcpServer~/src/registry/resource_registry.py: -------------------------------------------------------------------------------- ```python 1 | """ 2 | Resource registry for auto-discovery of MCP resources. 3 | """ 4 | from typing import Callable, Any 5 | 6 | # Global registry to collect decorated resources 7 | _resource_registry: list[dict[str, Any]] = [] 8 | 9 | 10 | def mcp_for_unity_resource( 11 | uri: str, 12 | name: str | None = None, 13 | description: str | None = None, 14 | **kwargs 15 | ) -> Callable: 16 | """ 17 | Decorator for registering MCP resources in the server's resources directory. 18 | 19 | Resources are registered in the global resource registry. 20 | 21 | Args: 22 | name: Resource name (defaults to function name) 23 | description: Resource description 24 | **kwargs: Additional arguments passed to @mcp.resource() 25 | 26 | Example: 27 | @mcp_for_unity_resource("mcpforunity://resource", description="Gets something interesting") 28 | async def my_custom_resource(ctx: Context, ...): 29 | pass 30 | """ 31 | def decorator(func: Callable) -> Callable: 32 | resource_name = name if name is not None else func.__name__ 33 | _resource_registry.append({ 34 | 'func': func, 35 | 'uri': uri, 36 | 'name': resource_name, 37 | 'description': description, 38 | 'kwargs': kwargs 39 | }) 40 | 41 | return func 42 | 43 | return decorator 44 | 45 | 46 | def get_registered_resources() -> list[dict[str, Any]]: 47 | """Get all registered resources""" 48 | return _resource_registry.copy() 49 | 50 | 51 | def clear_resource_registry(): 52 | """Clear the resource registry (useful for testing)""" 53 | _resource_registry.clear() 54 | ``` -------------------------------------------------------------------------------- /.github/workflows/unity-tests.yml: -------------------------------------------------------------------------------- ```yaml 1 | name: Unity Tests 2 | 3 | on: 4 | push: 5 | branches: [main] 6 | paths: 7 | - TestProjects/UnityMCPTests/** 8 | - MCPForUnity/Editor/** 9 | - .github/workflows/unity-tests.yml 10 | 11 | jobs: 12 | testAllModes: 13 | name: Test in ${{ matrix.testMode }} 14 | runs-on: ubuntu-latest 15 | strategy: 16 | fail-fast: false 17 | matrix: 18 | projectPath: 19 | - TestProjects/UnityMCPTests 20 | testMode: 21 | - editmode 22 | unityVersion: 23 | - 2021.3.45f2 24 | steps: 25 | # Checkout 26 | - name: Checkout repository 27 | uses: actions/checkout@v4 28 | with: 29 | lfs: true 30 | 31 | # Cache 32 | - uses: actions/cache@v4 33 | with: 34 | path: ${{ matrix.projectPath }}/Library 35 | key: Library-${{ matrix.projectPath }}-${{ matrix.unityVersion }} 36 | restore-keys: | 37 | Library-${{ matrix.projectPath }}- 38 | Library- 39 | 40 | # Test 41 | - name: Run tests 42 | uses: game-ci/unity-test-runner@v4 43 | id: tests 44 | env: 45 | UNITY_EMAIL: ${{ secrets.UNITY_EMAIL }} 46 | UNITY_PASSWORD: ${{ secrets.UNITY_PASSWORD }} 47 | UNITY_LICENSE: ${{ secrets.UNITY_LICENSE }} 48 | with: 49 | projectPath: ${{ matrix.projectPath }} 50 | unityVersion: ${{ matrix.unityVersion }} 51 | testMode: ${{ matrix.testMode }} 52 | 53 | # Upload test results 54 | - uses: actions/upload-artifact@v4 55 | if: always() 56 | with: 57 | name: Test results for ${{ matrix.testMode }} 58 | path: ${{ steps.tests.outputs.artifactsPath }} 59 | ``` -------------------------------------------------------------------------------- /MCPForUnity/Editor/Helpers/PackageInstaller.cs: -------------------------------------------------------------------------------- ```csharp 1 | using UnityEditor; 2 | using UnityEngine; 3 | 4 | namespace MCPForUnity.Editor.Helpers 5 | { 6 | /// <summary> 7 | /// Handles automatic installation of the MCP server when the package is first installed. 8 | /// </summary> 9 | [InitializeOnLoad] 10 | public static class PackageInstaller 11 | { 12 | private const string InstallationFlagKey = "MCPForUnity.ServerInstalled"; 13 | 14 | static PackageInstaller() 15 | { 16 | // Check if this is the first time the package is loaded 17 | if (!EditorPrefs.GetBool(InstallationFlagKey, false)) 18 | { 19 | // Schedule the installation for after Unity is fully loaded 20 | EditorApplication.delayCall += InstallServerOnFirstLoad; 21 | } 22 | } 23 | 24 | private static void InstallServerOnFirstLoad() 25 | { 26 | try 27 | { 28 | ServerInstaller.EnsureServerInstalled(); 29 | 30 | // Mark as installed/checked 31 | EditorPrefs.SetBool(InstallationFlagKey, true); 32 | 33 | // Only log success if server was actually embedded and copied 34 | if (ServerInstaller.HasEmbeddedServer()) 35 | { 36 | McpLog.Info("MCP server installation completed successfully."); 37 | } 38 | } 39 | catch (System.Exception) 40 | { 41 | EditorPrefs.SetBool(InstallationFlagKey, true); // Mark as handled 42 | McpLog.Info("Server installation pending. Open Window > MCP For Unity to download the server."); 43 | } 44 | } 45 | } 46 | } 47 | ``` -------------------------------------------------------------------------------- /TestProjects/UnityMCPTests/Assets/Tests/EditMode/Tools/ExecuteMenuItemTests.cs: -------------------------------------------------------------------------------- ```csharp 1 | using NUnit.Framework; 2 | using Newtonsoft.Json.Linq; 3 | using MCPForUnity.Editor.Tools; 4 | 5 | namespace MCPForUnityTests.Editor.Tools 6 | { 7 | public class ExecuteMenuItemTests 8 | { 9 | private static JObject ToJO(object o) => JObject.FromObject(o); 10 | 11 | [Test] 12 | public void Execute_MissingParam_ReturnsError() 13 | { 14 | var res = ExecuteMenuItem.HandleCommand(new JObject()); 15 | var jo = ToJO(res); 16 | Assert.IsFalse((bool)jo["success"], "Expected success false"); 17 | StringAssert.Contains("Required parameter", (string)jo["error"]); 18 | } 19 | 20 | [Test] 21 | public void Execute_Blacklisted_ReturnsError() 22 | { 23 | var res = ExecuteMenuItem.HandleCommand(new JObject { ["menuPath"] = "File/Quit" }); 24 | var jo = ToJO(res); 25 | Assert.IsFalse((bool)jo["success"], "Expected success false for blacklisted menu"); 26 | StringAssert.Contains("blocked for safety", (string)jo["error"], "Expected blacklist message"); 27 | } 28 | 29 | [Test] 30 | public void Execute_NonBlacklisted_ReturnsImmediateSuccess() 31 | { 32 | // We don't rely on the menu actually existing; execution is delayed and we only check the immediate response shape 33 | var res = ExecuteMenuItem.HandleCommand(new JObject { ["menuPath"] = "File/Save Project" }); 34 | var jo = ToJO(res); 35 | Assert.IsTrue((bool)jo["success"], "Expected immediate success response"); 36 | StringAssert.Contains("Attempted to execute menu item", (string)jo["message"], "Expected attempt message"); 37 | } 38 | } 39 | } 40 | ``` -------------------------------------------------------------------------------- /MCPForUnity/UnityMcpServer~/src/tools/__init__.py: -------------------------------------------------------------------------------- ```python 1 | """ 2 | MCP Tools package - Auto-discovers and registers all tools in this directory. 3 | """ 4 | import logging 5 | from pathlib import Path 6 | 7 | from mcp.server.fastmcp import FastMCP 8 | from telemetry_decorator import telemetry_tool 9 | 10 | from registry import get_registered_tools 11 | from module_discovery import discover_modules 12 | 13 | logger = logging.getLogger("mcp-for-unity-server") 14 | 15 | # Export decorator for easy imports within tools 16 | __all__ = ['register_all_tools'] 17 | 18 | 19 | def register_all_tools(mcp: FastMCP): 20 | """ 21 | Auto-discover and register all tools in the tools/ directory. 22 | 23 | Any .py file in this directory or subdirectories with @mcp_for_unity_tool decorated 24 | functions will be automatically registered. 25 | """ 26 | logger.info("Auto-discovering MCP for Unity Server tools...") 27 | # Dynamic import of all modules in this directory 28 | tools_dir = Path(__file__).parent 29 | 30 | # Discover and import all modules 31 | list(discover_modules(tools_dir, __package__)) 32 | 33 | tools = get_registered_tools() 34 | 35 | if not tools: 36 | logger.warning("No MCP tools registered!") 37 | return 38 | 39 | for tool_info in tools: 40 | func = tool_info['func'] 41 | tool_name = tool_info['name'] 42 | description = tool_info['description'] 43 | kwargs = tool_info['kwargs'] 44 | 45 | # Apply the @mcp.tool decorator and telemetry 46 | wrapped = telemetry_tool(tool_name)(func) 47 | wrapped = mcp.tool( 48 | name=tool_name, description=description, **kwargs)(wrapped) 49 | tool_info['func'] = wrapped 50 | logger.debug(f"Registered tool: {tool_name} - {description}") 51 | 52 | logger.info(f"Registered {len(tools)} MCP tools") 53 | ``` -------------------------------------------------------------------------------- /MCPForUnity/UnityMcpServer~/src/tools/run_tests.py: -------------------------------------------------------------------------------- ```python 1 | """Tool for executing Unity Test Runner suites.""" 2 | from typing import Annotated, Literal, Any 3 | 4 | from mcp.server.fastmcp import Context 5 | from pydantic import BaseModel, Field 6 | 7 | from models import MCPResponse 8 | from registry import mcp_for_unity_tool 9 | from unity_connection import async_send_command_with_retry 10 | 11 | 12 | class RunTestsSummary(BaseModel): 13 | total: int 14 | passed: int 15 | failed: int 16 | skipped: int 17 | durationSeconds: float 18 | resultState: str 19 | 20 | 21 | class RunTestsTestResult(BaseModel): 22 | name: str 23 | fullName: str 24 | state: str 25 | durationSeconds: float 26 | message: str | None = None 27 | stackTrace: str | None = None 28 | output: str | None = None 29 | 30 | 31 | class RunTestsResult(BaseModel): 32 | mode: str 33 | summary: RunTestsSummary 34 | results: list[RunTestsTestResult] 35 | 36 | 37 | class RunTestsResponse(MCPResponse): 38 | data: RunTestsResult | None = None 39 | 40 | 41 | @mcp_for_unity_tool(description="Runs Unity tests for the specified mode") 42 | async def run_tests( 43 | ctx: Context, 44 | mode: Annotated[Literal["edit", "play"], Field( 45 | description="Unity test mode to run")] = "edit", 46 | timeout_seconds: Annotated[int, Field( 47 | description="Optional timeout in seconds for the Unity test run")] | None = None, 48 | ) -> RunTestsResponse: 49 | await ctx.info(f"Processing run_tests: mode={mode}") 50 | 51 | params: dict[str, Any] = {"mode": mode} 52 | if timeout_seconds is not None: 53 | params["timeoutSeconds"] = timeout_seconds 54 | 55 | response = await async_send_command_with_retry("run_tests", params) 56 | await ctx.info(f'Response {response}') 57 | return RunTestsResponse(**response) if isinstance(response, dict) else response 58 | ``` -------------------------------------------------------------------------------- /UnityMcpBridge/Editor/Helpers/PackageInstaller.cs: -------------------------------------------------------------------------------- ```csharp 1 | using UnityEditor; 2 | using UnityEngine; 3 | 4 | namespace MCPForUnity.Editor.Helpers 5 | { 6 | /// <summary> 7 | /// Handles automatic installation of the Python server when the package is first installed. 8 | /// </summary> 9 | [InitializeOnLoad] 10 | public static class PackageInstaller 11 | { 12 | private const string InstallationFlagKey = "MCPForUnity.ServerInstalled"; 13 | 14 | static PackageInstaller() 15 | { 16 | // Check if this is the first time the package is loaded 17 | if (!EditorPrefs.GetBool(InstallationFlagKey, false)) 18 | { 19 | // Schedule the installation for after Unity is fully loaded 20 | EditorApplication.delayCall += InstallServerOnFirstLoad; 21 | } 22 | } 23 | 24 | private static void InstallServerOnFirstLoad() 25 | { 26 | try 27 | { 28 | Debug.Log("<b><color=#2EA3FF>MCP-FOR-UNITY</color></b>: Installing Python server..."); 29 | ServerInstaller.EnsureServerInstalled(); 30 | 31 | // Mark as installed 32 | EditorPrefs.SetBool(InstallationFlagKey, true); 33 | 34 | Debug.Log("<b><color=#2EA3FF>MCP-FOR-UNITY</color></b>: Python server installation completed successfully."); 35 | } 36 | catch (System.Exception ex) 37 | { 38 | Debug.LogError($"<b><color=#2EA3FF>MCP-FOR-UNITY</color></b>: Failed to install Python server: {ex.Message}"); 39 | Debug.LogWarning("<b><color=#2EA3FF>MCP-FOR-UNITY</color></b>: You may need to manually install the Python server. Check the MCP For Unity Window for instructions."); 40 | } 41 | } 42 | } 43 | } 44 | ``` -------------------------------------------------------------------------------- /UnityMcpBridge/UnityMcpServer~/src/tools/manage_menu_item.py: -------------------------------------------------------------------------------- ```python 1 | """ 2 | Defines the manage_menu_item tool for executing and reading Unity Editor menu items. 3 | """ 4 | import asyncio 5 | from typing import Annotated, Any, Literal 6 | 7 | from mcp.server.fastmcp import Context 8 | from registry import mcp_for_unity_tool 9 | from unity_connection import async_send_command_with_retry 10 | 11 | 12 | @mcp_for_unity_tool( 13 | description="Manage Unity menu items (execute/list/exists). If you're not sure what menu item to use, use the 'list' action to find it before using 'execute'." 14 | ) 15 | async def manage_menu_item( 16 | ctx: Context, 17 | action: Annotated[Literal["execute", "list", "exists"], "Read and execute Unity menu items."], 18 | menu_path: Annotated[str, 19 | "Menu path for 'execute' or 'exists' (e.g., 'File/Save Project')"] | None = None, 20 | search: Annotated[str, 21 | "Optional filter string for 'list' (e.g., 'Save')"] | None = None, 22 | refresh: Annotated[bool, 23 | "Optional flag to force refresh of the menu cache when listing"] | None = None, 24 | ) -> dict[str, Any]: 25 | ctx.info(f"Processing manage_menu_item: {action}") 26 | # Prepare parameters for the C# handler 27 | params_dict: dict[str, Any] = { 28 | "action": action, 29 | "menuPath": menu_path, 30 | "search": search, 31 | "refresh": refresh, 32 | } 33 | # Remove None values 34 | params_dict = {k: v for k, v in params_dict.items() if v is not None} 35 | 36 | # Get the current asyncio event loop 37 | loop = asyncio.get_running_loop() 38 | 39 | # Use centralized async retry helper 40 | result = await async_send_command_with_retry("manage_menu_item", params_dict, loop=loop) 41 | return result if isinstance(result, dict) else {"success": False, "message": str(result)} 42 | ``` -------------------------------------------------------------------------------- /MCPForUnity/Editor/Models/McpClient.cs: -------------------------------------------------------------------------------- ```csharp 1 | namespace MCPForUnity.Editor.Models 2 | { 3 | public class McpClient 4 | { 5 | public string name; 6 | public string windowsConfigPath; 7 | public string macConfigPath; 8 | public string linuxConfigPath; 9 | public McpTypes mcpType; 10 | public string configStatus; 11 | public McpStatus status = McpStatus.NotConfigured; 12 | 13 | // Helper method to convert the enum to a display string 14 | public string GetStatusDisplayString() 15 | { 16 | return status switch 17 | { 18 | McpStatus.NotConfigured => "Not Configured", 19 | McpStatus.Configured => "Configured", 20 | McpStatus.Running => "Running", 21 | McpStatus.Connected => "Connected", 22 | McpStatus.IncorrectPath => "Incorrect Path", 23 | McpStatus.CommunicationError => "Communication Error", 24 | McpStatus.NoResponse => "No Response", 25 | McpStatus.UnsupportedOS => "Unsupported OS", 26 | McpStatus.MissingConfig => "Missing MCPForUnity Config", 27 | McpStatus.Error => configStatus.StartsWith("Error:") ? configStatus : "Error", 28 | _ => "Unknown", 29 | }; 30 | } 31 | 32 | // Helper method to set both status enum and string for backward compatibility 33 | public void SetStatus(McpStatus newStatus, string errorDetails = null) 34 | { 35 | status = newStatus; 36 | 37 | if (newStatus == McpStatus.Error && !string.IsNullOrEmpty(errorDetails)) 38 | { 39 | configStatus = $"Error: {errorDetails}"; 40 | } 41 | else 42 | { 43 | configStatus = GetStatusDisplayString(); 44 | } 45 | } 46 | } 47 | } 48 | ``` -------------------------------------------------------------------------------- /UnityMcpBridge/Editor/Models/McpClient.cs: -------------------------------------------------------------------------------- ```csharp 1 | namespace MCPForUnity.Editor.Models 2 | { 3 | public class McpClient 4 | { 5 | public string name; 6 | public string windowsConfigPath; 7 | public string macConfigPath; 8 | public string linuxConfigPath; 9 | public McpTypes mcpType; 10 | public string configStatus; 11 | public McpStatus status = McpStatus.NotConfigured; 12 | 13 | // Helper method to convert the enum to a display string 14 | public string GetStatusDisplayString() 15 | { 16 | return status switch 17 | { 18 | McpStatus.NotConfigured => "Not Configured", 19 | McpStatus.Configured => "Configured", 20 | McpStatus.Running => "Running", 21 | McpStatus.Connected => "Connected", 22 | McpStatus.IncorrectPath => "Incorrect Path", 23 | McpStatus.CommunicationError => "Communication Error", 24 | McpStatus.NoResponse => "No Response", 25 | McpStatus.UnsupportedOS => "Unsupported OS", 26 | McpStatus.MissingConfig => "Missing MCPForUnity Config", 27 | McpStatus.Error => configStatus.StartsWith("Error:") ? configStatus : "Error", 28 | _ => "Unknown", 29 | }; 30 | } 31 | 32 | // Helper method to set both status enum and string for backward compatibility 33 | public void SetStatus(McpStatus newStatus, string errorDetails = null) 34 | { 35 | status = newStatus; 36 | 37 | if (newStatus == McpStatus.Error && !string.IsNullOrEmpty(errorDetails)) 38 | { 39 | configStatus = $"Error: {errorDetails}"; 40 | } 41 | else 42 | { 43 | configStatus = GetStatusDisplayString(); 44 | } 45 | } 46 | } 47 | } 48 | ``` -------------------------------------------------------------------------------- /TestProjects/UnityMCPTests/Assets/Tests/EditMode/Tools/CommandRegistryTests.cs: -------------------------------------------------------------------------------- ```csharp 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using Newtonsoft.Json; 5 | using Newtonsoft.Json.Linq; 6 | using NUnit.Framework; 7 | using MCPForUnity.Editor.Tools; 8 | 9 | namespace MCPForUnityTests.Editor.Tools 10 | { 11 | public class CommandRegistryTests 12 | { 13 | [OneTimeSetUp] 14 | public void OneTimeSetUp() 15 | { 16 | // Ensure CommandRegistry is initialized before tests run 17 | CommandRegistry.Initialize(); 18 | } 19 | 20 | [Test] 21 | public void GetHandler_ThrowsException_ForUnknownCommand() 22 | { 23 | var unknown = "nonexistent_command_that_should_not_exist"; 24 | 25 | Assert.Throws<InvalidOperationException>(() => 26 | { 27 | CommandRegistry.GetHandler(unknown); 28 | }, "Should throw InvalidOperationException for unknown handler"); 29 | } 30 | 31 | [Test] 32 | public void AutoDiscovery_RegistersAllBuiltInTools() 33 | { 34 | // Verify that all expected built-in tools are registered by trying to get their handlers 35 | var expectedTools = new[] 36 | { 37 | "manage_asset", 38 | "manage_editor", 39 | "manage_gameobject", 40 | "manage_scene", 41 | "manage_script", 42 | "manage_shader", 43 | "read_console", 44 | "execute_menu_item", 45 | "manage_prefabs" 46 | }; 47 | 48 | foreach (var toolName in expectedTools) 49 | { 50 | Assert.DoesNotThrow(() => 51 | { 52 | var handler = CommandRegistry.GetHandler(toolName); 53 | Assert.IsNotNull(handler, $"Handler for '{toolName}' should not be null"); 54 | }, $"Expected tool '{toolName}' to be auto-registered"); 55 | } 56 | } 57 | } 58 | } 59 | ``` -------------------------------------------------------------------------------- /MCPForUnity/Editor/Services/PythonToolRegistryService.cs: -------------------------------------------------------------------------------- ```csharp 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Security.Cryptography; 6 | using UnityEditor; 7 | using UnityEngine; 8 | using MCPForUnity.Editor.Data; 9 | 10 | namespace MCPForUnity.Editor.Services 11 | { 12 | public class PythonToolRegistryService : IPythonToolRegistryService 13 | { 14 | public IEnumerable<PythonToolsAsset> GetAllRegistries() 15 | { 16 | // Find all PythonToolsAsset instances in the project 17 | string[] guids = AssetDatabase.FindAssets("t:PythonToolsAsset"); 18 | foreach (string guid in guids) 19 | { 20 | string path = AssetDatabase.GUIDToAssetPath(guid); 21 | var asset = AssetDatabase.LoadAssetAtPath<PythonToolsAsset>(path); 22 | if (asset != null) 23 | yield return asset; 24 | } 25 | } 26 | 27 | public bool NeedsSync(PythonToolsAsset registry, TextAsset file) 28 | { 29 | if (!registry.useContentHashing) return true; 30 | 31 | string currentHash = ComputeHash(file); 32 | return registry.NeedsSync(file, currentHash); 33 | } 34 | 35 | public void RecordSync(PythonToolsAsset registry, TextAsset file) 36 | { 37 | string hash = ComputeHash(file); 38 | registry.RecordSync(file, hash); 39 | EditorUtility.SetDirty(registry); 40 | } 41 | 42 | public string ComputeHash(TextAsset file) 43 | { 44 | if (file == null || string.IsNullOrEmpty(file.text)) 45 | return string.Empty; 46 | 47 | using (var sha256 = SHA256.Create()) 48 | { 49 | byte[] bytes = System.Text.Encoding.UTF8.GetBytes(file.text); 50 | byte[] hash = sha256.ComputeHash(bytes); 51 | return BitConverter.ToString(hash).Replace("-", "").ToLower(); 52 | } 53 | } 54 | } 55 | } 56 | ``` -------------------------------------------------------------------------------- /MCPForUnity/UnityMcpServer~/src/resources/__init__.py: -------------------------------------------------------------------------------- ```python 1 | """ 2 | MCP Resources package - Auto-discovers and registers all resources in this directory. 3 | """ 4 | import logging 5 | from pathlib import Path 6 | 7 | from mcp.server.fastmcp import FastMCP 8 | from telemetry_decorator import telemetry_resource 9 | 10 | from registry import get_registered_resources 11 | from module_discovery import discover_modules 12 | 13 | logger = logging.getLogger("mcp-for-unity-server") 14 | 15 | # Export decorator for easy imports within tools 16 | __all__ = ['register_all_resources'] 17 | 18 | 19 | def register_all_resources(mcp: FastMCP): 20 | """ 21 | Auto-discover and register all resources in the resources/ directory. 22 | 23 | Any .py file in this directory or subdirectories with @mcp_for_unity_resource decorated 24 | functions will be automatically registered. 25 | """ 26 | logger.info("Auto-discovering MCP for Unity Server resources...") 27 | # Dynamic import of all modules in this directory 28 | resources_dir = Path(__file__).parent 29 | 30 | # Discover and import all modules 31 | list(discover_modules(resources_dir, __package__)) 32 | 33 | resources = get_registered_resources() 34 | 35 | if not resources: 36 | logger.warning("No MCP resources registered!") 37 | return 38 | 39 | for resource_info in resources: 40 | func = resource_info['func'] 41 | uri = resource_info['uri'] 42 | resource_name = resource_info['name'] 43 | description = resource_info['description'] 44 | kwargs = resource_info['kwargs'] 45 | 46 | # Apply the @mcp.resource decorator and telemetry 47 | wrapped = telemetry_resource(resource_name)(func) 48 | wrapped = mcp.resource(uri=uri, name=resource_name, 49 | description=description, **kwargs)(wrapped) 50 | resource_info['func'] = wrapped 51 | logger.debug(f"Registered resource: {resource_name} - {description}") 52 | 53 | logger.info(f"Registered {len(resources)} MCP resources") 54 | ``` -------------------------------------------------------------------------------- /MCPForUnity/Editor/Dependencies/Models/DependencyStatus.cs: -------------------------------------------------------------------------------- ```csharp 1 | using System; 2 | 3 | namespace MCPForUnity.Editor.Dependencies.Models 4 | { 5 | /// <summary> 6 | /// Represents the status of a dependency check 7 | /// </summary> 8 | [Serializable] 9 | public class DependencyStatus 10 | { 11 | /// <summary> 12 | /// Name of the dependency being checked 13 | /// </summary> 14 | public string Name { get; set; } 15 | 16 | /// <summary> 17 | /// Whether the dependency is available and functional 18 | /// </summary> 19 | public bool IsAvailable { get; set; } 20 | 21 | /// <summary> 22 | /// Version information if available 23 | /// </summary> 24 | public string Version { get; set; } 25 | 26 | /// <summary> 27 | /// Path to the dependency executable/installation 28 | /// </summary> 29 | public string Path { get; set; } 30 | 31 | /// <summary> 32 | /// Additional details about the dependency status 33 | /// </summary> 34 | public string Details { get; set; } 35 | 36 | /// <summary> 37 | /// Error message if dependency check failed 38 | /// </summary> 39 | public string ErrorMessage { get; set; } 40 | 41 | /// <summary> 42 | /// Whether this dependency is required for basic functionality 43 | /// </summary> 44 | public bool IsRequired { get; set; } 45 | 46 | /// <summary> 47 | /// Suggested installation method or URL 48 | /// </summary> 49 | public string InstallationHint { get; set; } 50 | 51 | public DependencyStatus(string name, bool isRequired = true) 52 | { 53 | Name = name; 54 | IsRequired = isRequired; 55 | IsAvailable = false; 56 | } 57 | 58 | public override string ToString() 59 | { 60 | var status = IsAvailable ? "✓" : "✗"; 61 | var version = !string.IsNullOrEmpty(Version) ? $" ({Version})" : ""; 62 | return $"{status} {Name}{version}"; 63 | } 64 | } 65 | } 66 | ``` -------------------------------------------------------------------------------- /UnityMcpBridge/Editor/Dependencies/Models/DependencyStatus.cs: -------------------------------------------------------------------------------- ```csharp 1 | using System; 2 | 3 | namespace MCPForUnity.Editor.Dependencies.Models 4 | { 5 | /// <summary> 6 | /// Represents the status of a dependency check 7 | /// </summary> 8 | [Serializable] 9 | public class DependencyStatus 10 | { 11 | /// <summary> 12 | /// Name of the dependency being checked 13 | /// </summary> 14 | public string Name { get; set; } 15 | 16 | /// <summary> 17 | /// Whether the dependency is available and functional 18 | /// </summary> 19 | public bool IsAvailable { get; set; } 20 | 21 | /// <summary> 22 | /// Version information if available 23 | /// </summary> 24 | public string Version { get; set; } 25 | 26 | /// <summary> 27 | /// Path to the dependency executable/installation 28 | /// </summary> 29 | public string Path { get; set; } 30 | 31 | /// <summary> 32 | /// Additional details about the dependency status 33 | /// </summary> 34 | public string Details { get; set; } 35 | 36 | /// <summary> 37 | /// Error message if dependency check failed 38 | /// </summary> 39 | public string ErrorMessage { get; set; } 40 | 41 | /// <summary> 42 | /// Whether this dependency is required for basic functionality 43 | /// </summary> 44 | public bool IsRequired { get; set; } 45 | 46 | /// <summary> 47 | /// Suggested installation method or URL 48 | /// </summary> 49 | public string InstallationHint { get; set; } 50 | 51 | public DependencyStatus(string name, bool isRequired = true) 52 | { 53 | Name = name; 54 | IsRequired = isRequired; 55 | IsAvailable = false; 56 | } 57 | 58 | public override string ToString() 59 | { 60 | var status = IsAvailable ? "✓" : "✗"; 61 | var version = !string.IsNullOrEmpty(Version) ? $" ({Version})" : ""; 62 | return $"{status} {Name}{version}"; 63 | } 64 | } 65 | } 66 | ``` -------------------------------------------------------------------------------- /tests/test_read_resource_minimal.py: -------------------------------------------------------------------------------- ```python 1 | from tools.resource_tools import register_resource_tools # type: ignore 2 | import sys 3 | import pathlib 4 | import asyncio 5 | import types 6 | import pytest 7 | 8 | ROOT = pathlib.Path(__file__).resolve().parents[1] 9 | SRC = ROOT / "MCPForUnity" / "UnityMcpServer~" / "src" 10 | sys.path.insert(0, str(SRC)) 11 | 12 | # Stub mcp.server.fastmcp to satisfy imports without full package 13 | mcp_pkg = types.ModuleType("mcp") 14 | server_pkg = types.ModuleType("mcp.server") 15 | fastmcp_pkg = types.ModuleType("mcp.server.fastmcp") 16 | 17 | 18 | class _Dummy: 19 | pass 20 | 21 | 22 | fastmcp_pkg.FastMCP = _Dummy 23 | fastmcp_pkg.Context = _Dummy 24 | server_pkg.fastmcp = fastmcp_pkg 25 | mcp_pkg.server = server_pkg 26 | sys.modules.setdefault("mcp", mcp_pkg) 27 | sys.modules.setdefault("mcp.server", server_pkg) 28 | sys.modules.setdefault("mcp.server.fastmcp", fastmcp_pkg) 29 | 30 | 31 | class DummyMCP: 32 | def __init__(self): 33 | self.tools = {} 34 | 35 | def tool(self, *args, **kwargs): 36 | def deco(fn): 37 | self.tools[fn.__name__] = fn 38 | return fn 39 | return deco 40 | 41 | 42 | @pytest.fixture() 43 | def resource_tools(): 44 | mcp = DummyMCP() 45 | register_resource_tools(mcp) 46 | return mcp.tools 47 | 48 | 49 | def test_read_resource_minimal_metadata_only(resource_tools, tmp_path): 50 | proj = tmp_path 51 | assets = proj / "Assets" 52 | assets.mkdir() 53 | f = assets / "A.txt" 54 | content = "hello world" 55 | f.write_text(content, encoding="utf-8") 56 | 57 | read_resource = resource_tools["read_resource"] 58 | loop = asyncio.new_event_loop() 59 | try: 60 | resp = loop.run_until_complete( 61 | read_resource(uri="unity://path/Assets/A.txt", 62 | ctx=None, project_root=str(proj)) 63 | ) 64 | finally: 65 | loop.close() 66 | 67 | assert resp["success"] is True 68 | data = resp["data"] 69 | assert "text" not in data 70 | meta = data["metadata"] 71 | assert "sha256" in meta and len(meta["sha256"]) == 64 72 | assert meta["lengthBytes"] == len(content.encode("utf-8")) 73 | ``` -------------------------------------------------------------------------------- /UnityMcpBridge/UnityMcpServer~/src/tools/__init__.py: -------------------------------------------------------------------------------- ```python 1 | """ 2 | MCP Tools package - Auto-discovers and registers all tools in this directory. 3 | """ 4 | import importlib 5 | import logging 6 | from pathlib import Path 7 | import pkgutil 8 | 9 | from mcp.server.fastmcp import FastMCP 10 | from telemetry_decorator import telemetry_tool 11 | 12 | from registry import get_registered_tools, mcp_for_unity_tool 13 | 14 | logger = logging.getLogger("mcp-for-unity-server") 15 | 16 | # Export decorator for easy imports within tools 17 | __all__ = ['register_all_tools', 'mcp_for_unity_tool'] 18 | 19 | 20 | def register_all_tools(mcp: FastMCP): 21 | """ 22 | Auto-discover and register all tools in the tools/ directory. 23 | 24 | Any .py file in this directory with @mcp_for_unity_tool decorated 25 | functions will be automatically registered. 26 | """ 27 | logger.info("Auto-discovering MCP for Unity Server tools...") 28 | # Dynamic import of all modules in this directory 29 | tools_dir = Path(__file__).parent 30 | 31 | for _, module_name, _ in pkgutil.iter_modules([str(tools_dir)]): 32 | # Skip private modules and __init__ 33 | if module_name.startswith('_'): 34 | continue 35 | 36 | try: 37 | importlib.import_module(f'.{module_name}', __package__) 38 | except Exception as e: 39 | logger.warning(f"Failed to import tool module {module_name}: {e}") 40 | 41 | tools = get_registered_tools() 42 | 43 | if not tools: 44 | logger.warning("No MCP tools registered!") 45 | return 46 | 47 | for tool_info in tools: 48 | func = tool_info['func'] 49 | tool_name = tool_info['name'] 50 | description = tool_info['description'] 51 | kwargs = tool_info['kwargs'] 52 | 53 | # Apply the @mcp.tool decorator and telemetry 54 | wrapped = telemetry_tool(tool_name)(func) 55 | wrapped = mcp.tool( 56 | name=tool_name, description=description, **kwargs)(wrapped) 57 | tool_info['func'] = wrapped 58 | logger.info(f"Registered tool: {tool_name} - {description}") 59 | 60 | logger.info(f"Registered {len(tools)} MCP tools") 61 | ``` -------------------------------------------------------------------------------- /TestProjects/UnityMCPTests/Packages/manifest.json: -------------------------------------------------------------------------------- ```json 1 | { 2 | "dependencies": { 3 | "com.coplaydev.unity-mcp": "file:../../../MCPForUnity", 4 | "com.unity.collab-proxy": "2.5.2", 5 | "com.unity.feature.development": "1.0.1", 6 | "com.unity.ide.rider": "3.0.31", 7 | "com.unity.ide.visualstudio": "2.0.22", 8 | "com.unity.ide.vscode": "1.2.5", 9 | "com.unity.ide.windsurf": "https://github.com/Asuta/com.unity.ide.windsurf.git", 10 | "com.unity.test-framework": "1.1.33", 11 | "com.unity.textmeshpro": "3.0.6", 12 | "com.unity.timeline": "1.6.5", 13 | "com.unity.ugui": "1.0.0", 14 | "com.unity.visualscripting": "1.9.4", 15 | "com.unity.modules.ai": "1.0.0", 16 | "com.unity.modules.androidjni": "1.0.0", 17 | "com.unity.modules.animation": "1.0.0", 18 | "com.unity.modules.assetbundle": "1.0.0", 19 | "com.unity.modules.audio": "1.0.0", 20 | "com.unity.modules.cloth": "1.0.0", 21 | "com.unity.modules.director": "1.0.0", 22 | "com.unity.modules.imageconversion": "1.0.0", 23 | "com.unity.modules.imgui": "1.0.0", 24 | "com.unity.modules.jsonserialize": "1.0.0", 25 | "com.unity.modules.particlesystem": "1.0.0", 26 | "com.unity.modules.physics": "1.0.0", 27 | "com.unity.modules.physics2d": "1.0.0", 28 | "com.unity.modules.screencapture": "1.0.0", 29 | "com.unity.modules.terrain": "1.0.0", 30 | "com.unity.modules.terrainphysics": "1.0.0", 31 | "com.unity.modules.tilemap": "1.0.0", 32 | "com.unity.modules.ui": "1.0.0", 33 | "com.unity.modules.uielements": "1.0.0", 34 | "com.unity.modules.umbra": "1.0.0", 35 | "com.unity.modules.unityanalytics": "1.0.0", 36 | "com.unity.modules.unitywebrequest": "1.0.0", 37 | "com.unity.modules.unitywebrequestassetbundle": "1.0.0", 38 | "com.unity.modules.unitywebrequestaudio": "1.0.0", 39 | "com.unity.modules.unitywebrequesttexture": "1.0.0", 40 | "com.unity.modules.unitywebrequestwww": "1.0.0", 41 | "com.unity.modules.vehicles": "1.0.0", 42 | "com.unity.modules.video": "1.0.0", 43 | "com.unity.modules.vr": "1.0.0", 44 | "com.unity.modules.wind": "1.0.0", 45 | "com.unity.modules.xr": "1.0.0" 46 | } 47 | } 48 | ``` -------------------------------------------------------------------------------- /tests/test_validate_script_summary.py: -------------------------------------------------------------------------------- ```python 1 | import sys 2 | import pathlib 3 | import importlib.util 4 | import types 5 | 6 | ROOT = pathlib.Path(__file__).resolve().parents[1] 7 | SRC = ROOT / "MCPForUnity" / "UnityMcpServer~" / "src" 8 | sys.path.insert(0, str(SRC)) 9 | 10 | # stub mcp.server.fastmcp similar to test_get_sha 11 | mcp_pkg = types.ModuleType("mcp") 12 | server_pkg = types.ModuleType("mcp.server") 13 | fastmcp_pkg = types.ModuleType("mcp.server.fastmcp") 14 | 15 | 16 | class _Dummy: 17 | pass 18 | 19 | 20 | fastmcp_pkg.FastMCP = _Dummy 21 | fastmcp_pkg.Context = _Dummy 22 | server_pkg.fastmcp = fastmcp_pkg 23 | mcp_pkg.server = server_pkg 24 | sys.modules.setdefault("mcp", mcp_pkg) 25 | sys.modules.setdefault("mcp.server", server_pkg) 26 | sys.modules.setdefault("mcp.server.fastmcp", fastmcp_pkg) 27 | 28 | 29 | def _load_module(path: pathlib.Path, name: str): 30 | spec = importlib.util.spec_from_file_location(name, path) 31 | mod = importlib.util.module_from_spec(spec) 32 | spec.loader.exec_module(mod) 33 | return mod 34 | 35 | 36 | manage_script = _load_module( 37 | SRC / "tools" / "manage_script.py", "manage_script_mod") 38 | 39 | 40 | class DummyMCP: 41 | def __init__(self): 42 | self.tools = {} 43 | 44 | def tool(self, *args, **kwargs): 45 | def deco(fn): 46 | self.tools[fn.__name__] = fn 47 | return fn 48 | return deco 49 | 50 | 51 | def setup_tools(): 52 | mcp = DummyMCP() 53 | manage_script.register_manage_script_tools(mcp) 54 | return mcp.tools 55 | 56 | 57 | def test_validate_script_returns_counts(monkeypatch): 58 | tools = setup_tools() 59 | validate_script = tools["validate_script"] 60 | 61 | def fake_send(cmd, params): 62 | return { 63 | "success": True, 64 | "data": { 65 | "diagnostics": [ 66 | {"severity": "warning"}, 67 | {"severity": "error"}, 68 | {"severity": "fatal"}, 69 | ] 70 | }, 71 | } 72 | 73 | monkeypatch.setattr(manage_script, "send_command_with_retry", fake_send) 74 | 75 | resp = validate_script(None, uri="unity://path/Assets/Scripts/A.cs") 76 | assert resp == {"success": True, "data": {"warnings": 1, "errors": 2}} 77 | ``` -------------------------------------------------------------------------------- /MCPForUnity/Editor/Services/IBridgeControlService.cs: -------------------------------------------------------------------------------- ```csharp 1 | namespace MCPForUnity.Editor.Services 2 | { 3 | /// <summary> 4 | /// Service for controlling the MCP for Unity Bridge connection 5 | /// </summary> 6 | public interface IBridgeControlService 7 | { 8 | /// <summary> 9 | /// Gets whether the bridge is currently running 10 | /// </summary> 11 | bool IsRunning { get; } 12 | 13 | /// <summary> 14 | /// Gets the current port the bridge is listening on 15 | /// </summary> 16 | int CurrentPort { get; } 17 | 18 | /// <summary> 19 | /// Gets whether the bridge is in auto-connect mode 20 | /// </summary> 21 | bool IsAutoConnectMode { get; } 22 | 23 | /// <summary> 24 | /// Starts the MCP for Unity Bridge 25 | /// </summary> 26 | void Start(); 27 | 28 | /// <summary> 29 | /// Stops the MCP for Unity Bridge 30 | /// </summary> 31 | void Stop(); 32 | 33 | /// <summary> 34 | /// Verifies the bridge connection by sending a ping and waiting for a pong response 35 | /// </summary> 36 | /// <param name="port">The port to verify</param> 37 | /// <returns>Verification result with detailed status</returns> 38 | BridgeVerificationResult Verify(int port); 39 | } 40 | 41 | /// <summary> 42 | /// Result of a bridge verification attempt 43 | /// </summary> 44 | public class BridgeVerificationResult 45 | { 46 | /// <summary> 47 | /// Whether the verification was successful 48 | /// </summary> 49 | public bool Success { get; set; } 50 | 51 | /// <summary> 52 | /// Human-readable message about the verification result 53 | /// </summary> 54 | public string Message { get; set; } 55 | 56 | /// <summary> 57 | /// Whether the handshake was valid (FRAMING=1 protocol) 58 | /// </summary> 59 | public bool HandshakeValid { get; set; } 60 | 61 | /// <summary> 62 | /// Whether the ping/pong exchange succeeded 63 | /// </summary> 64 | public bool PingSucceeded { get; set; } 65 | } 66 | } 67 | ``` -------------------------------------------------------------------------------- /MCPForUnity/UnityMcpServer~/src/module_discovery.py: -------------------------------------------------------------------------------- ```python 1 | """ 2 | Shared module discovery utilities for auto-registering tools and resources. 3 | """ 4 | import importlib 5 | import logging 6 | from pathlib import Path 7 | import pkgutil 8 | from typing import Generator 9 | 10 | logger = logging.getLogger("mcp-for-unity-server") 11 | 12 | 13 | def discover_modules(base_dir: Path, package_name: str) -> Generator[str, None, None]: 14 | """ 15 | Discover and import all Python modules in a directory and its subdirectories. 16 | 17 | Args: 18 | base_dir: The base directory to search for modules 19 | package_name: The package name to use for relative imports (e.g., 'tools' or 'resources') 20 | 21 | Yields: 22 | Full module names that were successfully imported 23 | """ 24 | # Discover modules in the top level 25 | for _, module_name, _ in pkgutil.iter_modules([str(base_dir)]): 26 | # Skip private modules and __init__ 27 | if module_name.startswith('_'): 28 | continue 29 | 30 | try: 31 | full_module_name = f'.{module_name}' 32 | importlib.import_module(full_module_name, package_name) 33 | yield full_module_name 34 | except Exception as e: 35 | logger.warning(f"Failed to import module {module_name}: {e}") 36 | 37 | # Discover modules in subdirectories (one level deep) 38 | for subdir in base_dir.iterdir(): 39 | if not subdir.is_dir() or subdir.name.startswith('_') or subdir.name.startswith('.'): 40 | continue 41 | 42 | # Check if subdirectory contains Python modules 43 | for _, module_name, _ in pkgutil.iter_modules([str(subdir)]): 44 | # Skip private modules and __init__ 45 | if module_name.startswith('_'): 46 | continue 47 | 48 | try: 49 | # Import as package.subdirname.modulename 50 | full_module_name = f'.{subdir.name}.{module_name}' 51 | importlib.import_module(full_module_name, package_name) 52 | yield full_module_name 53 | except Exception as e: 54 | logger.warning( 55 | f"Failed to import module {subdir.name}.{module_name}: {e}") 56 | ``` -------------------------------------------------------------------------------- /docs/v5_MIGRATION.md: -------------------------------------------------------------------------------- ```markdown 1 | # MCP for Unity v5 Migration Guide 2 | 3 | This guide will help you migrate from the legacy UnityMcpBridge installation to the new MCPForUnity package structure in version 5. 4 | 5 | ## Overview 6 | 7 | Version 5 introduces a new package structure. The package is now installed from the `MCPForUnity` folder instead of the legacy `UnityMcpBridge` folder. 8 | 9 | ## Migration Steps 10 | 11 | ### Step 1: Uninstall the Current Package 12 | 13 | 1. Open the Unity Package Manager (**Window > Package Manager**) 14 | 2. Select **Packages: In Project** from the dropdown 15 | 3. Find **MCP for Unity** in the list 16 | 4. Click the **Remove** button to uninstall the legacy package 17 | 18 |  19 | 20 | ### Step 2: Install from the New Path 21 | 22 | 1. In the Package Manager, click the **+** button in the top-left corner 23 | 2. Select **Add package from git URL...** 24 | 3. Enter the following URL: `https://github.com/CoplayDev/unity-mcp.git?path=/MCPForUnity` 25 | 4. Click **Add** to install the package 26 | 27 |  28 | 29 | ### Step 3: Rebuild MCP Server 30 | 31 | After installing the new package, you need to rebuild the MCP server: 32 | 33 | 1. In Unity, go to **Window > MCP for Unity > Open MCP Window** 34 |  35 | 2. Click the **Rebuild MCP Server** button 36 |  37 | 3. You should see a success message confirming the rebuild 38 |  39 | 40 | ## Verification 41 | 42 | After completing these steps, verify the migration was successful: 43 | 44 | - Check that the package appears in the Package Manager as **MCP for Unity** 45 | - Confirm the package location shows the new `MCPForUnity` path 46 | - Test basic MCP functionality to ensure everything works correctly 47 | 48 | ## Troubleshooting 49 | 50 | - Check the Unity Console for specific error messages 51 | - Ensure Python dependencies are properly installed 52 | - Try pressing the rebuild button again 53 | - Try restarting Unity and repeating the installation steps 54 | ``` -------------------------------------------------------------------------------- /MCPForUnity/Editor/Tools/ExecuteMenuItem.cs: -------------------------------------------------------------------------------- ```csharp 1 | using System; 2 | using System.Collections.Generic; 3 | using MCPForUnity.Editor.Helpers; 4 | using Newtonsoft.Json.Linq; 5 | using UnityEditor; 6 | 7 | namespace MCPForUnity.Editor.Tools 8 | { 9 | [McpForUnityTool("execute_menu_item")] 10 | public static class ExecuteMenuItem 11 | { 12 | // Basic blacklist to prevent execution of disruptive menu items. 13 | private static readonly HashSet<string> _menuPathBlacklist = new HashSet<string>( 14 | StringComparer.OrdinalIgnoreCase) 15 | { 16 | "File/Quit", 17 | }; 18 | 19 | public static object HandleCommand(JObject @params) 20 | { 21 | McpLog.Info("[ExecuteMenuItem] Handling menu item command"); 22 | string menuPath = @params["menu_path"]?.ToString() ?? @params["menuPath"]?.ToString(); 23 | if (string.IsNullOrWhiteSpace(menuPath)) 24 | { 25 | return Response.Error("Required parameter 'menu_path' or 'menuPath' is missing or empty."); 26 | } 27 | 28 | if (_menuPathBlacklist.Contains(menuPath)) 29 | { 30 | return Response.Error($"Execution of menu item '{menuPath}' is blocked for safety reasons."); 31 | } 32 | 33 | try 34 | { 35 | bool executed = EditorApplication.ExecuteMenuItem(menuPath); 36 | if (!executed) 37 | { 38 | McpLog.Error($"[MenuItemExecutor] Failed to execute menu item '{menuPath}'. It might be invalid, disabled, or context-dependent."); 39 | return Response.Error($"Failed to execute menu item '{menuPath}'. It might be invalid, disabled, or context-dependent."); 40 | } 41 | return Response.Success($"Attempted to execute menu item: '{menuPath}'. Check Unity logs for confirmation or errors."); 42 | } 43 | catch (Exception e) 44 | { 45 | McpLog.Error($"[MenuItemExecutor] Failed to setup execution for '{menuPath}': {e}"); 46 | return Response.Error($"Error setting up execution for menu item '{menuPath}': {e.Message}"); 47 | } 48 | } 49 | } 50 | } 51 | ``` -------------------------------------------------------------------------------- /MCPForUnity/Editor/Services/IPackageUpdateService.cs: -------------------------------------------------------------------------------- ```csharp 1 | namespace MCPForUnity.Editor.Services 2 | { 3 | /// <summary> 4 | /// Service for checking package updates and version information 5 | /// </summary> 6 | public interface IPackageUpdateService 7 | { 8 | /// <summary> 9 | /// Checks if a newer version of the package is available 10 | /// </summary> 11 | /// <param name="currentVersion">The current package version</param> 12 | /// <returns>Update check result containing availability and latest version info</returns> 13 | UpdateCheckResult CheckForUpdate(string currentVersion); 14 | 15 | /// <summary> 16 | /// Compares two version strings to determine if the first is newer than the second 17 | /// </summary> 18 | /// <param name="version1">First version string</param> 19 | /// <param name="version2">Second version string</param> 20 | /// <returns>True if version1 is newer than version2</returns> 21 | bool IsNewerVersion(string version1, string version2); 22 | 23 | /// <summary> 24 | /// Determines if the package was installed via Git or Asset Store 25 | /// </summary> 26 | /// <returns>True if installed via Git, false if Asset Store or unknown</returns> 27 | bool IsGitInstallation(); 28 | 29 | /// <summary> 30 | /// Clears the cached update check data, forcing a fresh check on next request 31 | /// </summary> 32 | void ClearCache(); 33 | } 34 | 35 | /// <summary> 36 | /// Result of an update check operation 37 | /// </summary> 38 | public class UpdateCheckResult 39 | { 40 | /// <summary> 41 | /// Whether an update is available 42 | /// </summary> 43 | public bool UpdateAvailable { get; set; } 44 | 45 | /// <summary> 46 | /// The latest version available (null if check failed or no update) 47 | /// </summary> 48 | public string LatestVersion { get; set; } 49 | 50 | /// <summary> 51 | /// Whether the check was successful (false if network error, etc.) 52 | /// </summary> 53 | public bool CheckSucceeded { get; set; } 54 | 55 | /// <summary> 56 | /// Optional message about the check result 57 | /// </summary> 58 | public string Message { get; set; } 59 | } 60 | } 61 | ``` -------------------------------------------------------------------------------- /UnityMcpBridge/Editor/Tools/MenuItems/MenuItemExecutor.cs: -------------------------------------------------------------------------------- ```csharp 1 | using System; 2 | using System.Collections.Generic; 3 | using Newtonsoft.Json.Linq; 4 | using UnityEditor; 5 | using MCPForUnity.Editor.Helpers; 6 | 7 | namespace MCPForUnity.Editor.Tools.MenuItems 8 | { 9 | /// <summary> 10 | /// Executes Unity Editor menu items by path with safety checks. 11 | /// </summary> 12 | public static class MenuItemExecutor 13 | { 14 | // Basic blacklist to prevent execution of disruptive menu items. 15 | private static readonly HashSet<string> _menuPathBlacklist = new HashSet<string>( 16 | StringComparer.OrdinalIgnoreCase) 17 | { 18 | "File/Quit", 19 | }; 20 | 21 | /// <summary> 22 | /// Execute a specific menu item. Expects 'menu_path' or 'menuPath' in params. 23 | /// </summary> 24 | public static object Execute(JObject @params) 25 | { 26 | string menuPath = @params["menu_path"]?.ToString() ?? @params["menuPath"]?.ToString(); 27 | if (string.IsNullOrWhiteSpace(menuPath)) 28 | { 29 | return Response.Error("Required parameter 'menu_path' or 'menuPath' is missing or empty."); 30 | } 31 | 32 | if (_menuPathBlacklist.Contains(menuPath)) 33 | { 34 | return Response.Error($"Execution of menu item '{menuPath}' is blocked for safety reasons."); 35 | } 36 | 37 | try 38 | { 39 | bool executed = EditorApplication.ExecuteMenuItem(menuPath); 40 | if (!executed) 41 | { 42 | McpLog.Error($"[MenuItemExecutor] Failed to execute menu item '{menuPath}'. It might be invalid, disabled, or context-dependent."); 43 | return Response.Error($"Failed to execute menu item '{menuPath}'. It might be invalid, disabled, or context-dependent."); 44 | } 45 | return Response.Success($"Attempted to execute menu item: '{menuPath}'. Check Unity logs for confirmation or errors."); 46 | } 47 | catch (Exception e) 48 | { 49 | McpLog.Error($"[MenuItemExecutor] Failed to setup execution for '{menuPath}': {e}"); 50 | return Response.Error($"Error setting up execution for menu item '{menuPath}': {e.Message}"); 51 | } 52 | } 53 | } 54 | } 55 | ``` -------------------------------------------------------------------------------- /TestProjects/UnityMCPTests/Assets/Tests/EditMode/Services/ToolSyncServiceTests.cs: -------------------------------------------------------------------------------- ```csharp 1 | using System.IO; 2 | using NUnit.Framework; 3 | using UnityEngine; 4 | using MCPForUnity.Editor.Data; 5 | using MCPForUnity.Editor.Services; 6 | 7 | namespace MCPForUnityTests.Editor.Services 8 | { 9 | public class ToolSyncServiceTests 10 | { 11 | private ToolSyncService _service; 12 | private string _testToolsDir; 13 | 14 | [SetUp] 15 | public void SetUp() 16 | { 17 | _service = new ToolSyncService(); 18 | _testToolsDir = Path.Combine(Path.GetTempPath(), "UnityMCPTests", "tools"); 19 | 20 | // Clean up any existing test directory 21 | if (Directory.Exists(_testToolsDir)) 22 | { 23 | Directory.Delete(_testToolsDir, true); 24 | } 25 | } 26 | 27 | [TearDown] 28 | public void TearDown() 29 | { 30 | // Clean up test directory 31 | if (Directory.Exists(_testToolsDir)) 32 | { 33 | try 34 | { 35 | Directory.Delete(_testToolsDir, true); 36 | } 37 | catch 38 | { 39 | // Ignore cleanup errors 40 | } 41 | } 42 | } 43 | 44 | [Test] 45 | public void SyncProjectTools_CreatesDestinationDirectory() 46 | { 47 | _service.SyncProjectTools(_testToolsDir); 48 | 49 | Assert.IsTrue(Directory.Exists(_testToolsDir), "Should create destination directory"); 50 | } 51 | 52 | [Test] 53 | public void SyncProjectTools_ReturnsSuccess_WhenNoPythonToolsAssets() 54 | { 55 | var result = _service.SyncProjectTools(_testToolsDir); 56 | 57 | Assert.IsNotNull(result, "Should return a result"); 58 | Assert.AreEqual(0, result.CopiedCount, "Should not copy any files"); 59 | Assert.AreEqual(0, result.ErrorCount, "Should not have errors"); 60 | } 61 | 62 | [Test] 63 | public void SyncProjectTools_ReportsCorrectCounts() 64 | { 65 | var result = _service.SyncProjectTools(_testToolsDir); 66 | 67 | Assert.IsTrue(result.CopiedCount >= 0, "Copied count should be non-negative"); 68 | Assert.IsTrue(result.SkippedCount >= 0, "Skipped count should be non-negative"); 69 | Assert.IsTrue(result.ErrorCount >= 0, "Error count should be non-negative"); 70 | } 71 | } 72 | } 73 | ``` -------------------------------------------------------------------------------- /tests/test_get_sha.py: -------------------------------------------------------------------------------- ```python 1 | import sys 2 | import pathlib 3 | import importlib.util 4 | import types 5 | 6 | 7 | ROOT = pathlib.Path(__file__).resolve().parents[1] 8 | SRC = ROOT / "MCPForUnity" / "UnityMcpServer~" / "src" 9 | sys.path.insert(0, str(SRC)) 10 | 11 | # stub mcp.server.fastmcp to satisfy imports without full dependency 12 | mcp_pkg = types.ModuleType("mcp") 13 | server_pkg = types.ModuleType("mcp.server") 14 | fastmcp_pkg = types.ModuleType("mcp.server.fastmcp") 15 | 16 | 17 | class _Dummy: 18 | pass 19 | 20 | 21 | fastmcp_pkg.FastMCP = _Dummy 22 | fastmcp_pkg.Context = _Dummy 23 | server_pkg.fastmcp = fastmcp_pkg 24 | mcp_pkg.server = server_pkg 25 | sys.modules.setdefault("mcp", mcp_pkg) 26 | sys.modules.setdefault("mcp.server", server_pkg) 27 | sys.modules.setdefault("mcp.server.fastmcp", fastmcp_pkg) 28 | 29 | 30 | def _load_module(path: pathlib.Path, name: str): 31 | spec = importlib.util.spec_from_file_location(name, path) 32 | mod = importlib.util.module_from_spec(spec) 33 | spec.loader.exec_module(mod) 34 | return mod 35 | 36 | 37 | manage_script = _load_module( 38 | SRC / "tools" / "manage_script.py", "manage_script_mod") 39 | 40 | 41 | class DummyMCP: 42 | def __init__(self): 43 | self.tools = {} 44 | 45 | def tool(self, *args, **kwargs): 46 | def deco(fn): 47 | self.tools[fn.__name__] = fn 48 | return fn 49 | return deco 50 | 51 | 52 | def setup_tools(): 53 | mcp = DummyMCP() 54 | manage_script.register_manage_script_tools(mcp) 55 | return mcp.tools 56 | 57 | 58 | def test_get_sha_param_shape_and_routing(monkeypatch): 59 | tools = setup_tools() 60 | get_sha = tools["get_sha"] 61 | 62 | captured = {} 63 | 64 | def fake_send(cmd, params): 65 | captured["cmd"] = cmd 66 | captured["params"] = params 67 | return {"success": True, "data": {"sha256": "abc", "lengthBytes": 1, "lastModifiedUtc": "2020-01-01T00:00:00Z", "uri": "unity://path/Assets/Scripts/A.cs", "path": "Assets/Scripts/A.cs"}} 68 | 69 | monkeypatch.setattr(manage_script, "send_command_with_retry", fake_send) 70 | 71 | resp = get_sha(None, uri="unity://path/Assets/Scripts/A.cs") 72 | assert captured["cmd"] == "manage_script" 73 | assert captured["params"]["action"] == "get_sha" 74 | assert captured["params"]["name"] == "A" 75 | assert captured["params"]["path"].endswith("Assets/Scripts") 76 | assert resp["success"] is True 77 | assert resp["data"] == {"sha256": "abc", "lengthBytes": 1} 78 | ``` -------------------------------------------------------------------------------- /MCPForUnity/Editor/Resources/MenuItems/GetMenuItems.cs: -------------------------------------------------------------------------------- ```csharp 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using MCPForUnity.Editor.Helpers; 5 | using Newtonsoft.Json.Linq; 6 | using UnityEditor; 7 | 8 | namespace MCPForUnity.Editor.Resources.MenuItems 9 | { 10 | /// <summary> 11 | /// Provides a simple read-only resource that returns Unity menu items. 12 | /// </summary> 13 | [McpForUnityResource("get_menu_items")] 14 | public static class GetMenuItems 15 | { 16 | private static List<string> _cached; 17 | 18 | [InitializeOnLoadMethod] 19 | private static void BuildCache() => Refresh(); 20 | 21 | public static object HandleCommand(JObject @params) 22 | { 23 | bool forceRefresh = @params?["refresh"]?.ToObject<bool>() ?? false; 24 | string search = @params?["search"]?.ToString(); 25 | 26 | var items = GetMenuItemsInternal(forceRefresh); 27 | 28 | if (!string.IsNullOrEmpty(search)) 29 | { 30 | items = items 31 | .Where(item => item.IndexOf(search, StringComparison.OrdinalIgnoreCase) >= 0) 32 | .ToList(); 33 | } 34 | 35 | string message = $"Retrieved {items.Count} menu items"; 36 | return Response.Success(message, items); 37 | } 38 | 39 | internal static List<string> GetMenuItemsInternal(bool forceRefresh) 40 | { 41 | if (forceRefresh || _cached == null) 42 | { 43 | Refresh(); 44 | } 45 | 46 | return (_cached ?? new List<string>()).ToList(); 47 | } 48 | 49 | private static void Refresh() 50 | { 51 | try 52 | { 53 | var methods = TypeCache.GetMethodsWithAttribute<MenuItem>(); 54 | _cached = methods 55 | .SelectMany(m => m 56 | .GetCustomAttributes(typeof(MenuItem), false) 57 | .OfType<MenuItem>() 58 | .Select(attr => attr.menuItem)) 59 | .Where(s => !string.IsNullOrEmpty(s)) 60 | .Distinct(StringComparer.Ordinal) 61 | .OrderBy(s => s, StringComparer.Ordinal) 62 | .ToList(); 63 | } 64 | catch (Exception ex) 65 | { 66 | McpLog.Error($"[GetMenuItems] Failed to scan menu items: {ex}"); 67 | _cached ??= new List<string>(); 68 | } 69 | } 70 | } 71 | } 72 | ``` -------------------------------------------------------------------------------- /prune_tool_results.py: -------------------------------------------------------------------------------- ```python 1 | #!/usr/bin/env python3 2 | import sys, json 3 | 4 | def summarize(txt): 5 | try: 6 | obj = json.loads(txt) 7 | except Exception: 8 | return f"tool_result: {len(txt)} bytes" 9 | data = obj.get("data", {}) or {} 10 | msg = obj.get("message") or obj.get("status") or "" 11 | # Common tool shapes 12 | if "sha256" in str(data): 13 | ln = data.get("lengthBytes") or data.get("length") or "" 14 | return f"len={ln}".strip() 15 | if "diagnostics" in data: 16 | diags = data["diagnostics"] or [] 17 | w = sum(d.get("severity","" ).lower()=="warning" for d in diags) 18 | e = sum(d.get("severity","" ).lower() in ("error","fatal") for d in diags) 19 | ok = "OK" if not e else "FAIL" 20 | return f"validate: {ok} (warnings={w}, errors={e})" 21 | if "matches" in data: 22 | m = data["matches"] or [] 23 | if m: 24 | first = m[0] 25 | return f"find_in_file: {len(m)} match(es) first@{first.get('line',0)}:{first.get('col',0)}" 26 | return "find_in_file: 0 matches" 27 | if "lines" in data: # console 28 | lines = data["lines"] or [] 29 | lvls = {"info":0,"warning":0,"error":0} 30 | for L in lines: 31 | lvls[L.get("level","" ).lower()] = lvls.get(L.get("level","" ).lower(),0)+1 32 | return f"console: {len(lines)} lines (info={lvls.get('info',0)},warn={lvls.get('warning',0)},err={lvls.get('error',0)})" 33 | # Fallback: short status 34 | return (msg or "tool_result")[:80] 35 | 36 | def prune_message(msg): 37 | if "content" not in msg: return msg 38 | newc=[] 39 | for c in msg["content"]: 40 | if c.get("type")=="tool_result" and c.get("content"): 41 | out=[] 42 | for chunk in c["content"]: 43 | if chunk.get("type")=="text": 44 | out.append({"type":"text","text":summarize(chunk.get("text","" ))}) 45 | newc.append({"type":"tool_result","tool_use_id":c.get("tool_use_id"),"content":out}) 46 | else: 47 | newc.append(c) 48 | msg["content"]=newc 49 | return msg 50 | 51 | def main(): 52 | convo=json.load(sys.stdin) 53 | if isinstance(convo, dict) and "messages" in convo: 54 | convo["messages"]=[prune_message(m) for m in convo["messages"]] 55 | elif isinstance(convo, list): 56 | convo=[prune_message(m) for m in convo] 57 | json.dump(convo, sys.stdout, ensure_ascii=False) 58 | main() 59 | ``` -------------------------------------------------------------------------------- /MCPForUnity/Editor/Helpers/Response.cs: -------------------------------------------------------------------------------- ```csharp 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | namespace MCPForUnity.Editor.Helpers 5 | { 6 | /// <summary> 7 | /// Provides static methods for creating standardized success and error response objects. 8 | /// Ensures consistent JSON structure for communication back to the Python server. 9 | /// </summary> 10 | public static class Response 11 | { 12 | /// <summary> 13 | /// Creates a standardized success response object. 14 | /// </summary> 15 | /// <param name="message">A message describing the successful operation.</param> 16 | /// <param name="data">Optional additional data to include in the response.</param> 17 | /// <returns>An object representing the success response.</returns> 18 | public static object Success(string message, object data = null) 19 | { 20 | if (data != null) 21 | { 22 | return new 23 | { 24 | success = true, 25 | message = message, 26 | data = data, 27 | }; 28 | } 29 | else 30 | { 31 | return new { success = true, message = message }; 32 | } 33 | } 34 | 35 | /// <summary> 36 | /// Creates a standardized error response object. 37 | /// </summary> 38 | /// <param name="errorCodeOrMessage">A message describing the error.</param> 39 | /// <param name="data">Optional additional data (e.g., error details) to include.</param> 40 | /// <returns>An object representing the error response.</returns> 41 | public static object Error(string errorCodeOrMessage, object data = null) 42 | { 43 | if (data != null) 44 | { 45 | // Note: The key is "error" for error messages, not "message" 46 | return new 47 | { 48 | success = false, 49 | // Preserve original behavior while adding a machine-parsable code field. 50 | // If callers pass a code string, it will be echoed in both code and error. 51 | code = errorCodeOrMessage, 52 | error = errorCodeOrMessage, 53 | data = data, 54 | }; 55 | } 56 | else 57 | { 58 | return new { success = false, code = errorCodeOrMessage, error = errorCodeOrMessage }; 59 | } 60 | } 61 | } 62 | } 63 | ``` -------------------------------------------------------------------------------- /UnityMcpBridge/Editor/Helpers/Response.cs: -------------------------------------------------------------------------------- ```csharp 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | namespace MCPForUnity.Editor.Helpers 5 | { 6 | /// <summary> 7 | /// Provides static methods for creating standardized success and error response objects. 8 | /// Ensures consistent JSON structure for communication back to the Python server. 9 | /// </summary> 10 | public static class Response 11 | { 12 | /// <summary> 13 | /// Creates a standardized success response object. 14 | /// </summary> 15 | /// <param name="message">A message describing the successful operation.</param> 16 | /// <param name="data">Optional additional data to include in the response.</param> 17 | /// <returns>An object representing the success response.</returns> 18 | public static object Success(string message, object data = null) 19 | { 20 | if (data != null) 21 | { 22 | return new 23 | { 24 | success = true, 25 | message = message, 26 | data = data, 27 | }; 28 | } 29 | else 30 | { 31 | return new { success = true, message = message }; 32 | } 33 | } 34 | 35 | /// <summary> 36 | /// Creates a standardized error response object. 37 | /// </summary> 38 | /// <param name="errorCodeOrMessage">A message describing the error.</param> 39 | /// <param name="data">Optional additional data (e.g., error details) to include.</param> 40 | /// <returns>An object representing the error response.</returns> 41 | public static object Error(string errorCodeOrMessage, object data = null) 42 | { 43 | if (data != null) 44 | { 45 | // Note: The key is "error" for error messages, not "message" 46 | return new 47 | { 48 | success = false, 49 | // Preserve original behavior while adding a machine-parsable code field. 50 | // If callers pass a code string, it will be echoed in both code and error. 51 | code = errorCodeOrMessage, 52 | error = errorCodeOrMessage, 53 | data = data, 54 | }; 55 | } 56 | else 57 | { 58 | return new { success = false, code = errorCodeOrMessage, error = errorCodeOrMessage }; 59 | } 60 | } 61 | } 62 | } 63 | ``` -------------------------------------------------------------------------------- /MCPForUnity/UnityMcpServer~/src/tools/manage_scene.py: -------------------------------------------------------------------------------- ```python 1 | from typing import Annotated, Literal, Any 2 | 3 | from mcp.server.fastmcp import Context 4 | from registry import mcp_for_unity_tool 5 | from unity_connection import send_command_with_retry 6 | 7 | 8 | @mcp_for_unity_tool(description="Manage Unity scenes") 9 | def manage_scene( 10 | ctx: Context, 11 | action: Annotated[Literal["create", "load", "save", "get_hierarchy", "get_active", "get_build_settings"], "Perform CRUD operations on Unity scenes."], 12 | name: Annotated[str, 13 | "Scene name. Not required get_active/get_build_settings"] | None = None, 14 | path: Annotated[str, 15 | "Asset path for scene operations (default: 'Assets/')"] | None = None, 16 | build_index: Annotated[int, 17 | "Build index for load/build settings actions"] | None = None, 18 | ) -> dict[str, Any]: 19 | ctx.info(f"Processing manage_scene: {action}") 20 | try: 21 | # Coerce numeric inputs defensively 22 | def _coerce_int(value, default=None): 23 | if value is None: 24 | return default 25 | try: 26 | if isinstance(value, bool): 27 | return default 28 | if isinstance(value, int): 29 | return int(value) 30 | s = str(value).strip() 31 | if s.lower() in ("", "none", "null"): 32 | return default 33 | return int(float(s)) 34 | except Exception: 35 | return default 36 | 37 | coerced_build_index = _coerce_int(build_index, default=None) 38 | 39 | params = {"action": action} 40 | if name: 41 | params["name"] = name 42 | if path: 43 | params["path"] = path 44 | if coerced_build_index is not None: 45 | params["buildIndex"] = coerced_build_index 46 | 47 | # Use centralized retry helper 48 | response = send_command_with_retry("manage_scene", params) 49 | 50 | # Preserve structured failure data; unwrap success into a friendlier shape 51 | if isinstance(response, dict) and response.get("success"): 52 | return {"success": True, "message": response.get("message", "Scene operation successful."), "data": response.get("data")} 53 | return response if isinstance(response, dict) else {"success": False, "message": str(response)} 54 | 55 | except Exception as e: 56 | return {"success": False, "message": f"Python error managing scene: {str(e)}"} 57 | ``` -------------------------------------------------------------------------------- /UnityMcpBridge/UnityMcpServer~/src/tools/manage_scene.py: -------------------------------------------------------------------------------- ```python 1 | from typing import Annotated, Literal, Any 2 | 3 | from mcp.server.fastmcp import Context 4 | from registry import mcp_for_unity_tool 5 | from unity_connection import send_command_with_retry 6 | 7 | 8 | @mcp_for_unity_tool(description="Manage Unity scenes") 9 | def manage_scene( 10 | ctx: Context, 11 | action: Annotated[Literal["create", "load", "save", "get_hierarchy", "get_active", "get_build_settings"], "Perform CRUD operations on Unity scenes."], 12 | name: Annotated[str, 13 | "Scene name. Not required get_active/get_build_settings"] | None = None, 14 | path: Annotated[str, 15 | "Asset path for scene operations (default: 'Assets/')"] | None = None, 16 | build_index: Annotated[int, 17 | "Build index for load/build settings actions"] | None = None, 18 | ) -> dict[str, Any]: 19 | ctx.info(f"Processing manage_scene: {action}") 20 | try: 21 | # Coerce numeric inputs defensively 22 | def _coerce_int(value, default=None): 23 | if value is None: 24 | return default 25 | try: 26 | if isinstance(value, bool): 27 | return default 28 | if isinstance(value, int): 29 | return int(value) 30 | s = str(value).strip() 31 | if s.lower() in ("", "none", "null"): 32 | return default 33 | return int(float(s)) 34 | except Exception: 35 | return default 36 | 37 | coerced_build_index = _coerce_int(build_index, default=None) 38 | 39 | params = {"action": action} 40 | if name: 41 | params["name"] = name 42 | if path: 43 | params["path"] = path 44 | if coerced_build_index is not None: 45 | params["buildIndex"] = coerced_build_index 46 | 47 | # Use centralized retry helper 48 | response = send_command_with_retry("manage_scene", params) 49 | 50 | # Preserve structured failure data; unwrap success into a friendlier shape 51 | if isinstance(response, dict) and response.get("success"): 52 | return {"success": True, "message": response.get("message", "Scene operation successful."), "data": response.get("data")} 53 | return response if isinstance(response, dict) else {"success": False, "message": str(response)} 54 | 55 | except Exception as e: 56 | return {"success": False, "message": f"Python error managing scene: {str(e)}"} 57 | ``` -------------------------------------------------------------------------------- /tests/test_telemetry_endpoint_validation.py: -------------------------------------------------------------------------------- ```python 1 | import os 2 | import importlib 3 | 4 | 5 | def test_endpoint_rejects_non_http(tmp_path, monkeypatch): 6 | # Point data dir to temp to avoid touching real files 7 | monkeypatch.setenv("XDG_DATA_HOME", str(tmp_path)) 8 | monkeypatch.setenv("UNITY_MCP_TELEMETRY_ENDPOINT", "file:///etc/passwd") 9 | 10 | telemetry = importlib.import_module( 11 | "MCPForUnity.UnityMcpServer~.src.telemetry") 12 | importlib.reload(telemetry) 13 | 14 | tc = telemetry.TelemetryCollector() 15 | # Should have fallen back to default endpoint 16 | assert tc.config.endpoint == tc.config.default_endpoint 17 | 18 | 19 | def test_config_preferred_then_env_override(tmp_path, monkeypatch): 20 | # Simulate config telemetry endpoint 21 | monkeypatch.setenv("XDG_DATA_HOME", str(tmp_path)) 22 | monkeypatch.delenv("UNITY_MCP_TELEMETRY_ENDPOINT", raising=False) 23 | 24 | # Patch config.telemetry_endpoint via import mocking 25 | import importlib 26 | cfg_mod = importlib.import_module( 27 | "MCPForUnity.UnityMcpServer~.src.config") 28 | old_endpoint = cfg_mod.config.telemetry_endpoint 29 | cfg_mod.config.telemetry_endpoint = "https://example.com/telemetry" 30 | try: 31 | telemetry = importlib.import_module( 32 | "MCPForUnity.UnityMcpServer~.src.telemetry") 33 | importlib.reload(telemetry) 34 | tc = telemetry.TelemetryCollector() 35 | assert tc.config.endpoint == "https://example.com/telemetry" 36 | 37 | # Env should override config 38 | monkeypatch.setenv("UNITY_MCP_TELEMETRY_ENDPOINT", 39 | "https://override.example/ep") 40 | importlib.reload(telemetry) 41 | tc2 = telemetry.TelemetryCollector() 42 | assert tc2.config.endpoint == "https://override.example/ep" 43 | finally: 44 | cfg_mod.config.telemetry_endpoint = old_endpoint 45 | 46 | 47 | def test_uuid_preserved_on_malformed_milestones(tmp_path, monkeypatch): 48 | monkeypatch.setenv("XDG_DATA_HOME", str(tmp_path)) 49 | 50 | telemetry = importlib.import_module( 51 | "MCPForUnity.UnityMcpServer~.src.telemetry") 52 | importlib.reload(telemetry) 53 | 54 | tc1 = telemetry.TelemetryCollector() 55 | first_uuid = tc1._customer_uuid 56 | 57 | # Write malformed milestones 58 | tc1.config.milestones_file.write_text("{not-json}", encoding="utf-8") 59 | 60 | # Reload collector; UUID should remain same despite bad milestones 61 | importlib.reload(telemetry) 62 | tc2 = telemetry.TelemetryCollector() 63 | assert tc2._customer_uuid == first_uuid 64 | ``` -------------------------------------------------------------------------------- /MCPForUnity/Editor/Tools/RunTests.cs: -------------------------------------------------------------------------------- ```csharp 1 | using System; 2 | using System.Threading.Tasks; 3 | using MCPForUnity.Editor.Helpers; 4 | using MCPForUnity.Editor.Resources.Tests; 5 | using MCPForUnity.Editor.Services; 6 | using Newtonsoft.Json.Linq; 7 | 8 | namespace MCPForUnity.Editor.Tools 9 | { 10 | /// <summary> 11 | /// Executes Unity tests for a specified mode and returns detailed results. 12 | /// </summary> 13 | [McpForUnityTool("run_tests")] 14 | public static class RunTests 15 | { 16 | private const int DefaultTimeoutSeconds = 600; // 10 minutes 17 | 18 | public static async Task<object> HandleCommand(JObject @params) 19 | { 20 | string modeStr = @params?["mode"]?.ToString(); 21 | if (string.IsNullOrWhiteSpace(modeStr)) 22 | { 23 | modeStr = "edit"; 24 | } 25 | 26 | if (!ModeParser.TryParse(modeStr, out var parsedMode, out var parseError)) 27 | { 28 | return Response.Error(parseError); 29 | } 30 | 31 | int timeoutSeconds = DefaultTimeoutSeconds; 32 | try 33 | { 34 | var timeoutToken = @params?["timeoutSeconds"]; 35 | if (timeoutToken != null && int.TryParse(timeoutToken.ToString(), out var parsedTimeout) && parsedTimeout > 0) 36 | { 37 | timeoutSeconds = parsedTimeout; 38 | } 39 | } 40 | catch 41 | { 42 | // Preserve default timeout if parsing fails 43 | } 44 | 45 | var testService = MCPServiceLocator.Tests; 46 | Task<TestRunResult> runTask; 47 | try 48 | { 49 | runTask = testService.RunTestsAsync(parsedMode.Value); 50 | } 51 | catch (Exception ex) 52 | { 53 | return Response.Error($"Failed to start test run: {ex.Message}"); 54 | } 55 | 56 | var timeoutTask = Task.Delay(TimeSpan.FromSeconds(timeoutSeconds)); 57 | var completed = await Task.WhenAny(runTask, timeoutTask).ConfigureAwait(true); 58 | 59 | if (completed != runTask) 60 | { 61 | return Response.Error($"Test run timed out after {timeoutSeconds} seconds"); 62 | } 63 | 64 | var result = await runTask.ConfigureAwait(true); 65 | 66 | string message = 67 | $"{parsedMode.Value} tests completed: {result.Passed}/{result.Total} passed, {result.Failed} failed, {result.Skipped} skipped"; 68 | 69 | var data = result.ToSerializable(parsedMode.Value.ToString()); 70 | return Response.Success(message, data); 71 | } 72 | } 73 | } 74 | ```