@akiojin/unity-mcp-server 2.46.0 → 3.0.0

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/README.md CHANGED
@@ -1,78 +1,12 @@
1
- # Unity MCP Server
1
+ # Unity MCP Server (npm package)
2
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.
3
+ MCP (Model Context Protocol) server for Unity Editor integration.
4
+ Enables AI assistants like Claude and Cursor to interact directly with Unity Editor for automated workflows.
4
5
 
5
- ## Features
6
+ This README documents the **npm package**.
7
+ For the Unity package, OpenUPM setup, and the full repository documentation, see:
6
8
 
7
- - **106 comprehensive tools** across 16+ categories for Unity Editor automation
8
- - **Tool discovery** - Efficient `search_tools` meta-tool for discovering relevant tools (96.2% token reduction)
9
- - **GameObject management** - Create, find, modify, delete GameObjects with full hierarchy control
10
- - **Component system** - Add, remove, modify components with property control
11
- - **Scene management** - Create, load, save, list scenes with build settings integration
12
- - **Scene analysis** - Deep inspection, component analysis, and performance metrics
13
- - **Asset management** - Create and modify prefabs, materials, scripts with full control
14
- - **UI automation** - Find, click, and interact with UI elements programmatically
15
- - **Input simulation** - Simulate keyboard, mouse, gamepad, and touch input
16
- - **Play mode controls** - Start, pause, stop Unity play mode for testing
17
- - **Performance profiling** - Record profiling sessions, collect metrics, save .data files for analysis
18
- - **Project settings** - Read and update Unity project settings safely
19
- - **Editor operations** - Console logs, screenshots, video capture, compilation monitoring
20
- - **Editor control** - Manage tags, layers, selection, windows, and tools
21
-
22
- ## Tool Discovery
23
-
24
- Unity MCP Server provides a **`search_tools`** meta-tool for efficient tool discovery, helping you find relevant tools quickly.
25
-
26
- ### Usage Examples
27
-
28
- ```javascript
29
- // Find tools for GameObject manipulation
30
- {
31
- "tool": "search_tools",
32
- "params": {
33
- "query": "gameobject",
34
- "limit": 10
35
- }
36
- }
37
- // Returns: gameobject_create, gameobject_find, gameobject_modify, ...
38
-
39
- // Filter by category
40
- {
41
- "tool": "search_tools",
42
- "params": {
43
- "category": "scene",
44
- "limit": 10
45
- }
46
- }
47
- // Returns: scene_create, scene_load, scene_save, scene_list, scene_info_get
48
-
49
- // Filter by tags
50
- {
51
- "tool": "search_tools",
52
- "params": {
53
- "tags": ["create", "asset"],
54
- "limit": 5
55
- }
56
- }
57
- // Returns tools that create assets
58
-
59
- // Include full input schemas (when needed)
60
- {
61
- "tool": "search_tools",
62
- "params": {
63
- "query": "screenshot",
64
- "includeSchemas": true
65
- }
66
- }
67
- // Returns full tool definitions with inputSchema
68
- ```
69
-
70
- ### Benefits
71
-
72
- - **Smart filtering** - Search by keywords, categories, tags, or scope (read/write/execute)
73
- - **Relevance scoring** - Results sorted by relevance to your query
74
- - **On-demand schemas** - Full inputSchema only when explicitly requested
75
- - **Easy discovery** - Find the right tool without browsing all 103 tools manually
9
+ - [github.com/akiojin/unity-mcp-server](https://github.com/akiojin/unity-mcp-server)
76
10
 
77
11
  ## Quick Start
78
12
 
@@ -82,25 +16,19 @@ Unity MCP Server provides a **`search_tools`** meta-tool for efficient tool disc
82
16
  npx @akiojin/unity-mcp-server@latest
83
17
  ```
84
18
 
85
- ### Global Installation
19
+ ### HTTP Mode (for HTTP-only networks)
86
20
 
87
21
  ```bash
88
- npm install -g @akiojin/unity-mcp-server
89
- unity-mcp-server
90
- ```
91
-
92
- ### Local Installation
93
-
94
- ```bash
95
- npm install @akiojin/unity-mcp-server
96
- npx unity-mcp-server
22
+ npx @akiojin/unity-mcp-server@latest --http 6401 --no-telemetry
23
+ curl http://localhost:6401/healthz
97
24
  ```
98
25
 
99
26
  ## Unity Setup
100
27
 
101
- 1. Install the Unity package from: `https://github.com/akiojin/unity-mcp-server.git?path=UnityMCPServer/Packages/unity-mcp-server`
28
+ 1. Install the Unity package from:
29
+ - [`https://github.com/akiojin/unity-mcp-server.git?path=UnityMCPServer/Packages/unity-mcp-server`](https://github.com/akiojin/unity-mcp-server.git?path=UnityMCPServer/Packages/unity-mcp-server)
102
30
  2. Open Unity Package Manager → Add package from git URL
103
- 3. The package will automatically start a TCP server on port 6400
31
+ 3. The package starts a TCP server (default port `6400`)
104
32
 
105
33
  ## MCP Client Configuration
106
34
 
@@ -119,328 +47,35 @@ Add to your `claude_desktop_config.json`:
119
47
  }
120
48
  ```
121
49
 
122
- ### Alternative (if globally installed)
50
+ ## Tools & Discovery
51
+
52
+ Unity MCP Server ships **100+ tools**.
53
+ Use the `search_tools` meta-tool to find the right tool quickly.
123
54
 
124
55
  ```json
125
56
  {
126
- "mcpServers": {
127
- "unity-mcp-server": {
128
- "command": "unity-mcp-server"
129
- }
57
+ "tool": "search_tools",
58
+ "params": {
59
+ "query": "create gameobject",
60
+ "limit": 10
130
61
  }
131
62
  }
132
63
  ```
133
64
 
134
- ## Code Index Workflow
135
-
136
- コードの自動編集を安定させるには、以下の 3 ステップでコードインデックスを運用してください。重いフルテストを回す必要はありません。
137
-
138
- 1. **初回または大幅変更後に `code_index_build`**
139
- プロジェクト内の `Assets/` と `Packages/` の C# をすべてスキャンし、`.unity/cache/code-index/code-index.db` にシンボルを格納します。
140
- 2. **ファイル更新後は `code_index_update`**
141
- 編集したファイルだけを配列で渡すと、LSP から最新シンボルを再取得してインデックスを差分更新します。長時間待ちになる `code_index_build` の再実行を避けられます。
142
- 3. **シンボル取得時は `script_symbol_find`**
143
- インデックスが利用可能なら高速に、未構築でも LSP にフォールバックして定義位置を取得できます。`script_edit_*` ツールに渡す `symbolName` 構築に活用してください。
144
-
145
- > **Tip:** `code_index_update` は JSON パラメータ `paths` に絶対パス/相対パスの配列を渡すだけで完了します。内部で存在チェックと LSP リトライを行うため、Unity を起動したままでも短時間で終わります。
146
-
147
- ### インデックス状態を検証したいとき
148
-
149
- ビルド中・未構築・完了後といったステータスを再現したい場合は、`code_index_build` に `delayStartMs` や `throttleMs` を付けて進捗を意図的に保ちます。さらに、`npm run simulate:code-index` を実行すると以下が自動化されます。
150
-
151
- - 既存インデックスのリセット(`--reset`)
152
- - `code_index_build` のバックグラウンド実行(任意の `--delayStart=MS` / `--throttle=MS`)
153
- - 指定間隔での `code_index_status` ポーリング(`--poll=MS`)
154
-
155
- 出力される JSON を追うことで、`ready: false` → `true` へ遷移する様子や `buildJob.status` の変化を簡単に確認できます。
156
-
157
- ## Available Tools (Standardized Names)
158
-
159
- ### System & Core Tools
160
-
161
- - `system_ping` — Test connection to Unity Editor and verify server status
162
- - `system_refresh_assets` — Refresh Unity assets and trigger recompilation
163
- - `system_get_command_stats` — Retrieve recent MCP command usage metrics
164
-
165
- ### GameObject Management
166
-
167
- - `gameobject_create` — Create GameObjects with primitives, transforms, tags, and layers
168
- - `gameobject_find` — Find GameObjects by name, tag, or layer with pattern matching
169
- - `gameobject_modify` — Modify GameObject properties (transform, name, active state, parent)
170
- - `gameobject_delete` — Delete single or multiple GameObjects with child handling
171
- - `gameobject_get_hierarchy` — Inspect scene hierarchy with component details
172
-
173
- ### Component System
174
-
175
- - `component_add` — Add Unity components to GameObjects with initial properties
176
- - `component_remove` — Remove components from GameObjects with safety checks
177
- - `component_modify` — Modify component properties with nested support
178
- - `component_field_set` — Update serialized fields (including private `[SerializeField]` values, arrays, and nested structs)
179
- - `component_list` — List all components on a GameObject with type information
180
- - `component_get_types` — Discover available component types with filtering
181
-
182
- ### Scene Management
183
-
184
- - `scene_create` — Create new scenes with build-settings integration
185
- - `scene_load` — Load scenes in Single or Additive mode
186
- - `scene_save` — Save the active scene (supports Save As)
187
- - `scene_list` — List scenes in the project with filtering options
188
- - `scene_info_get` — Retrieve detailed scene information including GameObject counts
189
-
190
- ### Analysis & Diagnostics
191
-
192
- - `analysis_scene_contents_analyze` — Gather scene statistics and performance metrics
193
- - `analysis_component_find` — Locate GameObjects by component type with scope filtering
194
- - `analysis_component_values_get` — Inspect component properties and values
195
- - `analysis_gameobject_details_get` — Deep inspection of GameObjects and components
196
- - `analysis_object_references_get` — Trace references between objects and assets
197
- - `analysis_animator_state_get` — Inspect Animator layers, parameters, and transitions
198
- - `analysis_animator_runtime_info_get` — Retrieve runtime Animator diagnostics (Play Mode)
199
-
200
- ### Asset Management
201
-
202
- - `asset_prefab_create` — Create prefabs from GameObjects or blank templates
203
- - `asset_prefab_instantiate` — Instantiate prefabs with custom transforms
204
- - `asset_prefab_modify` — Apply property overrides to existing prefabs
205
- - `asset_prefab_open` — Open prefabs in Prefab Mode for editing
206
- - `asset_prefab_exit_mode` — Exit Prefab Mode with save/discard handling
207
- - `asset_prefab_save` — Save prefab changes or apply instance overrides
208
- - `asset_material_create` — Create materials with shader and property setup
209
- - `asset_material_modify` — Update material properties and shaders
210
- - `asset_import_settings_manage` — Inspect or modify asset-import settings and presets
211
- - `asset_database_manage` — Perform asset database operations (find, move, copy, delete)
212
- - `asset_dependency_analyze` — Audit asset dependencies and identify unused assets
213
-
214
- ### Script & Code Tools
215
-
216
- - `script_read` — Read script file contents with syntax-aware formatting
217
- - `script_search` — Search C# sources by substring/regex/glob filters
218
- - `script_symbols_get` — Enumerate symbols within a specific C# file
219
- - `script_symbol_find` — Locate symbol definitions across the project
220
- - `script_refs_find` — List references/usages for a symbol via C# LSP
221
- - `script_edit_snippet` — Apply small text edits within C# files
222
- - `script_edit_structured` — Perform structured symbol edits via C# LSP extensions
223
- - `script_create_class` — Generate a new C# class file from parameters
224
- - `script_remove_symbol` — Delete symbols (types/members) with reference checks
225
- - `script_refactor_rename` — Rename symbols project-wide via LSP
226
- - `script_packages_list` — List installed packages relevant to scripting
227
- - `code_index_status` — Report status of the persistent code index
228
-
229
- ### Code Index Utilities
230
-
231
- - `code_index_build` — フルスキャンでシンボルインデックスを再構築(開発時は `delayStartMs` や `throttleMs` で進捗観測用に速度調整可能)
232
- - `code_index_update` — 変更した C# ファイルのみ差分再インデックス
233
-
234
- ### Play Mode Controls
235
-
236
- - `playmode_play` — Enter Play Mode
237
- - `playmode_pause` — Pause or resume Play Mode
238
- - `playmode_stop` — Exit Play Mode back to Edit Mode
239
- - `playmode_get_state` — Inspect current play/edit/compilation state
240
- - `playmode_wait_for_state` — Await a target play/edit state
241
-
242
- ### UI Automation
243
-
244
- - `ui_find_elements` — Locate UI elements by type, tag, or name
245
- - `ui_click_element` — Simulate clicking UI buttons, toggles, etc.
246
- - `ui_get_element_state` — Inspect UI element properties and interactability
247
- - `ui_set_element_value` — Modify UI input values (sliders, fields, dropdowns)
248
- - `ui_simulate_input` — Execute complex multi-step UI interaction sequences
249
-
250
- ### Input System Utilities
251
-
252
- - `input_system_control` — Dispatch keyboard/mouse/gamepad/touch operations
253
- - `input_keyboard` — Keyboard input with batching, combos, typing, and auto-hold durations
254
- - `input_mouse` — Mouse movement, clicks, drags, per-button press/hold, and batching
255
- - `input_gamepad` — Gamepad buttons, sticks, triggers, d-pad with batching and timed holds
256
- - `input_touch` — Touch gestures (tap/swipe/pinch/multi) with batched steps
257
-
258
- #### 同時押下とホールドの扱い
259
-
260
- - **単一アクションで同時押下**: キーボードは `action:"combo"`、タッチは `action:"multi"`、ゲームパッドのスティックは `action:"stick"` で X/Y を同時指定できます。
261
- - **`actions[]` バッチ**: 異なるボタンやデバイスを同一フレームで実行したい場合は `actions:[{...},{...}]` に並べて送信します(順番を保持)。
262
- - **ホールド時間**: `holdSeconds` を press/button/trigger などに付与すると、その秒数後に自動で release/state reset がスケジュールされます。省略すると押下状態が維持されるので、手動で release アクションを送ってください。
263
- - `input_action_add` — Add actions to an Input Action map
264
- - `input_action_remove` — Remove actions from an Input Action map
265
- - `input_action_map_create` — Create new Input Action maps
266
- - `input_action_map_remove` — Delete existing Input Action maps
267
- - `input_binding_add` — Add bindings to an action (including composites)
268
- - `input_binding_remove` — Remove a specific binding from an action
269
- - `input_binding_remove_all` — Clear all bindings from an action
270
- - `input_binding_composite_create` — Create composite bindings (e.g., 2D vectors)
271
- - `input_control_schemes_manage` — Manage control schemes and device lists
272
- - `input_actions_state_get` — Inspect current Input Actions asset configuration
273
- - `input_actions_asset_analyze` — Produce structured summaries of Input Actions assets
274
-
275
- ### Editor & Console Utilities
276
-
277
- - `menu_item_execute` — Trigger Unity Editor menu items programmatically
278
- - `console_clear` — Clear Unity console logs with filtering options
279
- - `console_read` — Stream Unity console output with advanced filters
280
- - `editor_tags_manage` — Manage project tags (add/remove/list)
281
- - `editor_layers_manage` — Manage project layers with index conversion
282
- - `editor_selection_manage` — Inspect or mutate the current editor selection
283
- - `editor_windows_manage` — Enumerate or focus Unity editor windows
284
- - `editor_tools_manage` — Manage editor tools and plugins
285
- - `compilation_get_state` — Inspect current compilation state and errors
286
-
287
- ### Project Settings & Packages
288
-
289
- - `settings_get` — Read Unity project settings with granular control
290
- - `settings_update` — Safely update project settings (requires confirmation)
291
- - `package_manage` — List or manage Unity packages via Package Manager
292
- - `package_registry_config` — Configure package registries/scopes
293
-
294
- ### Asset Visualization & Capture
295
-
296
- - `screenshot_capture` — Capture Game/Scene view screenshots
297
- - `screenshot_analyze` — Run image analysis on captured screenshots
298
- - `video_capture_start` — Begin recording the Game view to video
299
- - `video_capture_stop` — Stop the current video recording
300
- - `video_capture_status` — Inspect capture status / metadata
301
- - `video_capture_for` — Record for a fixed duration before auto-stop
302
-
303
- ### Testing & Diagnostics
304
-
305
- - `test_run` — Run Unity tests (EditMode/PlayMode)
306
- - `test_get_status` — Query Unity Test Runner progress/results. Parameters:
307
- - `includeTestResults` (bool, default `false`): attaches the latest exported `.unity/test-results/*.json` summary to the response when a test run has completed.
308
- - `includeFileContent` (bool, default `false`): when combined with `includeTestResults`, also returns the JSON file contents as a string so agents can parse the detailed per-test data without reading the file directly.
309
-
310
- ## Architecture
311
-
312
- ### Dependencies
313
-
314
- Unity MCP Server uses the following components for code indexing and analysis:
315
-
316
- | Component | Purpose | Distribution | Notes |
317
- |-----------|---------|--------------|-------|
318
- | **fast-sql** | Code index database | npm dependency | Hybrid backend: better-sqlite3 (native) or sql.js (WASM) |
319
- | **csharp-lsp** | C# symbol analysis | Downloaded on first use | ~80 MB per platform |
320
-
321
- #### Why fast-sql?
322
-
323
- We use **fast-sql** (hybrid SQLite library) for the code index database:
324
-
325
- - **Optimal performance**: Uses better-sqlite3 (native) when available for ~34x faster queries
326
- - **npx compatible**: Falls back to sql.js (WASM) when native bindings unavailable
327
- - **Zero native compilation required**: Works immediately via `npx` without build tools
328
- - **Cross-platform**: Automatic backend selection based on environment
329
-
330
- **Performance comparison** (better-sqlite3 vs sql.js):
331
- - 50K inserts: 29ms vs 53ms (1.8x faster)
332
- - 1000 queries: 0.34μs vs 11.78μs (34x faster)
333
- - LIKE search: 2.5ms vs 7ms (2.8x faster)
334
-
335
- #### csharp-lsp Distribution
336
-
337
- **csharp-lsp (downloaded on first use)**:
338
-
339
- - Large size (~80 MB per platform, ~480 MB for all 6 platforms)
340
- - Too large to bundle in npm package
341
- - Downloaded from GitHub Release on first use of script editing tools
342
-
343
- #### Supported Platforms
344
-
345
- | Platform | fast-sql | csharp-lsp |
346
- |----------|----------|------------|
347
- | Linux x64 | ✅ Native (better-sqlite3) | ✅ Downloaded (~79 MB) |
348
- | Linux arm64 | ✅ Native (better-sqlite3) | ✅ Downloaded (~86 MB) |
349
- | macOS x64 | ✅ Native (better-sqlite3) | ✅ Downloaded (~80 MB) |
350
- | macOS arm64 (Apple Silicon) | ✅ Native (better-sqlite3) | ✅ Downloaded (~86 MB) |
351
- | Windows x64 | ✅ Native (better-sqlite3) | ✅ Downloaded (~80 MB) |
352
- | Windows arm64 | ✅ WASM fallback (sql.js) | ✅ Downloaded (~85 MB) |
353
-
354
- #### Storage Locations
355
-
356
- - **csharp-lsp**: `~/.unity/tools/csharp-lsp/<rid>/`
357
- - **Code index database**: `<workspace>/.unity/cache/code-index/code-index.db`
65
+ More details:
66
+ - Tools & Code Index workflow: [`docs/tools.md`](https://github.com/akiojin/unity-mcp-server/blob/main/docs/tools.md)
67
+ - Configuration reference: [`docs/configuration.md`](https://github.com/akiojin/unity-mcp-server/blob/main/docs/configuration.md)
358
68
 
359
69
  ## Requirements
360
70
 
361
- - **Unity**: 2020.3 LTS or newer (Unity 6 supported)
362
- - **Node.js**: 18.0.0 or newer
363
- - **MCP Client**: Claude Desktop, Cursor, or compatible client
364
-
365
- ## Performance Benchmark
366
-
367
- Response time comparison between Code Index tools and standard Claude Code tools (with DB index built, `watch: true` enabled):
368
-
369
- | Operation | unity-mcp-server | Time | Standard Tool | Time | Result |
370
- |-----------|------------------|------|---------------|------|--------|
371
- | File Read | `script_read` | **instant** | `Read` | **instant** | EQUAL |
372
- | Symbol List | `script_symbols_get` | **instant** | N/A | N/A | PASS |
373
- | Symbol Find | `script_symbol_find` | **instant** | `Grep` | **instant** | EQUAL |
374
- | Text Search | `script_search` | **instant** | `Grep` | **instant** | EQUAL |
375
- | Reference Find | `script_refs_find` | **instant** | `Grep` | **instant** | EQUAL |
376
- | Index Status | `code_index_status` | **instant** | N/A | N/A | PASS |
377
-
378
- **Note**: Performance above requires DB index to be built. Run `code_index_build` before first use.
379
-
380
- ### Worker Thread Implementation
381
-
382
- Since v2.41.x, background index builds run in Worker Threads, so MCP tools are never blocked even with `watch: true`:
383
-
384
- | Scenario | Before Worker Thread | After Worker Thread |
385
- |----------|---------------------|---------------------|
386
- | `system_ping` | **60+ seconds block** | **instant** |
387
- | `code_index_status` | **60+ seconds block** | **instant** |
388
- | Any MCP tool | timeout during build | **instant** |
389
-
390
- See [`docs/benchmark.md`](../docs/benchmark.md) for detailed benchmark results.
71
+ - Unity 2020.3 LTS or newer
72
+ - Node.js 18.x / 20.x / 22.x LTS (23+ not supported)
73
+ - MCP client (Claude Desktop, Cursor, etc.)
391
74
 
392
75
  ## Troubleshooting
393
76
 
394
- ### Connection Issues
395
-
396
- 1. Ensure Unity Editor is running with the Unity package installed
397
- 2. Check Unity console for connection messages
398
- 3. Verify port 6400 is not blocked by firewall
399
-
400
- ### Installation Issues
401
-
402
- ```bash
403
- # Clear npm cache
404
- npm cache clean --force
405
-
406
- # Reinstall
407
- npm uninstall -g @akiojin/unity-mcp-server
408
- npm install -g @akiojin/unity-mcp-server
409
- ```
410
-
411
- ### MCP Client Shows "Capabilities: none"
412
-
413
- If your MCP client (Claude Code, Cursor, etc.) shows "Capabilities: none" despite successful connection:
414
-
415
- **Symptom**: Server connects successfully, but no tools are visible to the client.
416
-
417
- **Root Cause**: Empty capability objects (`resources: {}`, `prompts: {}`) in MCP SDK v0.6.1 cause capability validation to fail silently.
418
-
419
- **Solution**: Update to latest version with the fix:
420
-
421
- ```bash
422
- # Update to latest version (2.41.0+)
423
- npm update -g @akiojin/unity-mcp-server
424
-
425
- # Or reinstall
426
- npm uninstall -g @akiojin/unity-mcp-server
427
- npm install -g @akiojin/unity-mcp-server
428
- ```
429
-
430
- **Verification**: After restart, your MCP client should display 107 available tools.
431
-
432
- ## Repository
433
-
434
- Full source code and documentation: <https://github.com/akiojin/unity-mcp-server>
435
-
436
- ## Release Automation
437
-
438
- - Releases are generated by release-please on `main`.
439
- - If changes are in develop, run `scripts/prepare-release-pr.sh` (creates/auto-merges develop→main PR).
440
- - release-please runs on main and publishes via tags; no automatic back-merge to develop.
441
- - Required checks: Markdown, ESLint & Formatting / Commit Message Lint / Test & Coverage / Package.
442
- - Tags trigger `publish.yml` to push npm, OpenUPM (via tag), and csharp-lsp artifacts; Unity package version is kept in sync via release-please extra-files.
443
- - If OpenUPM lags, create a new release so the tag and Unity package version match.
77
+ - Troubleshooting index: [`docs/troubleshooting/README.md`](https://github.com/akiojin/unity-mcp-server/blob/main/docs/troubleshooting/README.md)
78
+ - “Capabilities: none” fix: [`docs/troubleshooting/capabilities-none.md`](https://github.com/akiojin/unity-mcp-server/blob/main/docs/troubleshooting/capabilities-none.md)
444
79
 
445
80
  ## License
446
81
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@akiojin/unity-mcp-server",
3
- "version": "2.46.0",
3
+ "version": "3.0.0",
4
4
  "description": "MCP server and Unity Editor bridge — enables AI assistants to control Unity for AI-assisted workflows",
5
5
  "type": "module",
6
6
  "main": "src/core/server.js",
@@ -1,4 +1,5 @@
1
1
  import fs from 'fs';
2
+ import os from 'os';
2
3
  import path from 'path';
3
4
  import * as findUpPkg from 'find-up';
4
5
  import { MCPLogger } from './mcpLogger.js';
@@ -67,20 +68,13 @@ function resolvePackageVersion() {
67
68
  /**
68
69
  * Base configuration for Unity MCP Server Server
69
70
  */
70
- const envUnityHost = process.env.UNITY_BIND_HOST || process.env.UNITY_HOST || null;
71
-
72
- const envMcpHost =
73
- process.env.UNITY_MCP_HOST || process.env.UNITY_CLIENT_HOST || process.env.UNITY_HOST || null;
74
-
75
- const envBindHost = process.env.UNITY_BIND_HOST || null;
76
-
77
71
  const baseConfig = {
78
72
  // Unity connection settings
79
73
  unity: {
80
- unityHost: envUnityHost,
81
- mcpHost: envMcpHost,
82
- bindHost: envBindHost,
83
- port: parseInt(process.env.UNITY_PORT || '', 10) || 6400,
74
+ unityHost: null,
75
+ mcpHost: null,
76
+ bindHost: null,
77
+ port: 6400,
84
78
  reconnectDelay: 1000,
85
79
  maxReconnectDelay: 30000,
86
80
  reconnectBackoffMultiplier: 2,
@@ -96,24 +90,21 @@ const baseConfig = {
96
90
 
97
91
  // Logging settings
98
92
  logging: {
99
- level: process.env.LOG_LEVEL || 'info',
93
+ level: 'info',
100
94
  prefix: '[unity-mcp-server]'
101
95
  },
102
96
 
103
97
  // HTTP transport (off by default)
104
98
  http: {
105
- enabled: (process.env.UNITY_MCP_HTTP_ENABLED || 'false').toLowerCase() === 'true',
106
- host: process.env.UNITY_MCP_HTTP_HOST || '0.0.0.0',
107
- port: parseInt(process.env.UNITY_MCP_HTTP_PORT || '', 10) || 6401,
99
+ enabled: false,
100
+ host: '0.0.0.0',
101
+ port: 6401,
108
102
  healthPath: '/healthz',
109
- allowedHosts: (process.env.UNITY_MCP_HTTP_ALLOWED_HOSTS || 'localhost,127.0.0.1')
110
- .split(',')
111
- .map(h => h.trim())
112
- .filter(h => h.length > 0)
103
+ allowedHosts: ['localhost', '127.0.0.1']
113
104
  },
114
105
 
115
106
  telemetry: {
116
- enabled: (process.env.UNITY_MCP_TELEMETRY || 'off').toLowerCase() === 'on',
107
+ enabled: false,
117
108
  destinations: [],
118
109
  fields: []
119
110
  },
@@ -123,13 +114,13 @@ const baseConfig = {
123
114
  // Search-related defaults and engine selection
124
115
  search: {
125
116
  // detail alias: 'compact' maps to returnMode 'snippets'
126
- defaultDetail: (process.env.SEARCH_DEFAULT_DETAIL || 'compact').toLowerCase(), // compact|metadata|snippets|full
127
- engine: (process.env.SEARCH_ENGINE || 'naive').toLowerCase() // naive|treesitter (future)
117
+ defaultDetail: 'compact', // compact|metadata|snippets|full
118
+ engine: 'naive' // naive|treesitter (future)
128
119
  },
129
120
 
130
121
  // LSP client defaults
131
122
  lsp: {
132
- requestTimeoutMs: Number(process.env.LSP_REQUEST_TIMEOUT_MS || 60000)
123
+ requestTimeoutMs: 60000
133
124
  },
134
125
 
135
126
  // Indexing (code index) settings
@@ -137,95 +128,48 @@ const baseConfig = {
137
128
  // Enable periodic incremental index updates (polling watcher)
138
129
  watch: true,
139
130
  // Polling interval (ms)
140
- intervalMs: Number(process.env.INDEX_WATCH_INTERVAL_MS || 15000),
131
+ intervalMs: 15000,
141
132
  // Build options
142
- concurrency: Number(process.env.INDEX_CONCURRENCY || 8),
143
- retry: Number(process.env.INDEX_RETRY || 2),
144
- reportEvery: Number(process.env.INDEX_REPORT_EVERY || 500)
133
+ concurrency: 8,
134
+ retry: 2,
135
+ reportEvery: 500
145
136
  }
146
137
  };
147
138
 
148
139
  /**
149
- * External config resolution (no legacy compatibility):
150
- * Priority:
151
- * 1) UNITY_MCP_CONFIG (explicit file path)
152
- * 2) ./.unity/config.json (project-local)
153
- * 3) ~/.unity/config.json (user-global)
154
- * If none found, create ./.unity/config.json with defaults.
140
+ * External config resolution:
141
+ * - Uses the nearest `.unity/config.json` found by walking up from the current working directory.
142
+ * - Intentionally ignores `~/.unity/config.json` (user-global).
155
143
  */
156
- function ensureDefaultProjectConfig(baseDir) {
157
- const dir = path.resolve(baseDir, '.unity');
158
- const file = path.join(dir, 'config.json');
159
-
160
- try {
161
- if (!fs.existsSync(dir)) {
162
- fs.mkdirSync(dir, { recursive: true });
163
- }
164
-
165
- if (!fs.existsSync(file)) {
166
- const inferredRoot = fs.existsSync(path.join(baseDir, 'Assets')) ? baseDir : '';
167
- const defaultConfig = {
168
- unity: {
169
- unityHost: 'localhost',
170
- mcpHost: 'localhost',
171
- port: 6400
172
- },
173
- project: {
174
- root: inferredRoot ? inferredRoot.replace(/\\/g, '/') : ''
175
- }
176
- };
177
- fs.writeFileSync(file, `${JSON.stringify(defaultConfig, null, 2)}\n`, 'utf8');
178
- }
179
- return file;
180
- } catch (error) {
181
- return null;
182
- }
183
- }
184
-
185
144
  function loadExternalConfig() {
186
145
  if (typeof findUpSyncCompat !== 'function') {
187
146
  return {};
188
147
  }
189
- const explicitPath = process.env.UNITY_MCP_CONFIG;
148
+ let userGlobalPath = null;
149
+ try {
150
+ const homeDir = os.homedir();
151
+ userGlobalPath = homeDir ? path.resolve(homeDir, '.unity', 'config.json') : null;
152
+ } catch {}
190
153
 
191
154
  const projectPath = findUpSyncCompat(
192
155
  directory => {
193
156
  const candidate = path.resolve(directory, '.unity', 'config.json');
157
+ if (userGlobalPath && path.resolve(candidate) === userGlobalPath) return undefined;
194
158
  return fs.existsSync(candidate) ? candidate : undefined;
195
159
  },
196
160
  { cwd: process.cwd() }
197
161
  );
198
- const homeDir = process.env.HOME || process.env.USERPROFILE || '';
199
- const userPath = homeDir ? path.resolve(homeDir, '.unity', 'config.json') : null;
200
162
 
201
- const candidates = [explicitPath, projectPath, userPath].filter(Boolean);
202
- for (const p of candidates) {
203
- try {
204
- if (p && fs.existsSync(p)) {
205
- const raw = fs.readFileSync(p, 'utf8');
206
- const json = JSON.parse(raw);
207
- const out = json && typeof json === 'object' ? json : {};
208
- out.__configPath = p;
209
- return out;
210
- }
211
- } catch (e) {
212
- return { __configLoadError: `${p}: ${e.message}` };
213
- }
214
- }
215
- const fallbackPath = ensureDefaultProjectConfig(process.cwd());
216
- if (fallbackPath && fs.existsSync(fallbackPath)) {
217
- try {
218
- const raw = fs.readFileSync(fallbackPath, 'utf8');
219
- const json = JSON.parse(raw);
220
- const out = json && typeof json === 'object' ? json : {};
221
- out.__configPath = fallbackPath;
222
- out.__configGenerated = true;
223
- return out;
224
- } catch (e) {
225
- return { __configLoadError: `${fallbackPath}: ${e.message}` };
226
- }
163
+ if (!projectPath) return {};
164
+ try {
165
+ const raw = fs.readFileSync(projectPath, 'utf8');
166
+ const json = JSON.parse(raw);
167
+ const out = json && typeof json === 'object' ? json : {};
168
+ out.__configPath = projectPath;
169
+ return out;
170
+ } catch (e) {
171
+ return { __configLoadError: `${projectPath}: ${e.message}` };
227
172
  }
228
- return {};
229
173
  }
230
174
 
231
175
  const external = loadExternalConfig();
@@ -234,22 +178,22 @@ export const config = merge(baseConfig, external);
234
178
  const normalizeUnityConfig = () => {
235
179
  const unityConfig = config.unity || (config.unity = {});
236
180
 
237
- // Legacy aliases coming from config files or env vars
181
+ // Legacy aliases coming from config files
238
182
  const legacyHost = unityConfig.host;
239
183
  const legacyClientHost = unityConfig.clientHost;
240
184
  const legacyBindHost = unityConfig.bindHost;
241
185
 
242
186
  if (!unityConfig.unityHost) {
243
- unityConfig.unityHost = legacyBindHost || legacyHost || envUnityHost || 'localhost';
187
+ unityConfig.unityHost = legacyBindHost || legacyHost || 'localhost';
244
188
  }
245
189
 
246
190
  if (!unityConfig.mcpHost) {
247
- unityConfig.mcpHost = legacyClientHost || envMcpHost || legacyHost || unityConfig.unityHost;
191
+ unityConfig.mcpHost = legacyClientHost || legacyHost || unityConfig.unityHost;
248
192
  }
249
193
 
250
194
  // Keep bindHost for backwards compatibility with legacy code paths
251
195
  if (!unityConfig.bindHost) {
252
- unityConfig.bindHost = legacyBindHost || envBindHost || unityConfig.unityHost;
196
+ unityConfig.bindHost = legacyBindHost || unityConfig.unityHost;
253
197
  }
254
198
 
255
199
  // Maintain legacy properties so older handlers keep working
@@ -291,7 +235,7 @@ if (config.__configLoadError) {
291
235
  // Startup debug log: output config info to stderr for troubleshooting
292
236
  // This helps diagnose connection issues (especially in WSL2/Docker environments)
293
237
  console.error(`[unity-mcp-server] Startup config:`);
294
- console.error(`[unity-mcp-server] Config file: ${config.__configPath || '(defaults)'}`);
238
+ console.error(`[unity-mcp-server] Config file: ${config.__configPath || '(not found)'}`);
295
239
  console.error(
296
240
  `[unity-mcp-server] Unity host: ${config.unity.mcpHost || config.unity.unityHost || 'localhost'}`
297
241
  );
@@ -1,8 +1,36 @@
1
+ import fs from 'fs';
1
2
  import path from 'path';
2
3
  import { logger, config, WORKSPACE_ROOT } from './config.js';
3
4
 
4
5
  const normalize = p => p.replace(/\\/g, '/');
5
6
 
7
+ const looksLikeUnityProjectRoot = dir => {
8
+ try {
9
+ return (
10
+ fs.existsSync(path.join(dir, 'Assets')) &&
11
+ fs.existsSync(path.join(dir, 'Packages')) &&
12
+ fs.statSync(path.join(dir, 'Assets')).isDirectory() &&
13
+ fs.statSync(path.join(dir, 'Packages')).isDirectory()
14
+ );
15
+ } catch {
16
+ return false;
17
+ }
18
+ };
19
+
20
+ const inferUnityProjectRootFromDir = startDir => {
21
+ try {
22
+ let dir = startDir;
23
+ const { root } = path.parse(dir);
24
+ while (true) {
25
+ if (looksLikeUnityProjectRoot(dir)) return dir;
26
+ if (dir === root) return null;
27
+ dir = path.dirname(dir);
28
+ }
29
+ } catch {
30
+ return null;
31
+ }
32
+ };
33
+
6
34
  const resolveDefaultCodeIndexRoot = projectRoot => {
7
35
  const base = WORKSPACE_ROOT || projectRoot || process.cwd();
8
36
  return normalize(path.join(base, '.unity', 'cache', 'code-index'));
@@ -18,21 +46,6 @@ export class ProjectInfoProvider {
18
46
 
19
47
  async get() {
20
48
  if (this.cached) return this.cached;
21
- // Env-driven project root override (primarily for tests)
22
- const envRootRaw = process.env.UNITY_PROJECT_ROOT;
23
- if (typeof envRootRaw === 'string' && envRootRaw.trim().length > 0) {
24
- const envRoot = envRootRaw.trim();
25
- const projectRoot = normalize(path.resolve(envRoot));
26
- const codeIndexRoot = normalize(resolveDefaultCodeIndexRoot(projectRoot));
27
- this.cached = {
28
- projectRoot,
29
- assetsPath: normalize(path.join(projectRoot, 'Assets')),
30
- packagesPath: normalize(path.join(projectRoot, 'Packages')),
31
- codeIndexRoot
32
- };
33
- return this.cached;
34
- }
35
-
36
49
  // Config-driven project root (no env fallback)
37
50
  const cfgRootRaw = config?.project?.root;
38
51
  if (typeof cfgRootRaw === 'string' && cfgRootRaw.trim().length > 0) {
@@ -73,13 +86,28 @@ export class ProjectInfoProvider {
73
86
  logger.warning(`get_editor_info failed: ${e.message}`);
74
87
  }
75
88
  }
89
+
90
+ // Best-effort local inference (when Unity isn't connected)
91
+ const inferredRoot = inferUnityProjectRootFromDir(process.cwd());
92
+ if (inferredRoot) {
93
+ const projectRoot = normalize(inferredRoot);
94
+ const codeIndexRoot = normalize(resolveDefaultCodeIndexRoot(projectRoot));
95
+ this.cached = {
96
+ projectRoot,
97
+ assetsPath: normalize(path.join(projectRoot, 'Assets')),
98
+ packagesPath: normalize(path.join(projectRoot, 'Packages')),
99
+ codeIndexRoot
100
+ };
101
+ return this.cached;
102
+ }
103
+
76
104
  if (typeof cfgRootRaw === 'string') {
77
105
  throw new Error(
78
- 'project.root is configured but empty. Set a valid path in .unity/config.json or UNITY_MCP_CONFIG.'
106
+ 'project.root is configured but empty. Set a valid path in .unity/config.json.'
79
107
  );
80
108
  }
81
109
  throw new Error(
82
- 'Unable to resolve Unity project root. Configure project.root in .unity/config.json or provide UNITY_MCP_CONFIG.'
110
+ 'Unable to resolve Unity project root. Start the server inside a Unity project (directory containing Assets/ and Packages/) or configure project.root in .unity/config.json.'
83
111
  );
84
112
  }
85
113