@akiojin/unity-mcp-server 2.40.2 → 2.40.3

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.
Files changed (90) hide show
  1. package/README.md +21 -0
  2. package/bin/unity-mcp-server +1 -1
  3. package/package.json +1 -1
  4. package/src/core/codeIndex.js +64 -15
  5. package/src/core/server.js +3 -34
  6. package/src/handlers/analysis/AnalyzeSceneContentsToolHandler.js +27 -24
  7. package/src/handlers/analysis/FindByComponentToolHandler.js +4 -1
  8. package/src/handlers/analysis/GetAnimatorStateToolHandler.js +5 -5
  9. package/src/handlers/analysis/GetComponentValuesToolHandler.js +4 -1
  10. package/src/handlers/analysis/GetGameObjectDetailsToolHandler.js +27 -24
  11. package/src/handlers/analysis/GetInputActionsStateToolHandler.js +5 -5
  12. package/src/handlers/analysis/GetObjectReferencesToolHandler.js +4 -1
  13. package/src/handlers/asset/AssetDatabaseManageToolHandler.js +24 -6
  14. package/src/handlers/asset/AssetDependencyAnalyzeToolHandler.js +21 -11
  15. package/src/handlers/asset/AssetImportSettingsManageToolHandler.js +7 -7
  16. package/src/handlers/asset/AssetMaterialCreateToolHandler.js +78 -81
  17. package/src/handlers/asset/AssetMaterialModifyToolHandler.js +57 -61
  18. package/src/handlers/asset/AssetPrefabCreateToolHandler.js +61 -64
  19. package/src/handlers/asset/AssetPrefabExitModeToolHandler.js +9 -13
  20. package/src/handlers/asset/AssetPrefabInstantiateToolHandler.js +110 -116
  21. package/src/handlers/asset/AssetPrefabModifyToolHandler.js +58 -58
  22. package/src/handlers/asset/AssetPrefabOpenToolHandler.js +7 -5
  23. package/src/handlers/asset/AssetPrefabSaveToolHandler.js +13 -6
  24. package/src/handlers/compilation/CompilationGetStateToolHandler.js +4 -3
  25. package/src/handlers/component/ComponentAddToolHandler.js +2 -2
  26. package/src/handlers/component/ComponentGetTypesToolHandler.js +17 -21
  27. package/src/handlers/component/ComponentListToolHandler.js +5 -3
  28. package/src/handlers/component/ComponentModifyToolHandler.js +3 -3
  29. package/src/handlers/component/ComponentRemoveToolHandler.js +2 -2
  30. package/src/handlers/console/ConsoleClearToolHandler.js +36 -46
  31. package/src/handlers/editor/EditorLayersManageToolHandler.js +7 -6
  32. package/src/handlers/editor/EditorTagsManageToolHandler.js +20 -11
  33. package/src/handlers/editor/EditorToolsManageToolHandler.js +2 -2
  34. package/src/handlers/editor/EditorWindowsManageToolHandler.js +6 -5
  35. package/src/handlers/gameobject/GameObjectCreateToolHandler.js +62 -66
  36. package/src/handlers/gameobject/GameObjectDeleteToolHandler.js +9 -9
  37. package/src/handlers/gameobject/GameObjectFindToolHandler.js +13 -11
  38. package/src/handlers/gameobject/GameObjectGetHierarchyToolHandler.js +22 -16
  39. package/src/handlers/input/InputActionAddToolHandler.js +2 -2
  40. package/src/handlers/input/InputActionMapCreateToolHandler.js +2 -2
  41. package/src/handlers/input/InputActionMapRemoveToolHandler.js +2 -2
  42. package/src/handlers/input/InputActionRemoveToolHandler.js +2 -2
  43. package/src/handlers/input/InputBindingAddToolHandler.js +2 -2
  44. package/src/handlers/input/InputBindingCompositeCreateToolHandler.js +2 -2
  45. package/src/handlers/input/InputBindingRemoveAllToolHandler.js +2 -2
  46. package/src/handlers/input/InputBindingRemoveToolHandler.js +2 -2
  47. package/src/handlers/input/InputControlSchemesManageToolHandler.js +2 -2
  48. package/src/handlers/package/PackageManagerToolHandler.js +41 -44
  49. package/src/handlers/package/RegistryConfigToolHandler.js +28 -7
  50. package/src/handlers/playmode/PlaymodeGetStateToolHandler.js +12 -16
  51. package/src/handlers/playmode/PlaymodePauseToolHandler.js +8 -12
  52. package/src/handlers/playmode/PlaymodeWaitForStateToolHandler.js +6 -3
  53. package/src/handlers/scene/GetSceneInfoToolHandler.js +11 -11
  54. package/src/handlers/scene/SceneCreateToolHandler.js +28 -31
  55. package/src/handlers/scene/SceneListToolHandler.js +21 -24
  56. package/src/handlers/scene/SceneLoadToolHandler.js +27 -29
  57. package/src/handlers/scene/SceneSaveToolHandler.js +19 -22
  58. package/src/handlers/screenshot/ScreenshotCaptureToolHandler.js +88 -66
  59. package/src/handlers/script/CodeIndexStatusToolHandler.js +4 -3
  60. package/src/handlers/script/CodeIndexUpdateToolHandler.js +24 -14
  61. package/src/handlers/script/ScriptCreateClassToolHandler.js +44 -9
  62. package/src/handlers/script/ScriptPackagesListToolHandler.js +91 -91
  63. package/src/handlers/script/ScriptRefactorRenameToolHandler.js +80 -71
  64. package/src/handlers/script/ScriptRemoveSymbolToolHandler.js +21 -7
  65. package/src/handlers/script/ScriptSearchToolHandler.js +299 -266
  66. package/src/handlers/script/ScriptSymbolsGetToolHandler.js +88 -79
  67. package/src/handlers/settings/SettingsGetToolHandler.js +28 -13
  68. package/src/handlers/settings/SettingsUpdateToolHandler.js +20 -6
  69. package/src/handlers/ui/UIClickElementToolHandler.js +87 -96
  70. package/src/handlers/ui/UIFindElementsToolHandler.js +45 -55
  71. package/src/handlers/ui/UIGetElementStateToolHandler.js +35 -43
  72. package/src/handlers/ui/UISetElementValueToolHandler.js +42 -49
  73. package/src/handlers/ui/UISimulateInputToolHandler.js +134 -136
  74. package/src/handlers/video/VideoCaptureForToolHandler.js +24 -7
  75. package/src/lsp/LspRpcClient.js +24 -12
  76. package/src/tools/analysis/analyzeSceneContents.js +85 -85
  77. package/src/tools/analysis/findByComponent.js +73 -73
  78. package/src/tools/analysis/getAnimatorState.js +287 -287
  79. package/src/tools/analysis/getComponentValues.js +161 -161
  80. package/src/tools/analysis/getGameObjectDetails.js +138 -138
  81. package/src/tools/analysis/getInputActionsState.js +291 -291
  82. package/src/tools/analysis/getObjectReferences.js +72 -72
  83. package/src/tools/input/inputActionsEditor.js +522 -474
  84. package/src/tools/scene/createScene.js +98 -97
  85. package/src/tools/scene/getSceneInfo.js +82 -81
  86. package/src/tools/scene/listScenes.js +70 -69
  87. package/src/tools/scene/loadScene.js +108 -106
  88. package/src/tools/scene/saveScene.js +78 -77
  89. package/src/tools/system/ping.js +9 -12
  90. package/src/utils/validators.js +2 -2
@@ -24,8 +24,9 @@ export class CodeIndexStatusToolHandler extends BaseToolHandler {
24
24
 
25
25
  async execute() {
26
26
  const jobManager = this.jobManager;
27
- const buildJobs = jobManager.getAllJobs()
28
- .filter((job) => typeof job?.id === 'string' && job.id.startsWith('build-'))
27
+ const buildJobs = jobManager
28
+ .getAllJobs()
29
+ .filter(job => typeof job?.id === 'string' && job.id.startsWith('build-'))
29
30
  .sort((a, b) => new Date(b.startedAt || 0).getTime() - new Date(a.startedAt || 0).getTime());
30
31
  const latestBuildJob = buildJobs.length > 0 ? buildJobs[0] : null;
31
32
 
@@ -61,7 +62,7 @@ export class CodeIndexStatusToolHandler extends BaseToolHandler {
61
62
  let totalFiles = 0;
62
63
  const breakdown = { assets: 0, packages: 0, packageCache: 0, other: 0 };
63
64
 
64
- const visit = (targetPath) => {
65
+ const visit = targetPath => {
65
66
  try {
66
67
  if (!fs.existsSync(targetPath)) return;
67
68
  const stats = fs.statSync(targetPath);
@@ -159,7 +159,9 @@ export class CodeIndexUpdateToolHandler extends BaseToolHandler {
159
159
  let lastErr = null;
160
160
  for (let attempt = 0; attempt <= retry; attempt += 1) {
161
161
  try {
162
- const res = await this.lsp.request('textDocument/documentSymbol', { textDocument: { uri } });
162
+ const res = await this.lsp.request('textDocument/documentSymbol', {
163
+ textDocument: { uri }
164
+ });
163
165
  return res?.result ?? res;
164
166
  } catch (err) {
165
167
  lastErr = err;
@@ -212,23 +214,31 @@ export class CodeIndexUpdateToolHandler extends BaseToolHandler {
212
214
  _toRelative(absPath, projectRoot) {
213
215
  const normAbs = String(absPath).replace(/\\/g, '/');
214
216
  const normRoot = String(projectRoot).replace(/\\/g, '/').replace(/\/$/, '');
215
- return normAbs.startsWith(normRoot)
216
- ? normAbs.substring(normRoot.length + 1)
217
- : normAbs;
217
+ return normAbs.startsWith(normRoot) ? normAbs.substring(normRoot.length + 1) : normAbs;
218
218
  }
219
219
 
220
220
  _kindFromLsp(kind) {
221
221
  switch (kind) {
222
- case 5: return 'class';
223
- case 23: return 'struct';
224
- case 11: return 'interface';
225
- case 10: return 'enum';
226
- case 6: return 'method';
227
- case 7: return 'property';
228
- case 8: return 'field';
229
- case 3: return 'namespace';
230
- case 9: return 'constructor';
231
- default: return 'symbol';
222
+ case 5:
223
+ return 'class';
224
+ case 23:
225
+ return 'struct';
226
+ case 11:
227
+ return 'interface';
228
+ case 10:
229
+ return 'enum';
230
+ case 6:
231
+ return 'method';
232
+ case 7:
233
+ return 'property';
234
+ case 8:
235
+ return 'field';
236
+ case 3:
237
+ return 'namespace';
238
+ case 9:
239
+ return 'constructor';
240
+ default:
241
+ return 'symbol';
232
242
  }
233
243
  }
234
244
  }
@@ -11,26 +11,54 @@ export class ScriptCreateClassToolHandler extends BaseToolHandler {
11
11
  {
12
12
  type: 'object',
13
13
  properties: {
14
- path: { type: 'string', description: 'Project-relative C# file path (e.g., Assets/Scripts/MyClass.cs)' },
14
+ path: {
15
+ type: 'string',
16
+ description: 'Project-relative C# file path (e.g., Assets/Scripts/MyClass.cs)'
17
+ },
15
18
  className: { type: 'string', description: 'Class name' },
16
19
  namespace: { type: 'string', description: 'Optional namespace' },
17
20
  baseType: { type: 'string', description: 'Optional base type (e.g., MonoBehaviour)' },
18
- usings: { type: 'string', description: 'Comma-separated using directives (e.g., System,Newtonsoft.Json)' },
21
+ usings: {
22
+ type: 'string',
23
+ description: 'Comma-separated using directives (e.g., System,Newtonsoft.Json)'
24
+ },
19
25
  partial: { type: 'boolean', description: 'Create as partial class (default: false)' },
20
26
  apply: { type: 'boolean', description: 'Apply immediately (default: false)' }
21
27
  },
22
- required: ['path','className']
28
+ required: ['path', 'className']
23
29
  }
24
30
  );
25
31
  this.unityConnection = unityConnection;
26
32
  }
27
33
 
28
34
  async execute(params) {
29
- const { path: rel, className, namespace: ns, baseType, usings, partial = false, apply = false } = params;
30
- const relative = String(rel).replace(/\\\\/g, '/').endsWith('.cs') ? String(rel).replace(/\\\\/g, '/') : String(rel).replace(/\\\\/g, '/') + '.cs';
31
- const code = this._buildSource({ className: String(className), ns: ns ? String(ns) : null, baseType: baseType ? String(baseType) : null, usings: usings ? String(usings) : null, partial: !!partial });
35
+ const {
36
+ path: rel,
37
+ className,
38
+ namespace: ns,
39
+ baseType,
40
+ usings,
41
+ partial = false,
42
+ apply = false
43
+ } = params;
44
+ const relative = String(rel).replace(/\\\\/g, '/').endsWith('.cs')
45
+ ? String(rel).replace(/\\\\/g, '/')
46
+ : String(rel).replace(/\\\\/g, '/') + '.cs';
47
+ const code = this._buildSource({
48
+ className: String(className),
49
+ ns: ns ? String(ns) : null,
50
+ baseType: baseType ? String(baseType) : null,
51
+ usings: usings ? String(usings) : null,
52
+ partial: !!partial
53
+ });
32
54
  if (!apply) {
33
- return { success: true, applied: false, preview: code.slice(0, 1000), previewTruncated: code.length > 1000, relative: relative };
55
+ return {
56
+ success: true,
57
+ applied: false,
58
+ preview: code.slice(0, 1000),
59
+ previewTruncated: code.length > 1000,
60
+ relative: relative
61
+ };
34
62
  }
35
63
  const full = this._resolveFullPath(relative);
36
64
  fs.mkdirSync(path.dirname(full), { recursive: true });
@@ -46,8 +74,15 @@ export class ScriptCreateClassToolHandler extends BaseToolHandler {
46
74
 
47
75
  _buildSource({ className, ns, baseType, usings, partial }) {
48
76
  const useList = [];
49
- if (usings) useList.push(...usings.split(',').map(s => s.trim()).filter(Boolean));
50
- if (baseType === 'MonoBehaviour' && !useList.includes('UnityEngine')) useList.push('UnityEngine');
77
+ if (usings)
78
+ useList.push(
79
+ ...usings
80
+ .split(',')
81
+ .map(s => s.trim())
82
+ .filter(Boolean)
83
+ );
84
+ if (baseType === 'MonoBehaviour' && !useList.includes('UnityEngine'))
85
+ useList.push('UnityEngine');
51
86
  const header = useList.length ? useList.map(u => `using ${u};`).join('\n') + '\n\n' : '';
52
87
  const partialKw = partial ? ' partial' : '';
53
88
  const baseClause = baseType ? ` : ${baseType}` : '';
@@ -5,99 +5,99 @@ import { ProjectInfoProvider } from '../../core/projectInfo.js';
5
5
  import { logger } from '../../core/config.js';
6
6
 
7
7
  export class ScriptPackagesListToolHandler extends BaseToolHandler {
8
- constructor(unityConnection) {
9
- super(
10
- 'script_packages_list',
11
- 'List Unity packages in the project (optionally include built‑in). BEST PRACTICES: Use to discover available packages and their paths. Set includeBuiltIn=false to see only user packages. Returns package IDs, versions, and resolved paths. Embedded packages can be edited directly. Essential for understanding project dependencies.',
12
- {
13
- type: 'object',
14
- properties: {
15
- includeBuiltIn: {
16
- type: 'boolean',
17
- description: 'If true, includes built‑in packages in results (default: false).'
18
- }
19
- },
20
- required: []
21
- }
22
- );
23
- this.unityConnection = unityConnection;
24
- this.projectInfo = new ProjectInfoProvider(unityConnection);
25
- }
8
+ constructor(unityConnection) {
9
+ super(
10
+ 'script_packages_list',
11
+ 'List Unity packages in the project (optionally include built‑in). BEST PRACTICES: Use to discover available packages and their paths. Set includeBuiltIn=false to see only user packages. Returns package IDs, versions, and resolved paths. Embedded packages can be edited directly. Essential for understanding project dependencies.',
12
+ {
13
+ type: 'object',
14
+ properties: {
15
+ includeBuiltIn: {
16
+ type: 'boolean',
17
+ description: 'If true, includes built‑in packages in results (default: false).'
18
+ }
19
+ },
20
+ required: []
21
+ }
22
+ );
23
+ this.unityConnection = unityConnection;
24
+ this.projectInfo = new ProjectInfoProvider(unityConnection);
25
+ }
26
26
 
27
- async execute(params) {
28
- const { includeBuiltIn = false } = params;
29
- const info = await this.projectInfo.get();
27
+ async execute(params) {
28
+ const { includeBuiltIn = false } = params;
29
+ const info = await this.projectInfo.get();
30
30
 
31
- // Prefer packages-lock.json for authoritative list (includes builtin/embedded/registry)
32
- const lockPath = path.join(info.projectRoot, 'Packages', 'packages-lock.json');
33
- const manifestPath = path.join(info.projectRoot, 'Packages', 'manifest.json');
34
- let results = [];
35
- try {
36
- if (fs.existsSync(lockPath)) {
37
- const json = JSON.parse(fs.readFileSync(lockPath, 'utf8'));
38
- const deps = json?.dependencies || {};
39
- for (const [name, meta] of Object.entries(deps)) {
40
- const source = String(meta.source || '').toLowerCase();
41
- if (!includeBuiltIn && source === 'builtin') continue;
42
- const version = String(meta.version || '');
43
- let resolvedPath = null;
44
- let isEmbedded = source === 'embedded';
45
- if (isEmbedded) {
46
- // For embedded, manifest specifies file:folder
47
- try {
48
- const man = JSON.parse(fs.readFileSync(manifestPath, 'utf8'));
49
- const spec = man?.dependencies?.[name];
50
- if (typeof spec === 'string' && spec.startsWith('file:')) {
51
- const folder = spec.substring('file:'.length);
52
- resolvedPath = path.join(info.projectRoot, 'Packages', folder).replace(/\\/g, '/');
53
- }
54
- } catch {}
55
- }
56
- results.push({
57
- packageId: `${name}@${version}`,
58
- name,
59
- displayName: name,
60
- version,
61
- source,
62
- isEmbedded,
63
- resolvedPath
64
- });
65
- }
66
- } else if (fs.existsSync(manifestPath)) {
67
- const man = JSON.parse(fs.readFileSync(manifestPath, 'utf8'));
68
- const deps = man?.dependencies || {};
69
- for (const [name, spec] of Object.entries(deps)) {
70
- let source = 'registry';
71
- let version = String(spec);
72
- let resolvedPath = null;
73
- let isEmbedded = false;
74
- if (version.startsWith('file:')) {
75
- source = 'embedded';
76
- isEmbedded = true;
77
- const folder = version.substring('file:'.length);
78
- resolvedPath = path.join(info.projectRoot, 'Packages', folder).replace(/\\/g, '/');
79
- }
80
- if (!includeBuiltIn && source === 'builtin') continue;
81
- results.push({
82
- packageId: `${name}@${version}`,
83
- name,
84
- displayName: name,
85
- version,
86
- source,
87
- isEmbedded,
88
- resolvedPath
89
- });
90
- }
91
- } else {
92
- return { success: true, packages: [], totalCount: 0 };
93
- }
94
- } catch (e) {
95
- logger.error(`[script_packages_list] local parse failed: ${e.message}`);
96
- return { error: e.message };
31
+ // Prefer packages-lock.json for authoritative list (includes builtin/embedded/registry)
32
+ const lockPath = path.join(info.projectRoot, 'Packages', 'packages-lock.json');
33
+ const manifestPath = path.join(info.projectRoot, 'Packages', 'manifest.json');
34
+ let results = [];
35
+ try {
36
+ if (fs.existsSync(lockPath)) {
37
+ const json = JSON.parse(fs.readFileSync(lockPath, 'utf8'));
38
+ const deps = json?.dependencies || {};
39
+ for (const [name, meta] of Object.entries(deps)) {
40
+ const source = String(meta.source || '').toLowerCase();
41
+ if (!includeBuiltIn && source === 'builtin') continue;
42
+ const version = String(meta.version || '');
43
+ let resolvedPath = null;
44
+ let isEmbedded = source === 'embedded';
45
+ if (isEmbedded) {
46
+ // For embedded, manifest specifies file:folder
47
+ try {
48
+ const man = JSON.parse(fs.readFileSync(manifestPath, 'utf8'));
49
+ const spec = man?.dependencies?.[name];
50
+ if (typeof spec === 'string' && spec.startsWith('file:')) {
51
+ const folder = spec.substring('file:'.length);
52
+ resolvedPath = path.join(info.projectRoot, 'Packages', folder).replace(/\\/g, '/');
53
+ }
54
+ } catch {}
55
+ }
56
+ results.push({
57
+ packageId: `${name}@${version}`,
58
+ name,
59
+ displayName: name,
60
+ version,
61
+ source,
62
+ isEmbedded,
63
+ resolvedPath
64
+ });
97
65
  }
98
-
99
- // Sort by name
100
- results.sort((a, b) => a.name.localeCompare(b.name));
101
- return { success: true, packages: results, totalCount: results.length };
66
+ } else if (fs.existsSync(manifestPath)) {
67
+ const man = JSON.parse(fs.readFileSync(manifestPath, 'utf8'));
68
+ const deps = man?.dependencies || {};
69
+ for (const [name, spec] of Object.entries(deps)) {
70
+ let source = 'registry';
71
+ let version = String(spec);
72
+ let resolvedPath = null;
73
+ let isEmbedded = false;
74
+ if (version.startsWith('file:')) {
75
+ source = 'embedded';
76
+ isEmbedded = true;
77
+ const folder = version.substring('file:'.length);
78
+ resolvedPath = path.join(info.projectRoot, 'Packages', folder).replace(/\\/g, '/');
79
+ }
80
+ if (!includeBuiltIn && source === 'builtin') continue;
81
+ results.push({
82
+ packageId: `${name}@${version}`,
83
+ name,
84
+ displayName: name,
85
+ version,
86
+ source,
87
+ isEmbedded,
88
+ resolvedPath
89
+ });
90
+ }
91
+ } else {
92
+ return { success: true, packages: [], totalCount: 0 };
93
+ }
94
+ } catch (e) {
95
+ logger.error(`[script_packages_list] local parse failed: ${e.message}`);
96
+ return { error: e.message };
102
97
  }
98
+
99
+ // Sort by name
100
+ results.sort((a, b) => a.name.localeCompare(b.name));
101
+ return { success: true, packages: results, totalCount: results.length };
102
+ }
103
103
  }
@@ -3,81 +3,90 @@ import { LspRpcClient } from '../../lsp/LspRpcClient.js';
3
3
  import { ProjectInfoProvider } from '../../core/projectInfo.js';
4
4
 
5
5
  export class ScriptRefactorRenameToolHandler extends BaseToolHandler {
6
- constructor(unityConnection) {
7
- super(
8
- 'script_refactor_rename',
9
- 'Refactor: rename a symbol across the project using the bundled C# LSP. Required params: relative (file path starting with Assets/ or Packages/), namePath (container path like Outer/Nested/Member), newName. Guidance: resolve targets first (script_symbols_get/script_symbol_find), prefer fully-qualified namePath to avoid ambiguity, and use preview for diagnostics only (apply proceeds even if diagnostics exist; errors are returned in response). Responses are summarized (errors≤30, message≤200 chars, large text≤1000 chars).',
10
- {
11
- type: 'object',
12
- properties: {
13
- relative: { type: 'string', description: 'Project-relative file path (Assets/ or Packages/)' },
14
- namePath: { type: 'string', description: 'Symbol path like Class/Method' },
15
- newName: { type: 'string', description: 'New name' },
16
- preview: { type: 'boolean', description: 'Preview changes before applying (default: true)' }
17
- },
18
- required: ['relative', 'namePath', 'newName']
19
- }
20
- );
21
- this.projectInfo = new ProjectInfoProvider(unityConnection);
22
- this.lsp = null;
23
- }
6
+ constructor(unityConnection) {
7
+ super(
8
+ 'script_refactor_rename',
9
+ 'Refactor: rename a symbol across the project using the bundled C# LSP. Required params: relative (file path starting with Assets/ or Packages/), namePath (container path like Outer/Nested/Member), newName. Guidance: resolve targets first (script_symbols_get/script_symbol_find), prefer fully-qualified namePath to avoid ambiguity, and use preview for diagnostics only (apply proceeds even if diagnostics exist; errors are returned in response). Responses are summarized (errors≤30, message≤200 chars, large text≤1000 chars).',
10
+ {
11
+ type: 'object',
12
+ properties: {
13
+ relative: {
14
+ type: 'string',
15
+ description: 'Project-relative file path (Assets/ or Packages/)'
16
+ },
17
+ namePath: { type: 'string', description: 'Symbol path like Class/Method' },
18
+ newName: { type: 'string', description: 'New name' },
19
+ preview: {
20
+ type: 'boolean',
21
+ description: 'Preview changes before applying (default: true)'
22
+ }
23
+ },
24
+ required: ['relative', 'namePath', 'newName']
25
+ }
26
+ );
27
+ this.projectInfo = new ProjectInfoProvider(unityConnection);
28
+ this.lsp = null;
29
+ }
24
30
 
25
- validate(params) {
26
- super.validate(params);
27
- const { relative, namePath, newName } = params;
28
- if (!relative || !namePath || !newName) throw new Error('relative, namePath, newName are required');
29
- }
31
+ validate(params) {
32
+ super.validate(params);
33
+ const { relative, namePath, newName } = params;
34
+ if (!relative || !namePath || !newName)
35
+ throw new Error('relative, namePath, newName are required');
36
+ }
30
37
 
31
- async execute(params) {
32
- const { relative, namePath, newName, preview = true } = params;
33
- const info = await this.projectInfo.get();
34
- if (!this.lsp) this.lsp = new LspRpcClient(info.projectRoot);
35
- const resp = await this.lsp.request('mcp/renameByNamePath', {
36
- relative: String(relative).replace(/\\\\/g, '/'),
37
- namePath: String(namePath),
38
- newName: String(newName),
39
- apply: !preview
40
- });
41
- const r = resp?.result ?? resp;
42
- return this._summarizeResult(r);
43
- }
38
+ async execute(params) {
39
+ const { relative, namePath, newName, preview = true } = params;
40
+ const info = await this.projectInfo.get();
41
+ if (!this.lsp) this.lsp = new LspRpcClient(info.projectRoot);
42
+ const resp = await this.lsp.request('mcp/renameByNamePath', {
43
+ relative: String(relative).replace(/\\\\/g, '/'),
44
+ namePath: String(namePath),
45
+ newName: String(newName),
46
+ apply: !preview
47
+ });
48
+ const r = resp?.result ?? resp;
49
+ return this._summarizeResult(r);
50
+ }
44
51
 
45
- _summarizeResult(res) {
46
- if (!res || typeof res !== 'object') return res;
47
- const MAX_ERRORS = 30;
48
- const MAX_MSG_LEN = 200;
49
- const MAX_TEXT_LEN = 1000;
50
- const out = {};
51
- if ('id' in res) out.id = res.id;
52
- if ('success' in res) out.success = !!res.success;
53
- if ('applied' in res) out.applied = !!res.applied;
54
- if (Array.isArray(res.errors)) {
55
- const trimmed = res.errors.slice(0, MAX_ERRORS).map(e => {
56
- const o = {};
57
- if (e && typeof e === 'object') {
58
- if ('id' in e) o.id = e.id;
59
- if ('message' in e) o.message = String(e.message).slice(0, MAX_MSG_LEN);
60
- if ('file' in e) o.file = String(e.file).slice(0, 260);
61
- if ('line' in e) o.line = e.line;
62
- if ('column' in e) o.column = e.column;
63
- } else { o.message = String(e).slice(0, MAX_MSG_LEN); }
64
- return o;
65
- });
66
- out.errorCount = trimmed.length;
67
- out.totalErrors = res.errors.length;
68
- out.errors = trimmed;
52
+ _summarizeResult(res) {
53
+ if (!res || typeof res !== 'object') return res;
54
+ const MAX_ERRORS = 30;
55
+ const MAX_MSG_LEN = 200;
56
+ const MAX_TEXT_LEN = 1000;
57
+ const out = {};
58
+ if ('id' in res) out.id = res.id;
59
+ if ('success' in res) out.success = !!res.success;
60
+ if ('applied' in res) out.applied = !!res.applied;
61
+ if (Array.isArray(res.errors)) {
62
+ const trimmed = res.errors.slice(0, MAX_ERRORS).map(e => {
63
+ const o = {};
64
+ if (e && typeof e === 'object') {
65
+ if ('id' in e) o.id = e.id;
66
+ if ('message' in e) o.message = String(e.message).slice(0, MAX_MSG_LEN);
67
+ if ('file' in e) o.file = String(e.file).slice(0, 260);
68
+ if ('line' in e) o.line = e.line;
69
+ if ('column' in e) o.column = e.column;
70
+ } else {
71
+ o.message = String(e).slice(0, MAX_MSG_LEN);
69
72
  }
70
- // workspace情報は返さない(厳格: .sln必須のため)
73
+ return o;
74
+ });
75
+ out.errorCount = trimmed.length;
76
+ out.totalErrors = res.errors.length;
77
+ out.errors = trimmed;
78
+ }
79
+ // workspace情報は返さない(厳格: .sln必須のため)
71
80
 
72
- for (const k of ['preview','diff','text','content']) {
73
- if (typeof res[k] === 'string' && res[k].length > 0) {
74
- out[k] = res[k].slice(0, MAX_TEXT_LEN);
75
- if (res[k].length > MAX_TEXT_LEN) out[`${k}Truncated`] = true;
76
- }
77
- }
78
- for (const k of ['operation','path','relative','symbolName']) {
79
- if (res[k] !== undefined) out[k] = res[k];
80
- }
81
- return Object.keys(out).length ? out : res;
81
+ for (const k of ['preview', 'diff', 'text', 'content']) {
82
+ if (typeof res[k] === 'string' && res[k].length > 0) {
83
+ out[k] = res[k].slice(0, MAX_TEXT_LEN);
84
+ if (res[k].length > MAX_TEXT_LEN) out[`${k}Truncated`] = true;
85
+ }
86
+ }
87
+ for (const k of ['operation', 'path', 'relative', 'symbolName']) {
88
+ if (res[k] !== undefined) out[k] = res[k];
82
89
  }
90
+ return Object.keys(out).length ? out : res;
91
+ }
83
92
  }
@@ -14,10 +14,16 @@ export class ScriptRemoveSymbolToolHandler extends BaseToolHandler {
14
14
  path: { type: 'string', description: 'Project-relative C# file path' },
15
15
  namePath: { type: 'string', description: 'Symbol path like Outer/Nested/Member' },
16
16
  apply: { type: 'boolean', description: 'Apply changes immediately (default: false)' },
17
- failOnReferences: { type: 'boolean', description: 'Fail if symbol has references (default: true)' },
18
- removeEmptyFile: { type: 'boolean', description: 'Remove file if it becomes empty (default: false)' }
17
+ failOnReferences: {
18
+ type: 'boolean',
19
+ description: 'Fail if symbol has references (default: true)'
20
+ },
21
+ removeEmptyFile: {
22
+ type: 'boolean',
23
+ description: 'Remove file if it becomes empty (default: false)'
24
+ }
19
25
  },
20
- required: ['path','namePath']
26
+ required: ['path', 'namePath']
21
27
  }
22
28
  );
23
29
  this.projectInfo = new ProjectInfoProvider(unityConnection);
@@ -25,7 +31,13 @@ export class ScriptRemoveSymbolToolHandler extends BaseToolHandler {
25
31
  }
26
32
 
27
33
  async execute(params) {
28
- const { path, namePath, apply = false, failOnReferences = true, removeEmptyFile = false } = params;
34
+ const {
35
+ path,
36
+ namePath,
37
+ apply = false,
38
+ failOnReferences = true,
39
+ removeEmptyFile = false
40
+ } = params;
29
41
  const info = await this.projectInfo.get();
30
42
  if (!this.lsp) this.lsp = new LspRpcClient(info.projectRoot);
31
43
  const resp = await this.lsp.request('mcp/removeSymbol', {
@@ -56,7 +68,9 @@ export class ScriptRemoveSymbolToolHandler extends BaseToolHandler {
56
68
  if ('file' in e) o.file = String(e.file).slice(0, 260);
57
69
  if ('line' in e) o.line = e.line;
58
70
  if ('column' in e) o.column = e.column;
59
- } else { o.message = String(e).slice(0, MAX_MSG_LEN); }
71
+ } else {
72
+ o.message = String(e).slice(0, MAX_MSG_LEN);
73
+ }
60
74
  return o;
61
75
  });
62
76
  out.errorCount = trimmed.length;
@@ -65,13 +79,13 @@ export class ScriptRemoveSymbolToolHandler extends BaseToolHandler {
65
79
  }
66
80
  // workspace情報は返さない(厳格: .sln必須のため)
67
81
 
68
- for (const k of ['preview','diff','text','content']) {
82
+ for (const k of ['preview', 'diff', 'text', 'content']) {
69
83
  if (typeof res[k] === 'string' && res[k].length > 0) {
70
84
  out[k] = res[k].slice(0, MAX_TEXT_LEN);
71
85
  if (res[k].length > MAX_TEXT_LEN) out[`${k}Truncated`] = true;
72
86
  }
73
87
  }
74
- for (const k of ['operation','path','relative','symbolName']) {
88
+ for (const k of ['operation', 'path', 'relative', 'symbolName']) {
75
89
  if (res[k] !== undefined) out[k] = res[k];
76
90
  }
77
91
  return Object.keys(out).length ? out : res;