@akiojin/unity-mcp-server 2.14.14
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +206 -0
- package/bin/unity-mcp-server +2 -0
- package/package.json +73 -0
- package/src/core/codeIndex.js +163 -0
- package/src/core/codeIndexDb.js +96 -0
- package/src/core/config.js +165 -0
- package/src/core/indexWatcher.js +52 -0
- package/src/core/projectInfo.js +111 -0
- package/src/core/server.js +294 -0
- package/src/core/unityConnection.js +426 -0
- package/src/handlers/analysis/AnalyzeSceneContentsToolHandler.js +35 -0
- package/src/handlers/analysis/FindByComponentToolHandler.js +20 -0
- package/src/handlers/analysis/GetAnimatorStateToolHandler.js +37 -0
- package/src/handlers/analysis/GetComponentValuesToolHandler.js +20 -0
- package/src/handlers/analysis/GetGameObjectDetailsToolHandler.js +35 -0
- package/src/handlers/analysis/GetInputActionsStateToolHandler.js +37 -0
- package/src/handlers/analysis/GetObjectReferencesToolHandler.js +20 -0
- package/src/handlers/asset/AssetDatabaseToolHandler.js +221 -0
- package/src/handlers/asset/AssetDependencyToolHandler.js +201 -0
- package/src/handlers/asset/AssetImportSettingsToolHandler.js +170 -0
- package/src/handlers/asset/CreateMaterialToolHandler.js +96 -0
- package/src/handlers/asset/CreatePrefabToolHandler.js +78 -0
- package/src/handlers/asset/ExitPrefabModeToolHandler.js +83 -0
- package/src/handlers/asset/InstantiatePrefabToolHandler.js +133 -0
- package/src/handlers/asset/ModifyMaterialToolHandler.js +76 -0
- package/src/handlers/asset/ModifyPrefabToolHandler.js +72 -0
- package/src/handlers/asset/OpenPrefabToolHandler.js +121 -0
- package/src/handlers/asset/SavePrefabToolHandler.js +106 -0
- package/src/handlers/base/BaseToolHandler.js +133 -0
- package/src/handlers/compilation/GetCompilationStateToolHandler.js +90 -0
- package/src/handlers/component/AddComponentToolHandler.js +126 -0
- package/src/handlers/component/GetComponentTypesToolHandler.js +100 -0
- package/src/handlers/component/ListComponentsToolHandler.js +85 -0
- package/src/handlers/component/ModifyComponentToolHandler.js +143 -0
- package/src/handlers/component/RemoveComponentToolHandler.js +108 -0
- package/src/handlers/console/ClearConsoleToolHandler.js +160 -0
- package/src/handlers/console/ReadConsoleToolHandler.js +276 -0
- package/src/handlers/editor/LayerManagementToolHandler.js +160 -0
- package/src/handlers/editor/SelectionToolHandler.js +141 -0
- package/src/handlers/editor/TagManagementToolHandler.js +129 -0
- package/src/handlers/editor/ToolManagementToolHandler.js +135 -0
- package/src/handlers/editor/WindowManagementToolHandler.js +125 -0
- package/src/handlers/gameobject/CreateGameObjectToolHandler.js +131 -0
- package/src/handlers/gameobject/DeleteGameObjectToolHandler.js +101 -0
- package/src/handlers/gameobject/FindGameObjectToolHandler.js +119 -0
- package/src/handlers/gameobject/GetHierarchyToolHandler.js +132 -0
- package/src/handlers/gameobject/ModifyGameObjectToolHandler.js +128 -0
- package/src/handlers/index.js +389 -0
- package/src/handlers/input/AddInputActionToolHandler.js +20 -0
- package/src/handlers/input/AddInputBindingToolHandler.js +20 -0
- package/src/handlers/input/CreateActionMapToolHandler.js +20 -0
- package/src/handlers/input/CreateCompositeBindingToolHandler.js +20 -0
- package/src/handlers/input/GamepadSimulationHandler.js +116 -0
- package/src/handlers/input/InputSystemHandler.js +80 -0
- package/src/handlers/input/KeyboardSimulationHandler.js +79 -0
- package/src/handlers/input/ManageControlSchemesToolHandler.js +20 -0
- package/src/handlers/input/MouseSimulationHandler.js +107 -0
- package/src/handlers/input/RemoveActionMapToolHandler.js +20 -0
- package/src/handlers/input/RemoveAllBindingsToolHandler.js +20 -0
- package/src/handlers/input/RemoveInputActionToolHandler.js +20 -0
- package/src/handlers/input/RemoveInputBindingToolHandler.js +20 -0
- package/src/handlers/input/TouchSimulationHandler.js +142 -0
- package/src/handlers/menu/ExecuteMenuItemToolHandler.js +304 -0
- package/src/handlers/package/PackageManagerToolHandler.js +248 -0
- package/src/handlers/package/RegistryConfigToolHandler.js +198 -0
- package/src/handlers/playmode/GetEditorStateToolHandler.js +81 -0
- package/src/handlers/playmode/PauseToolHandler.js +44 -0
- package/src/handlers/playmode/PlayToolHandler.js +91 -0
- package/src/handlers/playmode/StopToolHandler.js +77 -0
- package/src/handlers/playmode/WaitForEditorStateToolHandler.js +45 -0
- package/src/handlers/scene/CreateSceneToolHandler.js +91 -0
- package/src/handlers/scene/GetSceneInfoToolHandler.js +20 -0
- package/src/handlers/scene/ListScenesToolHandler.js +58 -0
- package/src/handlers/scene/LoadSceneToolHandler.js +92 -0
- package/src/handlers/scene/SaveSceneToolHandler.js +76 -0
- package/src/handlers/screenshot/AnalyzeScreenshotToolHandler.js +238 -0
- package/src/handlers/screenshot/CaptureScreenshotToolHandler.js +692 -0
- package/src/handlers/script/BuildCodeIndexToolHandler.js +163 -0
- package/src/handlers/script/ScriptCreateClassFileToolHandler.js +60 -0
- package/src/handlers/script/ScriptEditStructuredToolHandler.js +173 -0
- package/src/handlers/script/ScriptIndexStatusToolHandler.js +61 -0
- package/src/handlers/script/ScriptPackagesListToolHandler.js +103 -0
- package/src/handlers/script/ScriptReadToolHandler.js +106 -0
- package/src/handlers/script/ScriptRefactorRenameToolHandler.js +83 -0
- package/src/handlers/script/ScriptRefsFindToolHandler.js +144 -0
- package/src/handlers/script/ScriptRemoveSymbolToolHandler.js +79 -0
- package/src/handlers/script/ScriptSearchToolHandler.js +320 -0
- package/src/handlers/script/ScriptSymbolFindToolHandler.js +117 -0
- package/src/handlers/script/ScriptSymbolsGetToolHandler.js +96 -0
- package/src/handlers/settings/GetProjectSettingsToolHandler.js +161 -0
- package/src/handlers/settings/UpdateProjectSettingsToolHandler.js +272 -0
- package/src/handlers/system/GetCommandStatsToolHandler.js +25 -0
- package/src/handlers/system/PingToolHandler.js +53 -0
- package/src/handlers/system/RefreshAssetsToolHandler.js +45 -0
- package/src/handlers/ui/ClickUIElementToolHandler.js +110 -0
- package/src/handlers/ui/FindUIElementsToolHandler.js +63 -0
- package/src/handlers/ui/GetUIElementStateToolHandler.js +50 -0
- package/src/handlers/ui/SetUIElementValueToolHandler.js +49 -0
- package/src/handlers/ui/SimulateUIInputToolHandler.js +156 -0
- package/src/handlers/video/CaptureVideoForToolHandler.js +96 -0
- package/src/handlers/video/CaptureVideoStartToolHandler.js +38 -0
- package/src/handlers/video/CaptureVideoStatusToolHandler.js +30 -0
- package/src/handlers/video/CaptureVideoStopToolHandler.js +32 -0
- package/src/lsp/CSharpLspUtils.js +134 -0
- package/src/lsp/LspProcessManager.js +60 -0
- package/src/lsp/LspRpcClient.js +133 -0
- package/src/tools/analysis/analyzeSceneContents.js +100 -0
- package/src/tools/analysis/findByComponent.js +87 -0
- package/src/tools/analysis/getAnimatorState.js +326 -0
- package/src/tools/analysis/getComponentValues.js +182 -0
- package/src/tools/analysis/getGameObjectDetails.js +159 -0
- package/src/tools/analysis/getInputActionsState.js +329 -0
- package/src/tools/analysis/getObjectReferences.js +86 -0
- package/src/tools/input/inputActionsEditor.js +556 -0
- package/src/tools/scene/createScene.js +112 -0
- package/src/tools/scene/getSceneInfo.js +95 -0
- package/src/tools/scene/listScenes.js +82 -0
- package/src/tools/scene/loadScene.js +122 -0
- package/src/tools/scene/saveScene.js +91 -0
- package/src/tools/system/ping.js +72 -0
- package/src/tools/video/recordFor.js +31 -0
- package/src/tools/video/recordPlayMode.js +61 -0
- package/src/utils/csharpParse.js +88 -0
- package/src/utils/validators.js +90 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Ozan Kasıkçı
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
# Unity Editor MCP Server
|
|
2
|
+
|
|
3
|
+
MCP (Model Context Protocol) server for Unity Editor integration. Enables AI assistants like Claude and Cursor to interact directly with Unity Editor for automated game development.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- **70 comprehensive tools** across 13 categories for Unity Editor automation
|
|
8
|
+
- **GameObject management** - Create, find, modify, delete GameObjects with full hierarchy control
|
|
9
|
+
- **Component system** - Add, remove, modify components with property control
|
|
10
|
+
- **Scene management** - Create, load, save, list scenes with build settings integration
|
|
11
|
+
- **Scene analysis** - Deep inspection, component analysis, and performance metrics
|
|
12
|
+
- **Asset management** - Create and modify prefabs, materials, scripts with full control
|
|
13
|
+
- **UI automation** - Find, click, and interact with UI elements programmatically
|
|
14
|
+
- **Input simulation** - Simulate keyboard, mouse, gamepad, and touch input
|
|
15
|
+
- **Play mode controls** - Start, pause, stop Unity play mode for testing
|
|
16
|
+
- **Project settings** - Read and update Unity project settings safely
|
|
17
|
+
- **Editor operations** - Console logs, screenshots, compilation monitoring
|
|
18
|
+
- **Editor control** - Manage tags, layers, selection, windows, and tools
|
|
19
|
+
|
|
20
|
+
## Quick Start
|
|
21
|
+
|
|
22
|
+
### Using npx (Recommended)
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
npx @akiojin/unity-mcp-server@latest
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
### Global Installation
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
npm install -g @akiojin/unity-mcp-server
|
|
32
|
+
unity-mcp-server
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
### Local Installation
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
npm install @akiojin/unity-mcp-server
|
|
39
|
+
npx unity-mcp-server
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
## Unity Setup
|
|
43
|
+
|
|
44
|
+
1. Install the Unity package from: `https://github.com/akiojin/unity-mcp-server.git?path=UnityMCPServer/Packages/unity-mcp-server`
|
|
45
|
+
2. Open Unity Package Manager → Add package from git URL
|
|
46
|
+
3. The package will automatically start a TCP server on port 6400
|
|
47
|
+
|
|
48
|
+
## MCP Client Configuration
|
|
49
|
+
|
|
50
|
+
### Claude Desktop
|
|
51
|
+
|
|
52
|
+
Add to your `claude_desktop_config.json`:
|
|
53
|
+
|
|
54
|
+
```json
|
|
55
|
+
{
|
|
56
|
+
"mcpServers": {
|
|
57
|
+
"unity-mcp-server": {
|
|
58
|
+
"command": "npx",
|
|
59
|
+
"args": ["@akiojin/unity-mcp-server@latest"]
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
### Alternative (if globally installed)
|
|
66
|
+
|
|
67
|
+
```json
|
|
68
|
+
{
|
|
69
|
+
"mcpServers": {
|
|
70
|
+
"unity-mcp-server": {
|
|
71
|
+
"command": "unity-mcp-server"
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
## Available Tools (70 Tools)
|
|
78
|
+
|
|
79
|
+
### System & Core Tools (2 tools)
|
|
80
|
+
- `ping` - Test connection to Unity Editor and verify server status
|
|
81
|
+
- `refresh_assets` - Refresh Unity assets and trigger recompilation
|
|
82
|
+
|
|
83
|
+
### GameObject Management (5 tools)
|
|
84
|
+
- `create_gameobject` - Create GameObjects with primitives, transforms, tags, and layers
|
|
85
|
+
- `find_gameobject` - Find GameObjects by name, tag, layer with pattern matching
|
|
86
|
+
- `modify_gameobject` - Modify GameObject properties (transform, name, active state, parent)
|
|
87
|
+
- `delete_gameobject` - Delete single or multiple GameObjects with child handling
|
|
88
|
+
- `get_hierarchy` - Get scene hierarchy with components and depth control
|
|
89
|
+
|
|
90
|
+
### Component System (5 tools)
|
|
91
|
+
- `add_component` - Add Unity components to GameObjects with initial properties
|
|
92
|
+
- `remove_component` - Remove components from GameObjects with safety checks
|
|
93
|
+
- `modify_component` - Modify component properties with nested property support
|
|
94
|
+
- `list_components` - List all components on a GameObject with type information
|
|
95
|
+
- `get_component_types` - Discover available component types with filtering
|
|
96
|
+
|
|
97
|
+
### Scene Management (5 tools)
|
|
98
|
+
- `create_scene` - Create new scenes with build settings integration
|
|
99
|
+
- `load_scene` - Load scenes in Single or Additive mode
|
|
100
|
+
- `save_scene` - Save current scene with Save As functionality
|
|
101
|
+
- `list_scenes` - List all scenes in project with filtering options
|
|
102
|
+
- `get_scene_info` - Get detailed scene information including GameObject counts
|
|
103
|
+
|
|
104
|
+
### Scene Analysis (7 tools)
|
|
105
|
+
- `get_gameobject_details` - Deep inspection of GameObjects with component details
|
|
106
|
+
- `analyze_scene_contents` - Comprehensive scene statistics and performance metrics
|
|
107
|
+
- `get_component_values` - Get all properties and values of specific components
|
|
108
|
+
- `find_by_component` - Find GameObjects by component type with scope filtering
|
|
109
|
+
- `get_object_references` - Analyze references between objects and assets
|
|
110
|
+
- `get_animator_state` - Get current Animator state, parameters, and transitions
|
|
111
|
+
- `get_animator_runtime_info` - Get runtime Animator info (Play mode only)
|
|
112
|
+
|
|
113
|
+
### Asset Management (11 tools)
|
|
114
|
+
- `create_prefab` - Create prefabs from GameObjects or templates
|
|
115
|
+
- `modify_prefab` - Modify existing prefabs with property changes
|
|
116
|
+
- `instantiate_prefab` - Instantiate prefabs in scenes with transform options
|
|
117
|
+
- `open_prefab` - Open prefabs in Unity's prefab mode for editing
|
|
118
|
+
- `exit_prefab_mode` - Exit prefab mode with save/discard options
|
|
119
|
+
- `save_prefab` - Save prefab changes or apply instance overrides
|
|
120
|
+
- `create_material` - Create new materials with shader and properties
|
|
121
|
+
- `modify_material` - Modify material properties and shaders
|
|
122
|
+
- `manage_asset_import_settings` - Manage asset import settings and presets
|
|
123
|
+
- `manage_asset_database` - Asset database operations (find, move, copy, delete)
|
|
124
|
+
- `analyze_asset_dependencies` - Analyze asset dependencies and find unused assets
|
|
125
|
+
|
|
126
|
+
### Script Management (6 tools)
|
|
127
|
+
- `create_script` - Create new C# scripts with templates (MonoBehaviour, ScriptableObject, etc.)
|
|
128
|
+
- `read_script` - Read script file contents with syntax highlighting
|
|
129
|
+
- `update_script` - Modify existing scripts with validation
|
|
130
|
+
- `delete_script` - Delete script files with dependency checking
|
|
131
|
+
- `list_scripts` - List all scripts in project with filtering and metadata
|
|
132
|
+
- `validate_script` - Validate script syntax and Unity compatibility
|
|
133
|
+
|
|
134
|
+
### Play Mode Controls (4 tools)
|
|
135
|
+
- `play_game` - Start Unity play mode for testing
|
|
136
|
+
- `pause_game` - Pause or resume Unity play mode
|
|
137
|
+
- `stop_game` - Stop Unity play mode and return to edit mode
|
|
138
|
+
- `get_editor_state` - Get current editor state and compilation status
|
|
139
|
+
|
|
140
|
+
### UI Automation (5 tools)
|
|
141
|
+
- `find_ui_elements` - Locate UI elements by type, tag, or name
|
|
142
|
+
- `click_ui_element` - Simulate clicking on UI elements (buttons, toggles)
|
|
143
|
+
- `get_ui_element_state` - Get UI element properties and interaction state
|
|
144
|
+
- `set_ui_element_value` - Set values for input fields, sliders, dropdowns
|
|
145
|
+
- `simulate_ui_input` - Execute complex UI interaction sequences
|
|
146
|
+
|
|
147
|
+
### Input System Simulation (5 tools)
|
|
148
|
+
- `simulate_keyboard` - Simulate keyboard input with key combos and text typing
|
|
149
|
+
- `simulate_mouse` - Simulate mouse movement, clicks, drags, and scrolling
|
|
150
|
+
- `simulate_gamepad` - Simulate gamepad buttons, sticks, triggers, and d-pad
|
|
151
|
+
- `simulate_touch` - Simulate touch gestures (tap, swipe, pinch, multi-touch)
|
|
152
|
+
- `get_current_input_state` - Get current state of all input devices
|
|
153
|
+
|
|
154
|
+
### Editor Operations (5 tools)
|
|
155
|
+
- `execute_menu_item` - Execute Unity menu items programmatically
|
|
156
|
+
- `clear_console` - Clear Unity console logs with filtering options
|
|
157
|
+
- `read_console` - Read console logs with advanced filtering and search
|
|
158
|
+
- `capture_screenshot` - Take screenshots of Game View or Scene View
|
|
159
|
+
- `analyze_screenshot` - Analyze screenshot content with image analysis
|
|
160
|
+
|
|
161
|
+
### Editor Control & Automation (6 tools)
|
|
162
|
+
- `manage_tags` - Manage Unity project tags (add, remove, list)
|
|
163
|
+
- `manage_layers` - Manage Unity project layers with index conversion
|
|
164
|
+
- `manage_selection` - Manage Editor selection (get, set, clear)
|
|
165
|
+
- `manage_windows` - Manage Editor windows (list, focus, get state)
|
|
166
|
+
- `manage_tools` - Manage Editor tools and plugins
|
|
167
|
+
- `get_compilation_state` - Get current compilation state and errors
|
|
168
|
+
|
|
169
|
+
### Project Settings Management (2 tools)
|
|
170
|
+
- `get_project_settings` - Read Unity project settings with granular control
|
|
171
|
+
- Player, Graphics, Quality, Physics, Audio, Time settings
|
|
172
|
+
- Build settings, Tags and layers configuration
|
|
173
|
+
- `update_project_settings` - Safely update project settings
|
|
174
|
+
- Requires explicit confirmation for safety
|
|
175
|
+
- Supports partial updates to specific categories
|
|
176
|
+
|
|
177
|
+
## Requirements
|
|
178
|
+
|
|
179
|
+
- **Unity**: 2020.3 LTS or newer (Unity 6 supported)
|
|
180
|
+
- **Node.js**: 18.0.0 or newer
|
|
181
|
+
- **MCP Client**: Claude Desktop, Cursor, or compatible client
|
|
182
|
+
|
|
183
|
+
## Troubleshooting
|
|
184
|
+
|
|
185
|
+
### Connection Issues
|
|
186
|
+
1. Ensure Unity Editor is running with the Unity package installed
|
|
187
|
+
2. Check Unity console for connection messages
|
|
188
|
+
3. Verify port 6400 is not blocked by firewall
|
|
189
|
+
|
|
190
|
+
### Installation Issues
|
|
191
|
+
```bash
|
|
192
|
+
# Clear npm cache
|
|
193
|
+
npm cache clean --force
|
|
194
|
+
|
|
195
|
+
# Reinstall
|
|
196
|
+
npm uninstall -g @akiojin/unity-mcp-server
|
|
197
|
+
npm install -g @akiojin/unity-mcp-server
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
## Repository
|
|
201
|
+
|
|
202
|
+
Full source code and documentation: https://github.com/akiojin/unity-mcp-server
|
|
203
|
+
|
|
204
|
+
## License
|
|
205
|
+
|
|
206
|
+
MIT License - see LICENSE file for details.
|
package/package.json
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@akiojin/unity-mcp-server",
|
|
3
|
+
"version": "2.14.14",
|
|
4
|
+
"description": "MCP server and Unity Editor bridge — enables AI assistants to control Unity for AI-assisted workflows",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "src/core/server.js",
|
|
7
|
+
"bin": {
|
|
8
|
+
"unity-mcp-server": "./bin/unity-mcp-server"
|
|
9
|
+
},
|
|
10
|
+
"scripts": {
|
|
11
|
+
"start": "node src/core/server.js",
|
|
12
|
+
"dev": "node --watch src/core/server.js",
|
|
13
|
+
"build:index": "node src/tools/buildCodeIndex.js",
|
|
14
|
+
"test": "node --test tests/unit/**/*.test.js tests/integration/*.test.js",
|
|
15
|
+
"test:unit": "NODE_ENV=test node --test tests/unit/**/*.test.js",
|
|
16
|
+
"test:integration": "NODE_ENV=test node --test tests/integration/*.test.js",
|
|
17
|
+
"test:e2e": "NODE_ENV=test node --test tests/e2e/*.test.js",
|
|
18
|
+
"test:coverage": "c8 --reporter=lcov --reporter=text --reporter=html node --test tests/unit/**/*.test.js tests/integration/*.test.js",
|
|
19
|
+
"test:coverage:full": "c8 --reporter=lcov --reporter=text --reporter=html node --test tests/**/*.test.js",
|
|
20
|
+
"test:watch": "node --watch --test tests/unit/**/*.test.js",
|
|
21
|
+
"test:watch:all": "node --watch --test tests/**/*.test.js",
|
|
22
|
+
"test:performance": "node --test tests/performance/*.test.js",
|
|
23
|
+
"test:ci": "c8 --reporter=lcov --check-coverage=false node --test tests/unit/core/config.test.js tests/unit/handlers/PingToolHandler.test.js tests/unit/handlers/CreateGameObjectToolHandler.test.js",
|
|
24
|
+
"test:ci:all": "c8 --reporter=lcov node --test tests/unit/**/*.test.js",
|
|
25
|
+
"test:verbose": "VERBOSE_TEST=true node --test tests/**/*.test.js",
|
|
26
|
+
"prepublishOnly": "npm run test:ci",
|
|
27
|
+
"postinstall": "chmod +x bin/unity-mcp-server || true"
|
|
28
|
+
},
|
|
29
|
+
"keywords": [
|
|
30
|
+
"mcp",
|
|
31
|
+
"unity",
|
|
32
|
+
"unity-editor",
|
|
33
|
+
"model-context-protocol",
|
|
34
|
+
"ai",
|
|
35
|
+
"automation",
|
|
36
|
+
"claude",
|
|
37
|
+
"cursor",
|
|
38
|
+
"gamedev",
|
|
39
|
+
"unity3d"
|
|
40
|
+
],
|
|
41
|
+
"author": "Akio Jinsenji <akio-jinsenji@cloud-creative-studios.com>",
|
|
42
|
+
"license": "MIT",
|
|
43
|
+
"dependencies": {
|
|
44
|
+
"@modelcontextprotocol/sdk": "^0.6.1",
|
|
45
|
+
"better-sqlite3": "^9.4.3"
|
|
46
|
+
},
|
|
47
|
+
"engines": {
|
|
48
|
+
"node": ">=18.0.0"
|
|
49
|
+
},
|
|
50
|
+
"repository": {
|
|
51
|
+
"type": "git",
|
|
52
|
+
"url": "https://github.com/akiojin/unity-mcp-server.git",
|
|
53
|
+
"directory": "mcp-server"
|
|
54
|
+
},
|
|
55
|
+
"homepage": "https://github.com/akiojin/unity-mcp-server#readme",
|
|
56
|
+
"bugs": {
|
|
57
|
+
"url": "https://github.com/akiojin/unity-mcp-server/issues"
|
|
58
|
+
},
|
|
59
|
+
"files": [
|
|
60
|
+
"src/",
|
|
61
|
+
"bin/",
|
|
62
|
+
"README.md",
|
|
63
|
+
"LICENSE"
|
|
64
|
+
],
|
|
65
|
+
"preferGlobal": false,
|
|
66
|
+
"publishConfig": {
|
|
67
|
+
"access": "public"
|
|
68
|
+
},
|
|
69
|
+
"devDependencies": {
|
|
70
|
+
"c8": "^10.1.3",
|
|
71
|
+
"nodemon": "^3.1.7"
|
|
72
|
+
}
|
|
73
|
+
}
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import { ProjectInfoProvider } from './projectInfo.js';
|
|
4
|
+
|
|
5
|
+
export class CodeIndex {
|
|
6
|
+
constructor(unityConnection) {
|
|
7
|
+
this.unityConnection = unityConnection;
|
|
8
|
+
this.projectInfo = new ProjectInfoProvider(unityConnection);
|
|
9
|
+
this.db = null;
|
|
10
|
+
this.dbPath = null;
|
|
11
|
+
this.disabled = false; // set true if better-sqlite3 is unavailable
|
|
12
|
+
this._Database = null;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
async _ensureDriver() {
|
|
16
|
+
if (this.disabled) return false;
|
|
17
|
+
if (this._Database) return true;
|
|
18
|
+
try {
|
|
19
|
+
// Dynamic import to avoid hard failure when native binding is missing
|
|
20
|
+
const mod = await import('better-sqlite3');
|
|
21
|
+
this._Database = mod.default || mod;
|
|
22
|
+
return true;
|
|
23
|
+
} catch (e) {
|
|
24
|
+
// Mark as disabled and operate in fallback (index unavailable)
|
|
25
|
+
this.disabled = true;
|
|
26
|
+
return false;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
async open() {
|
|
31
|
+
if (this.db) return this.db;
|
|
32
|
+
const ok = await this._ensureDriver();
|
|
33
|
+
if (!ok) return null; // index disabled
|
|
34
|
+
const info = await this.projectInfo.get();
|
|
35
|
+
const dir = info.codeIndexRoot;
|
|
36
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
37
|
+
const dbPath = path.join(dir, 'code-index.db');
|
|
38
|
+
this.dbPath = dbPath;
|
|
39
|
+
this.db = new this._Database(dbPath);
|
|
40
|
+
this._initSchema();
|
|
41
|
+
return this.db;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
_initSchema() {
|
|
45
|
+
if (!this.db) return;
|
|
46
|
+
const db = this.db;
|
|
47
|
+
db.exec(`
|
|
48
|
+
PRAGMA journal_mode=WAL;
|
|
49
|
+
CREATE TABLE IF NOT EXISTS meta (
|
|
50
|
+
key TEXT PRIMARY KEY,
|
|
51
|
+
value TEXT
|
|
52
|
+
);
|
|
53
|
+
CREATE TABLE IF NOT EXISTS files (
|
|
54
|
+
path TEXT PRIMARY KEY,
|
|
55
|
+
sig TEXT,
|
|
56
|
+
updatedAt TEXT
|
|
57
|
+
);
|
|
58
|
+
CREATE TABLE IF NOT EXISTS symbols (
|
|
59
|
+
path TEXT NOT NULL,
|
|
60
|
+
name TEXT NOT NULL,
|
|
61
|
+
kind TEXT NOT NULL,
|
|
62
|
+
container TEXT,
|
|
63
|
+
namespace TEXT,
|
|
64
|
+
line INTEGER,
|
|
65
|
+
column INTEGER
|
|
66
|
+
);
|
|
67
|
+
CREATE INDEX IF NOT EXISTS idx_symbols_name ON symbols(name);
|
|
68
|
+
CREATE INDEX IF NOT EXISTS idx_symbols_kind ON symbols(kind);
|
|
69
|
+
CREATE INDEX IF NOT EXISTS idx_symbols_path ON symbols(path);
|
|
70
|
+
`);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
async isReady() {
|
|
74
|
+
const db = await this.open();
|
|
75
|
+
if (!db) return false;
|
|
76
|
+
const row = db.prepare('SELECT COUNT(*) AS c FROM symbols').get();
|
|
77
|
+
return (row?.c || 0) > 0;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
async clearAndLoad(symbols) {
|
|
81
|
+
const db = await this.open();
|
|
82
|
+
if (!db) throw new Error('CodeIndex is unavailable (better-sqlite3 not installed)');
|
|
83
|
+
const insert = db.prepare('INSERT INTO symbols(path,name,kind,container,namespace,line,column) VALUES (?,?,?,?,?,?,?)');
|
|
84
|
+
const tx = db.transaction((rows) => {
|
|
85
|
+
db.exec('DELETE FROM symbols');
|
|
86
|
+
db.exec('DELETE FROM files');
|
|
87
|
+
for (const r of rows) {
|
|
88
|
+
insert.run(r.path, r.name, r.kind, r.container || null, r.ns || r.namespace || null, r.line || null, r.column || null);
|
|
89
|
+
}
|
|
90
|
+
db.prepare('REPLACE INTO meta(key,value) VALUES (?,?)').run('lastIndexedAt', new Date().toISOString());
|
|
91
|
+
});
|
|
92
|
+
tx(symbols || []);
|
|
93
|
+
return { total: symbols?.length || 0 };
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// Incremental APIs
|
|
97
|
+
async getFiles() {
|
|
98
|
+
const db = await this.open();
|
|
99
|
+
if (!db) return new Map();
|
|
100
|
+
const rows = db.prepare('SELECT path, sig FROM files').all();
|
|
101
|
+
const map = new Map();
|
|
102
|
+
for (const r of rows) map.set(String(r.path), String(r.sig || ''));
|
|
103
|
+
return map;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
async upsertFile(pathStr, sig) {
|
|
107
|
+
const db = await this.open();
|
|
108
|
+
if (!db) return;
|
|
109
|
+
db.prepare('REPLACE INTO files(path,sig,updatedAt) VALUES (?,?,?)').run(pathStr, sig || '', new Date().toISOString());
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
async removeFile(pathStr) {
|
|
113
|
+
const db = await this.open();
|
|
114
|
+
if (!db) return;
|
|
115
|
+
const tx = db.transaction((p) => {
|
|
116
|
+
db.prepare('DELETE FROM symbols WHERE path = ?').run(p);
|
|
117
|
+
db.prepare('DELETE FROM files WHERE path = ?').run(p);
|
|
118
|
+
});
|
|
119
|
+
tx(pathStr);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
async replaceSymbolsForPath(pathStr, rows) {
|
|
123
|
+
const db = await this.open();
|
|
124
|
+
if (!db) return;
|
|
125
|
+
const tx = db.transaction((p, list) => {
|
|
126
|
+
db.prepare('DELETE FROM symbols WHERE path = ?').run(p);
|
|
127
|
+
const insert = db.prepare('INSERT INTO symbols(path,name,kind,container,namespace,line,column) VALUES (?,?,?,?,?,?,?)');
|
|
128
|
+
for (const r of list) insert.run(p, r.name, r.kind, r.container || null, r.ns || r.namespace || null, r.line || null, r.column || null);
|
|
129
|
+
db.prepare('REPLACE INTO meta(key,value) VALUES (?,?)').run('lastIndexedAt', new Date().toISOString());
|
|
130
|
+
});
|
|
131
|
+
tx(pathStr, rows || []);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
async querySymbols({ name, kind, scope = 'all', exact = false }) {
|
|
135
|
+
const db = await this.open();
|
|
136
|
+
if (!db) return [];
|
|
137
|
+
let sql = 'SELECT path,name,kind,container,namespace,line,column FROM symbols WHERE 1=1';
|
|
138
|
+
const params = {};
|
|
139
|
+
if (name) {
|
|
140
|
+
if (exact) { sql += ' AND name = @name'; params.name = name; }
|
|
141
|
+
else { sql += ' AND name LIKE @name'; params.name = `%${name}%`; }
|
|
142
|
+
}
|
|
143
|
+
if (kind) { sql += ' AND kind = @kind'; params.kind = kind; }
|
|
144
|
+
const rows = db.prepare(sql).all(params);
|
|
145
|
+
// Apply path-based scope filter in JS (simpler than CASE in SQL)
|
|
146
|
+
const filtered = rows.filter(r => {
|
|
147
|
+
const p = String(r.path || '').replace(/\\\\/g, '/');
|
|
148
|
+
if (scope === 'assets') return p.startsWith('Assets/');
|
|
149
|
+
if (scope === 'packages') return p.startsWith('Packages/') || p.includes('Library/PackageCache/');
|
|
150
|
+
if (scope === 'embedded') return p.startsWith('Packages/');
|
|
151
|
+
return true;
|
|
152
|
+
});
|
|
153
|
+
return filtered.map(r => ({ path: r.path, name: r.name, kind: r.kind, container: r.container, ns: r.namespace, line: r.line, column: r.column }));
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
async getStats() {
|
|
157
|
+
const db = await this.open();
|
|
158
|
+
if (!db) return { total: 0, lastIndexedAt: null };
|
|
159
|
+
const total = db.prepare('SELECT COUNT(*) AS c FROM symbols').get().c || 0;
|
|
160
|
+
const last = db.prepare("SELECT value AS v FROM meta WHERE key = 'lastIndexedAt'").get()?.v || null;
|
|
161
|
+
return { total, lastIndexedAt: last };
|
|
162
|
+
}
|
|
163
|
+
}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import Database from 'better-sqlite3';
|
|
4
|
+
import { logger } from './config.js';
|
|
5
|
+
|
|
6
|
+
let dbCache = new Map();
|
|
7
|
+
|
|
8
|
+
function getDbPath(projectRoot) {
|
|
9
|
+
const dir = path.join(projectRoot, 'Library', 'UnityMCP', 'CodeIndex');
|
|
10
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
11
|
+
return path.join(dir, 'index.db');
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function openDb(projectRoot) {
|
|
15
|
+
const key = path.resolve(projectRoot);
|
|
16
|
+
if (dbCache.has(key)) return dbCache.get(key);
|
|
17
|
+
const dbPath = getDbPath(projectRoot);
|
|
18
|
+
const db = new Database(dbPath);
|
|
19
|
+
db.pragma('journal_mode = WAL');
|
|
20
|
+
db.exec(`
|
|
21
|
+
CREATE TABLE IF NOT EXISTS files (
|
|
22
|
+
path TEXT PRIMARY KEY,
|
|
23
|
+
mtime INTEGER NOT NULL
|
|
24
|
+
);
|
|
25
|
+
CREATE TABLE IF NOT EXISTS symbols (
|
|
26
|
+
path TEXT NOT NULL,
|
|
27
|
+
name TEXT NOT NULL,
|
|
28
|
+
kind TEXT,
|
|
29
|
+
container TEXT,
|
|
30
|
+
ns TEXT,
|
|
31
|
+
line INTEGER,
|
|
32
|
+
column INTEGER,
|
|
33
|
+
FOREIGN KEY(path) REFERENCES files(path)
|
|
34
|
+
);
|
|
35
|
+
CREATE INDEX IF NOT EXISTS idx_symbols_name ON symbols(name);
|
|
36
|
+
CREATE TABLE IF NOT EXISTS refs (
|
|
37
|
+
path TEXT NOT NULL,
|
|
38
|
+
name TEXT NOT NULL,
|
|
39
|
+
line INTEGER,
|
|
40
|
+
snippet TEXT,
|
|
41
|
+
FOREIGN KEY(path) REFERENCES files(path)
|
|
42
|
+
);
|
|
43
|
+
CREATE INDEX IF NOT EXISTS idx_refs_name ON refs(name);
|
|
44
|
+
`);
|
|
45
|
+
dbCache.set(key, db);
|
|
46
|
+
return db;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export function upsertFile(db, filePath, mtimeMs) {
|
|
50
|
+
const stmt = db.prepare('INSERT INTO files(path, mtime) VALUES(?, ?) ON CONFLICT(path) DO UPDATE SET mtime=excluded.mtime');
|
|
51
|
+
stmt.run(filePath, Math.floor(mtimeMs));
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export function replaceSymbols(db, filePath, symbols) {
|
|
55
|
+
const del = db.prepare('DELETE FROM symbols WHERE path = ?');
|
|
56
|
+
del.run(filePath);
|
|
57
|
+
const ins = db.prepare('INSERT INTO symbols(path, name, kind, container, ns, line, column) VALUES(?,?,?,?,?,?,?)');
|
|
58
|
+
const tr = db.transaction((rows) => {
|
|
59
|
+
for (const s of rows) ins.run(filePath, s.name || '', s.kind || '', s.container || null, s.ns || null, s.line || 0, s.column || 0);
|
|
60
|
+
});
|
|
61
|
+
tr(symbols || []);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export function replaceReferences(db, filePath, refs) {
|
|
65
|
+
const del = db.prepare('DELETE FROM refs WHERE path = ?');
|
|
66
|
+
del.run(filePath);
|
|
67
|
+
const ins = db.prepare('INSERT INTO refs(path, name, line, snippet) VALUES(?,?,?,?)');
|
|
68
|
+
const tr = db.transaction((rows) => {
|
|
69
|
+
for (const r of rows) ins.run(filePath, r.name || '', r.line || 0, r.snippet || null);
|
|
70
|
+
});
|
|
71
|
+
tr(refs || []);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export function querySymbolsByName(db, name, kind = null) {
|
|
75
|
+
if (kind) {
|
|
76
|
+
return db.prepare('SELECT path,name,kind,container,ns,line,column FROM symbols WHERE name = ? AND kind = ? LIMIT 500').all(name, kind);
|
|
77
|
+
}
|
|
78
|
+
return db.prepare('SELECT path,name,kind,container,ns,line,column FROM symbols WHERE name = ? LIMIT 500').all(name);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export function queryRefsByName(db, name) {
|
|
82
|
+
return db.prepare('SELECT path,name,line,snippet FROM refs WHERE name = ? LIMIT 1000').all(name);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export function isFresh(projectRoot, filePath, db) {
|
|
86
|
+
try {
|
|
87
|
+
const row = db.prepare('SELECT mtime FROM files WHERE path = ?').get(filePath);
|
|
88
|
+
if (!row) return false;
|
|
89
|
+
const abs = path.join(projectRoot, filePath);
|
|
90
|
+
const st = fs.statSync(abs);
|
|
91
|
+
return Math.floor(st.mtimeMs) === row.mtime;
|
|
92
|
+
} catch {
|
|
93
|
+
return false;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|