unity-mcp-server 1.0.0 → 1.1.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 +22 -6
- package/dist/index.d.ts +1 -3
- package/dist/index.js +134 -58
- package/dist/readers.d.ts +90 -0
- package/dist/readers.js +502 -0
- package/package.json +1 -1
- package/server.json +3 -3
- package/src/index.ts +272 -56
- package/src/readers.ts +518 -0
package/src/index.ts
CHANGED
|
@@ -1,38 +1,19 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
/**
|
|
3
3
|
* unity-mcp-server – Lightweight MCP server for any Unity project.
|
|
4
|
-
* Exposes tools
|
|
5
|
-
* Set UNITY_PROJECT_PATH to the Unity project root in your MCP client config.
|
|
6
|
-
* Run: node dist/index.js (after npm run build).
|
|
4
|
+
* Exposes tools for project info, build scenes, agent docs, packages, code, assets, and more. No Unity Editor required.
|
|
7
5
|
*/
|
|
8
6
|
|
|
9
|
-
import {
|
|
10
|
-
import { join, resolve } from "node:path";
|
|
7
|
+
import { resolve } from "node:path";
|
|
11
8
|
import { z } from "zod";
|
|
9
|
+
import * as R from "./readers.js";
|
|
12
10
|
|
|
13
11
|
function getProjectRoot(): string {
|
|
14
12
|
const env = process.env.UNITY_PROJECT_PATH;
|
|
15
13
|
if (env) return resolve(env);
|
|
16
14
|
console.error("UNITY_PROJECT_PATH is required. Set it in your MCP client config (e.g. Cursor) to your Unity project root.");
|
|
17
15
|
process.exit(1);
|
|
18
|
-
return "";
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
function getBuildScenes(projectRoot: string): { index: number; path: string; name: string }[] {
|
|
22
|
-
const path = join(projectRoot, "ProjectSettings", "EditorBuildSettings.asset");
|
|
23
|
-
if (!existsSync(path)) return [];
|
|
24
|
-
const content = readFileSync(path, "utf-8");
|
|
25
|
-
const scenes: { index: number; path: string; name: string }[] = [];
|
|
26
|
-
let index = 0;
|
|
27
|
-
const pathRe = /path: (Assets\/[^\n]+\.unity)/g;
|
|
28
|
-
let m: RegExpExecArray | null;
|
|
29
|
-
while ((m = pathRe.exec(content)) !== null) {
|
|
30
|
-
const fullPath = m[1];
|
|
31
|
-
const name = fullPath.replace(/^.*\//, "").replace(/\.unity$/, "");
|
|
32
|
-
scenes.push({ index, path: fullPath, name });
|
|
33
|
-
index++;
|
|
34
|
-
}
|
|
35
|
-
return scenes;
|
|
16
|
+
return "";
|
|
36
17
|
}
|
|
37
18
|
|
|
38
19
|
async function main() {
|
|
@@ -46,67 +27,302 @@ async function main() {
|
|
|
46
27
|
version: "1.0.0",
|
|
47
28
|
});
|
|
48
29
|
|
|
30
|
+
const text = (s: string) => ({ content: [{ type: "text" as const, text: s }] });
|
|
31
|
+
const json = (o: unknown) => text(JSON.stringify(o, null, 2));
|
|
32
|
+
|
|
33
|
+
// --- Existing + expanded project info ---
|
|
49
34
|
server.registerTool(
|
|
50
35
|
"get_project_info",
|
|
51
36
|
{
|
|
52
|
-
description: "Get Unity project info
|
|
37
|
+
description: "Get Unity project info: path, Unity version, build scene count, player/product name.",
|
|
53
38
|
inputSchema: {},
|
|
54
39
|
},
|
|
55
40
|
async () => {
|
|
56
|
-
|
|
57
|
-
const
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
if (m) unityVersion = m[1].trim();
|
|
61
|
-
}
|
|
62
|
-
const scenes = getBuildScenes(projectRoot);
|
|
63
|
-
const text = [
|
|
41
|
+
const unityVersion = R.getUnityVersion(projectRoot);
|
|
42
|
+
const scenes = R.getBuildScenes(projectRoot);
|
|
43
|
+
const player = R.getPlayerSettings(projectRoot);
|
|
44
|
+
const lines = [
|
|
64
45
|
`Project root: ${projectRoot}`,
|
|
65
46
|
`Unity version: ${unityVersion}`,
|
|
66
47
|
`Build scenes: ${scenes.length}`,
|
|
67
|
-
scenes.map((s) => ` ${s.index}: ${s.name} (${s.path})`)
|
|
68
|
-
|
|
69
|
-
|
|
48
|
+
...scenes.map((s) => ` ${s.index}: ${s.name} (${s.path})`),
|
|
49
|
+
"",
|
|
50
|
+
"Player / product (if available):",
|
|
51
|
+
...Object.entries(player).map(([k, v]) => ` ${k}: ${v}`),
|
|
52
|
+
];
|
|
53
|
+
return text(lines.join("\n"));
|
|
70
54
|
}
|
|
71
55
|
);
|
|
72
56
|
|
|
73
57
|
server.registerTool(
|
|
74
58
|
"list_build_scenes",
|
|
59
|
+
{ description: "List scenes in EditorBuildSettings (build order) as JSON.", inputSchema: {} },
|
|
60
|
+
async () => json(R.getBuildScenes(projectRoot))
|
|
61
|
+
);
|
|
62
|
+
|
|
63
|
+
server.registerTool(
|
|
64
|
+
"read_agent_docs",
|
|
65
|
+
{
|
|
66
|
+
description: "Read .agents/AGENT.md and optionally REPO_UNDERSTANDING.md.",
|
|
67
|
+
inputSchema: {
|
|
68
|
+
include_repo_understanding: z.boolean().optional().describe("If true, also return REPO_UNDERSTANDING.md").default(false),
|
|
69
|
+
},
|
|
70
|
+
},
|
|
71
|
+
async (args: unknown) => {
|
|
72
|
+
const include = (args as { include_repo_understanding?: boolean })?.include_repo_understanding ?? false;
|
|
73
|
+
let content = R.readFileSafe(projectRoot, ".agents", "AGENT.md") ?? "(No .agents/AGENT.md found)";
|
|
74
|
+
if (include) {
|
|
75
|
+
const rep = R.readFileSafe(projectRoot, "REPO_UNDERSTANDING.md");
|
|
76
|
+
if (rep) content += "\n\n---\n\n# REPO_UNDERSTANDING.md\n\n" + rep;
|
|
77
|
+
}
|
|
78
|
+
return text(content);
|
|
79
|
+
}
|
|
80
|
+
);
|
|
81
|
+
|
|
82
|
+
// --- 1. Project & package info ---
|
|
83
|
+
server.registerTool(
|
|
84
|
+
"get_player_settings",
|
|
85
|
+
{ description: "Get Player/ProjectSettings (product name, company, bundle ID, version, etc.).", inputSchema: {} },
|
|
86
|
+
async () => json(R.getPlayerSettings(projectRoot))
|
|
87
|
+
);
|
|
88
|
+
|
|
89
|
+
server.registerTool(
|
|
90
|
+
"list_packages",
|
|
91
|
+
{ description: "List Unity packages from Packages/manifest.json (and optional packages-lock.json).", inputSchema: {} },
|
|
92
|
+
async () => json(R.getPackages(projectRoot))
|
|
93
|
+
);
|
|
94
|
+
|
|
95
|
+
server.registerTool(
|
|
96
|
+
"get_quality_settings",
|
|
97
|
+
{ description: "Get QualitySettings (quality levels) from ProjectSettings.", inputSchema: {} },
|
|
98
|
+
async () => json(R.getQualitySettings(projectRoot))
|
|
99
|
+
);
|
|
100
|
+
|
|
101
|
+
server.registerTool(
|
|
102
|
+
"get_scripting_defines",
|
|
103
|
+
{ description: "Get global and per-assembly scripting define symbols.", inputSchema: {} },
|
|
104
|
+
async () => json(R.getScriptingDefines(projectRoot))
|
|
105
|
+
);
|
|
106
|
+
|
|
107
|
+
// --- 2. Code & assemblies ---
|
|
108
|
+
server.registerTool(
|
|
109
|
+
"list_assemblies",
|
|
75
110
|
{
|
|
76
|
-
description: "List
|
|
111
|
+
description: "List assembly definitions (.asmdef) with path, name, references, platforms.",
|
|
77
112
|
inputSchema: {},
|
|
78
113
|
},
|
|
79
|
-
async () =>
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
114
|
+
async () => json(R.getAssemblyDefinitions(projectRoot))
|
|
115
|
+
);
|
|
116
|
+
|
|
117
|
+
server.registerTool(
|
|
118
|
+
"list_scripts",
|
|
119
|
+
{
|
|
120
|
+
description: "List all C# scripts under Assets. Optionally filter by folder prefix (e.g. Scripts).",
|
|
121
|
+
inputSchema: {
|
|
122
|
+
folder: z.string().optional().describe("Optional folder under Assets, e.g. Scripts/Runtime"),
|
|
123
|
+
},
|
|
124
|
+
},
|
|
125
|
+
async (args: unknown) => json(R.listScripts(projectRoot, (args as { folder?: string })?.folder))
|
|
126
|
+
);
|
|
127
|
+
|
|
128
|
+
server.registerTool(
|
|
129
|
+
"find_scripts_by_content",
|
|
130
|
+
{
|
|
131
|
+
description: "Find C# scripts that contain a type or pattern (e.g. MonoBehaviour, ScriptableObject, or custom). Optionally filter by namespace.",
|
|
132
|
+
inputSchema: {
|
|
133
|
+
pattern: z.string().describe("Pattern to search for, e.g. MonoBehaviour or a type name"),
|
|
134
|
+
namespace_filter: z.string().optional().describe("Optional namespace pattern, e.g. Game.*"),
|
|
135
|
+
},
|
|
136
|
+
},
|
|
137
|
+
async (args: unknown) => {
|
|
138
|
+
const a = args as { pattern: string; namespace_filter?: string };
|
|
139
|
+
return json(R.findScriptsByContent(projectRoot, a.pattern, a.namespace_filter));
|
|
83
140
|
}
|
|
84
141
|
);
|
|
85
142
|
|
|
143
|
+
// --- 3. Scenes ---
|
|
86
144
|
server.registerTool(
|
|
87
|
-
"
|
|
145
|
+
"list_all_scenes",
|
|
146
|
+
{ description: "List all .unity scene files under Assets (not only build-indexed).", inputSchema: {} },
|
|
147
|
+
async () => json(R.getAllScenes(projectRoot))
|
|
148
|
+
);
|
|
149
|
+
|
|
150
|
+
server.registerTool(
|
|
151
|
+
"get_scene_summary",
|
|
152
|
+
{
|
|
153
|
+
description: "Get a short summary of a scene: root GameObject names and approximate component count.",
|
|
154
|
+
inputSchema: { scene_path: z.string().describe("Path relative to project, e.g. Assets/Scenes/Main.unity") },
|
|
155
|
+
},
|
|
156
|
+
async (args: unknown) => json(R.getSceneSummary(projectRoot, (args as { scene_path: string }).scene_path))
|
|
157
|
+
);
|
|
158
|
+
|
|
159
|
+
// --- 4. Prefabs ---
|
|
160
|
+
server.registerTool(
|
|
161
|
+
"list_prefabs",
|
|
162
|
+
{
|
|
163
|
+
description: "List all .prefab files. Optionally filter by path prefix under Assets.",
|
|
164
|
+
inputSchema: { path_prefix: z.string().optional().describe("e.g. Prefabs/Characters") },
|
|
165
|
+
},
|
|
166
|
+
async (args: unknown) => json(R.getPrefabs(projectRoot, (args as { path_prefix?: string })?.path_prefix))
|
|
167
|
+
);
|
|
168
|
+
|
|
169
|
+
// --- 5. Assets & references ---
|
|
170
|
+
server.registerTool(
|
|
171
|
+
"find_references",
|
|
88
172
|
{
|
|
89
|
-
description: "
|
|
173
|
+
description: "Find all assets (scenes, prefabs, materials, etc.) that reference the given asset. Pass asset path (e.g. Assets/Texture.png) or GUID.",
|
|
90
174
|
inputSchema: {
|
|
91
|
-
|
|
175
|
+
asset_path_or_guid: z.string().describe("Asset path relative to project (e.g. Assets/My.asset) or 32-char GUID"),
|
|
92
176
|
},
|
|
93
177
|
},
|
|
94
178
|
async (args: unknown) => {
|
|
95
|
-
const
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
text = readFileSync(agentPath, "utf-8");
|
|
179
|
+
const input = (args as { asset_path_or_guid: string }).asset_path_or_guid.trim();
|
|
180
|
+
let guid: string;
|
|
181
|
+
if (/^[a-f0-9]{32}$/i.test(input)) {
|
|
182
|
+
guid = input;
|
|
100
183
|
} else {
|
|
101
|
-
|
|
184
|
+
guid = R.getGuidFromMeta(projectRoot, input) ?? "";
|
|
185
|
+
if (!guid) return text(`No .meta found for ${input}; cannot resolve GUID.`);
|
|
102
186
|
}
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
187
|
+
const refs = R.findReferencesToGuid(projectRoot, guid);
|
|
188
|
+
return json({ guid, references: refs });
|
|
189
|
+
}
|
|
190
|
+
);
|
|
191
|
+
|
|
192
|
+
server.registerTool(
|
|
193
|
+
"get_asset_folder_tree",
|
|
194
|
+
{
|
|
195
|
+
description: "Get a tree of Assets folder (directories and file names). Useful for understanding project layout.",
|
|
196
|
+
inputSchema: { max_depth: z.number().optional().describe("Max folder depth, default 4").default(4) },
|
|
197
|
+
},
|
|
198
|
+
async (args: unknown) => json(R.getAssetFolderTree(projectRoot, (args as { max_depth?: number })?.max_depth ?? 4))
|
|
199
|
+
);
|
|
200
|
+
|
|
201
|
+
server.registerTool(
|
|
202
|
+
"list_assets_by_extension",
|
|
203
|
+
{
|
|
204
|
+
description: "List assets by file extension (e.g. .png, .fbx, .mp3). Optionally under a folder.",
|
|
205
|
+
inputSchema: {
|
|
206
|
+
extension: z.string().describe("e.g. .png, .fbx, .mp3"),
|
|
207
|
+
folder: z.string().optional().describe("Optional folder under Assets"),
|
|
208
|
+
},
|
|
209
|
+
},
|
|
210
|
+
async (args: unknown) => {
|
|
211
|
+
const a = args as { extension: string; folder?: string };
|
|
212
|
+
const ext = a.extension.startsWith(".") ? a.extension : `.${a.extension}`;
|
|
213
|
+
return json(R.listAssetsByExtension(projectRoot, ext, a.folder));
|
|
214
|
+
}
|
|
215
|
+
);
|
|
216
|
+
|
|
217
|
+
// --- 6. Materials & shaders ---
|
|
218
|
+
server.registerTool(
|
|
219
|
+
"list_materials",
|
|
220
|
+
{
|
|
221
|
+
description: "List .mat materials; optionally under a folder. Includes shader GUID when readable.",
|
|
222
|
+
inputSchema: { folder: z.string().optional() },
|
|
223
|
+
},
|
|
224
|
+
async (args: unknown) => json(R.getMaterials(projectRoot, (args as { folder?: string })?.folder))
|
|
225
|
+
);
|
|
226
|
+
|
|
227
|
+
server.registerTool(
|
|
228
|
+
"list_shaders",
|
|
229
|
+
{ description: "List all .shader files in Assets and Packages.", inputSchema: {} },
|
|
230
|
+
async () => json(R.getShaders(projectRoot))
|
|
231
|
+
);
|
|
232
|
+
|
|
233
|
+
// --- 7. Animation ---
|
|
234
|
+
server.registerTool(
|
|
235
|
+
"list_animator_controllers",
|
|
236
|
+
{ description: "List all Animator Controller (.controller) assets.", inputSchema: {} },
|
|
237
|
+
async () => json(R.getAnimatorControllers(projectRoot))
|
|
238
|
+
);
|
|
239
|
+
|
|
240
|
+
server.registerTool(
|
|
241
|
+
"list_animation_clips",
|
|
242
|
+
{ description: "List all Animation Clip (.anim) assets.", inputSchema: {} },
|
|
243
|
+
async () => json(R.getAnimationClips(projectRoot))
|
|
244
|
+
);
|
|
245
|
+
|
|
246
|
+
server.registerTool(
|
|
247
|
+
"get_animator_states",
|
|
248
|
+
{
|
|
249
|
+
description: "Get state names from an Animator Controller (.controller) file.",
|
|
250
|
+
inputSchema: { controller_path: z.string().describe("e.g. Assets/Animations/Player.controller") },
|
|
251
|
+
},
|
|
252
|
+
async (args: unknown) => json(R.getAnimatorStates(projectRoot, (args as { controller_path: string }).controller_path))
|
|
253
|
+
);
|
|
254
|
+
|
|
255
|
+
// --- 8. Audio ---
|
|
256
|
+
server.registerTool(
|
|
257
|
+
"list_audio_clips",
|
|
258
|
+
{ description: "List audio clip files (.wav, .mp3, .ogg, .aiff) under Assets.", inputSchema: {} },
|
|
259
|
+
async () => json(R.getAudioClips(projectRoot))
|
|
260
|
+
);
|
|
261
|
+
|
|
262
|
+
server.registerTool(
|
|
263
|
+
"list_audio_mixers",
|
|
264
|
+
{ description: "List Audio Mixer (.mixer) assets.", inputSchema: {} },
|
|
265
|
+
async () => json(R.getAudioMixers(projectRoot))
|
|
266
|
+
);
|
|
267
|
+
|
|
268
|
+
// --- 9. Addressables ---
|
|
269
|
+
server.registerTool(
|
|
270
|
+
"get_addressables_info",
|
|
271
|
+
{ description: "Get Addressables groups and config path if the project uses Addressables.", inputSchema: {} },
|
|
272
|
+
async () => json(R.getAddressablesInfo(projectRoot))
|
|
273
|
+
);
|
|
274
|
+
|
|
275
|
+
// --- 10. Localization ---
|
|
276
|
+
server.registerTool(
|
|
277
|
+
"get_localization_tables",
|
|
278
|
+
{ description: "List localization table files under Assets/Localization if present.", inputSchema: {} },
|
|
279
|
+
async () => json(R.getLocalizationTables(projectRoot))
|
|
280
|
+
);
|
|
281
|
+
|
|
282
|
+
// --- 11. Input ---
|
|
283
|
+
server.registerTool(
|
|
284
|
+
"get_input_axes",
|
|
285
|
+
{ description: "Get input axes from InputManager (name, descriptive name, positive button).", inputSchema: {} },
|
|
286
|
+
async () => json(R.getInputAxes(projectRoot))
|
|
287
|
+
);
|
|
288
|
+
|
|
289
|
+
// --- 12. Tags & layers ---
|
|
290
|
+
server.registerTool(
|
|
291
|
+
"get_tags_and_layers",
|
|
292
|
+
{ description: "Get Tags and Layers from TagManager.", inputSchema: {} },
|
|
293
|
+
async () => json(R.getTagsAndLayers(projectRoot))
|
|
294
|
+
);
|
|
295
|
+
|
|
296
|
+
// --- 13. Testing ---
|
|
297
|
+
server.registerTool(
|
|
298
|
+
"list_test_assemblies",
|
|
299
|
+
{ description: "List assembly definitions that look like test assemblies (path contains test/editor).", inputSchema: {} },
|
|
300
|
+
async () => json(R.getTestAssemblies(projectRoot))
|
|
301
|
+
);
|
|
302
|
+
|
|
303
|
+
// --- 14. Docs & conventions ---
|
|
304
|
+
server.registerTool(
|
|
305
|
+
"get_repo_docs",
|
|
306
|
+
{
|
|
307
|
+
description: "Read README, CONTRIBUTING, .cursorrules, CODING_STANDARDS, STYLE if present at project root.",
|
|
308
|
+
inputSchema: {},
|
|
309
|
+
},
|
|
310
|
+
async () => json(R.getRepoDocs(projectRoot))
|
|
311
|
+
);
|
|
312
|
+
|
|
313
|
+
// --- 15. CI / versioning ---
|
|
314
|
+
server.registerTool(
|
|
315
|
+
"get_project_version",
|
|
316
|
+
{ description: "Get project/bundle version from PlayerSettings.", inputSchema: {} },
|
|
317
|
+
async () => text(R.getProjectVersion(projectRoot))
|
|
318
|
+
);
|
|
319
|
+
|
|
320
|
+
server.registerTool(
|
|
321
|
+
"get_changelog",
|
|
322
|
+
{ description: "Read CHANGELOG.md or CHANGELOG if present.", inputSchema: {} },
|
|
323
|
+
async () => {
|
|
324
|
+
const content = R.getChangelog(projectRoot);
|
|
325
|
+
return text(content ?? "(No CHANGELOG found)");
|
|
110
326
|
}
|
|
111
327
|
);
|
|
112
328
|
|